xref: /petsc/config/BuildSystem/RDict.py (revision 2b8d69ca7ea5fe9190df62c1dce3bbd66fce84dd)
1#!/usr/bin/env python
2'''A remote dictionary server
3
4    RDict is a typed, hierarchical, persistent dictionary intended to manage
5    all arguments or options for a program. The interface remains exactly the
6    same as dict, but the storage is more complicated.
7
8    Argument typing is handled by wrapping all values stored in the dictionary
9    with nargs.Arg or a subclass. A user can call setType() to set the type of
10    an argument without any value being present. Whenever __getitem__() or
11    __setitem__() is called, values are extracted or replaced in the wrapper.
12    These wrappers can be accessed directly using getType(), setType(), and
13    types().
14
15    Hierarchy is allowed using a single "parent" dictionary. All operations
16    cascade to the parent. For instance, the length of the dictionary is the
17    number of local keys plus the number of keys in the parent, and its
18    parent, etc. Also, a dictionary need not have a parent. If a key does not
19    appear in the local dicitonary, the call if passed to the parent. However,
20    in this case we see that local keys can shadow those in a parent.
21    Communication with the parent is handled using sockets, with the parent
22    being a server and the interactive dictionary a client.
23
24    The default persistence mechanism is a pickle file, RDict.db, written
25    whenever an argument is changed locally. A timer thread is created after
26    an initial change, so that many rapid changes do not cause many writes.
27    Each dictionary only saves its local entries, so all parents also
28    separately save data in different RDict.db files. Each time a dictionary
29    is created, the current directory is searched for an RDict.db file, and
30    if found the contents are loaded into the dictionary.
31
32    This script also provides some default actions:
33
34      - server [parent]
35        Starts a server in the current directory with an optional parent. This
36        server will accept socket connections from other dictionaries and act
37        as a parent.
38
39      - client [parent]
40        Creates a dictionary in the current directory with an optional parent
41        and lists the contents. Notice that the contents may come from either
42        an RDict.db file in the current directory, or from the parent.
43
44      - clear [parent]
45        Creates a dictionary in the current directory with an optional parent
46        and clears the contents. Notice that this will also clear the parent.
47
48      - insert <parent> <key> <value>
49        Creates a dictionary in the current directory with a parent, and inserts
50        the key-value pair. If "parent" is "None", no parent is assigned.
51
52      - remove <parent> <key>
53        Creates a dictionary in the current directory with a parent, and removes
54        the given key. If "parent" is "None", no parent is assigned.
55'''
56try:
57  import project          # This is necessary for us to create Project objects on load
58  import build.buildGraph # This is necessary for us to create BuildGraph objects on load
59except ImportError:
60  pass
61import nargs
62
63import cPickle
64import os
65import sys
66useThreads = nargs.Arg.findArgument('useThreads', sys.argv[1:])
67if useThreads is None:
68  useThreads = 1
69else:
70  useThreads = int(useThreads)
71
72class RDict(dict):
73  '''An RDict is a typed dictionary, which may be hierarchically composed. All elements derive from the
74Arg class, which wraps the usual value.'''
75  # The server will self-shutdown after this many seconds
76  shutdownDelay = 60*60*5
77
78  def __init__(self, parentAddr = None, parentDirectory = None, load = 1, autoShutdown = 1, readonly = False):
79    import atexit
80    import time
81    import xdrlib
82
83    self.logFile         = None
84    self.setupLogFile()
85    self.target          = ['default']
86    self.parent          = None
87    self.saveTimer       = None
88    self.shutdownTimer   = None
89    self.lastAccess      = time.time()
90    self.saveFilename    = 'RDict.db'
91    self.addrFilename    = 'RDict.loc'
92    self.parentAddr      = parentAddr
93    self.isServer        = 0
94    self.readonly        = readonly
95    self.parentDirectory = parentDirectory
96    self.packer          = xdrlib.Packer()
97    self.unpacker        = xdrlib.Unpacker('')
98    self.stopCmd         = cPickle.dumps(('stop',))
99    self.writeLogLine('Greetings')
100    self.connectParent(self.parentAddr, self.parentDirectory)
101    if load: self.load()
102    if autoShutdown and useThreads:
103      atexit.register(self.shutdown)
104    self.writeLogLine('SERVER: Last access '+str(self.lastAccess))
105    return
106
107  def __getstate__(self):
108    '''Remove any parent socket object, the XDR translators, and the log file from the dictionary before pickling'''
109    self.writeLogLine('Pickling RDict')
110    d = self.__dict__.copy()
111    if 'parent'    in d: del d['parent']
112    if 'saveTimer' in d: del d['saveTimer']
113    if '_setCommandLine' in d: del d['_setCommandLine']
114    del d['packer']
115    del d['unpacker']
116    del d['logFile']
117    return d
118
119  def __setstate__(self, d):
120    '''Reconnect the parent socket object, recreate the XDR translators and reopen the log file after unpickling'''
121    self.logFile  = file('RDict.log', 'a')
122    self.writeLogLine('Unpickling RDict')
123    self.__dict__.update(d)
124    import xdrlib
125    self.packer   = xdrlib.Packer()
126    self.unpacker = xdrlib.Unpacker('')
127    self.connectParent(self.parentAddr, self.parentDirectory)
128    return
129
130  def setupLogFile(self, filename = 'RDict.log'):
131    if not self.logFile is None:
132      self.logFile.close()
133    if os.path.isfile(filename) and os.stat(filename).st_size > 10*1024*1024:
134      if os.path.isfile(filename+'.bkp'):
135        os.remove(filename+'.bkp')
136      os.rename(filename, filename+'.bkp')
137      self.logFile = file(filename, 'w')
138    else:
139      self.logFile = file(filename, 'a')
140    return
141
142  def writeLogLine(self, message):
143    '''Writes the message to the log along with the current time'''
144    import time
145    self.logFile.write('('+str(os.getpid())+')('+str(id(self))+')'+message+' ['+time.asctime(time.localtime())+']\n')
146    self.logFile.flush()
147    return
148
149  def __len__(self):
150    '''Returns the length of both the local and parent dictionaries'''
151    length = dict.__len__(self)
152    if not self.parent is None:
153      length = length + self.send()
154    return length
155
156  def getType(self, key):
157    '''Checks for the key locally, and if not found consults the parent. Returns the Arg object or None if not found.'''
158    if dict.has_key(self, key):
159      self.writeLogLine('getType: Getting local type for '+key+' '+str(dict.__getitem__(self, key)))
160      return dict.__getitem__(self, key)
161    elif not self.parent is None:
162      return self.send(key)
163    return None
164
165  def __getitem__(self, key):
166    '''Checks for the key locally, and if not found consults the parent. Returns the value of the Arg.
167       - If the value has not been set, the user will be prompted for input'''
168    if dict.has_key(self, key):
169      self.writeLogLine('__getitem__: '+key+' has local type')
170      pass
171    elif not self.parent is None:
172      self.writeLogLine('__getitem__: Checking parent value')
173      if self.send(key, operation = 'has_key'):
174        self.writeLogLine('__getitem__: Parent has value')
175        return self.send(key)
176      else:
177        self.writeLogLine('__getitem__: Checking parent type')
178        arg = self.send(key, operation = 'getType')
179        if not arg:
180          self.writeLogLine('__getitem__: Parent has no type')
181          arg = nargs.Arg(key)
182        try:
183          value = arg.getValue()
184        except AttributeError, e:
185          self.writeLogLine('__getitem__: Parent had invalid entry: '+str(e))
186          arg   = nargs.Arg(key)
187          value = arg.getValue()
188        self.writeLogLine('__getitem__: Setting parent value '+str(value))
189        self.send(key, value, operation = '__setitem__')
190        return value
191    else:
192      self.writeLogLine('__getitem__: Setting local type for '+key)
193      dict.__setitem__(self, key, nargs.Arg(key))
194      self.save()
195    self.writeLogLine('__getitem__: Setting local value for '+key)
196    return dict.__getitem__(self, key).getValue()
197
198  def setType(self, key, value, forceLocal = 0):
199    '''Checks for the key locally, and if not found consults the parent. Sets the type for this key.
200       - If a value for the key already exists, it is converted to the new type'''
201    if not isinstance(value, nargs.Arg):
202      raise TypeError('An argument type must be a subclass of Arg')
203    value.setKey(key)
204    if forceLocal or self.parent is None or dict.has_key(self, key):
205      if dict.has_key(self, key):
206        v = dict.__getitem__(self, key)
207        if v.isValueSet():
208          try:
209            value.setValue(v.getValue())
210          except TypeError: pass
211      dict.__setitem__(self, key, value)
212      self.save()
213    else:
214      return self.send(key, value)
215    return
216
217  def __setitem__(self, key, value):
218    '''Checks for the key locally, and if not found consults the parent. Sets the value of the Arg.'''
219    if not dict.has_key(self, key):
220      if not self.parent is None:
221        return self.send(key, value)
222      else:
223        dict.__setitem__(self, key, nargs.Arg(key))
224    dict.__getitem__(self, key).setValue(value)
225    self.writeLogLine('__setitem__: Set value for '+key+' to '+str(dict.__getitem__(self, key)))
226    self.save()
227    return
228
229  def __delitem__(self, key):
230    '''Checks for the key locally, and if not found consults the parent. Deletes the Arg completely.'''
231    if dict.has_key(self, key):
232      dict.__delitem__(self, key)
233      self.save()
234    elif not self.parent is None:
235      self.send(key)
236    return
237
238  def clear(self):
239    '''Clears both the local and parent dictionaries'''
240    if dict.__len__(self):
241      dict.clear(self)
242      self.save()
243    if not self.parent is None:
244      self.send()
245    return
246
247  def __contains__(self, key):
248    '''This method just calls self.has_key(key)'''
249    return self.has_key(key)
250
251  def has_key(self, key):
252    '''Checks for the key locally, and if not found consults the parent. Then checks whether the value has been set'''
253    if dict.has_key(self, key):
254      if dict.__getitem__(self, key).isValueSet():
255        self.writeLogLine('has_key: Have value for '+key)
256      else:
257        self.writeLogLine('has_key: Do not have value for '+key)
258      return dict.__getitem__(self, key).isValueSet()
259    elif not self.parent is None:
260      return self.send(key)
261    return 0
262
263  def get(self, key, default=None):
264    if self.has_key(key):
265      return self.__getitem__(key)
266    else:
267      return default
268
269  def hasType(self, key):
270    '''Checks for the key locally, and if not found consults the parent. Then checks whether the type has been set'''
271    if dict.has_key(self, key):
272      return 1
273    elif not self.parent is None:
274      return self.send(key)
275    return 0
276
277  def items(self):
278    '''Return a list of all accessible items, as (key, value) pairs.'''
279    l = dict.items(self)
280    if not self.parent is None:
281      l.extend(self.send())
282    return l
283
284  def localitems(self):
285    '''Return a list of all the items stored locally, as (key, value) pairs.'''
286    return dict.items(self)
287
288  def keys(self):
289    '''Returns the list of keys in both the local and parent dictionaries'''
290    keyList = filter(lambda key: dict.__getitem__(self, key).isValueSet(), dict.keys(self))
291    if not self.parent is None:
292      keyList.extend(self.send())
293    return keyList
294
295  def types(self):
296    '''Returns the list of keys for which types are defined in both the local and parent dictionaries'''
297    keyList = dict.keys(self)
298    if not self.parent is None:
299      keyList.extend(self.send())
300    return keyList
301
302  def update(self, d):
303    '''Update the dictionary with the contents of d'''
304    for k in d:
305      self[k] = d[k]
306    return
307
308  def updateTypes(self, d):
309    '''Update types locally, which is equivalent to the dict.update() method'''
310    return dict.update(self, d)
311
312  def insertArg(self, key, value, arg):
313    '''Insert a (key, value) pair into the dictionary. If key is None, arg is put into the target list.'''
314    if not key is None:
315      self[key] = value
316    else:
317      if not self.target == ['default']:
318        self.target.append(arg)
319      else:
320        self.target = [arg]
321    return
322
323  def insertArgs(self, args):
324    '''Insert some text arguments into the dictionary (list and dictionaries are recognized)'''
325    import UserDict
326
327    if isinstance(args, list):
328      for arg in args:
329        (key, value) = nargs.Arg.parseArgument(arg)
330        self.insertArg(key, value, arg)
331    # Necessary since os.environ is a UserDict
332    elif isinstance(args, dict) or isinstance(args, UserDict.UserDict):
333      for key in args.keys():
334        if isinstance(args[key], str):
335          value = nargs.Arg.parseValue(args[key])
336        else:
337          value = args[key]
338        self.insertArg(key, value, None)
339    elif isinstance(args, str):
340        (key, value) = nargs.Arg.parseArgument(args)
341        self.insertArg(key, value, args)
342    return
343
344  def hasParent(self):
345    '''Return True if this RDict has a parent dictionary'''
346    return not self.parent is None
347
348  def getServerAddr(self, dir):
349    '''Read the server socket address (in pickled form) from a file, usually RDict.loc
350       - If we fail to connect to the server specified in the file, we spawn it using startServer()'''
351    filename = os.path.join(dir, self.addrFilename)
352    if not os.path.exists(filename):
353      self.startServer(filename)
354    if not os.path.exists(filename):
355      raise RuntimeError('Server address file does not exist: '+filename)
356    try:
357      f    = open(filename, 'r')
358      addr = cPickle.load(f)
359      f.close()
360      return addr
361    except Exception, e:
362      self.writeLogLine('CLIENT: Exception during server address determination: '+str(e.__class__)+': '+str(e))
363    raise RuntimeError('Could not get server address in '+filename)
364
365  def writeServerAddr(self, server):
366    '''Write the server socket address (in pickled form) to a file, usually RDict.loc.'''
367    f = file(self.addrFilename, 'w')
368    cPickle.dump(server.server_address, f)
369    f.close()
370    self.writeLogLine('SERVER: Wrote lock file '+os.path.abspath(self.addrFilename))
371    return
372
373  def startServer(self, addrFilename):
374    '''Spawn a new RDict server in the parent directory'''
375    import RDict # Need this to locate server script
376    import sys
377    import time
378    import distutils.sysconfig
379
380    self.writeLogLine('CLIENT: Spawning a new server with lock file '+os.path.abspath(addrFilename))
381    if os.path.exists(addrFilename):
382      os.remove(addrFilename)
383    oldDir      = os.getcwd()
384    source      = os.path.join(os.path.dirname(os.path.abspath(sys.modules['RDict'].__file__)), 'RDict.py')
385    interpreter = os.path.join(distutils.sysconfig.get_config_var('BINDIR'), distutils.sysconfig.get_config_var('PYTHON'))
386    if not os.path.isfile(interpreter):
387      interpreter = 'python'
388    os.chdir(os.path.dirname(addrFilename))
389    self.writeLogLine('CLIENT: Executing '+interpreter+' '+source+' server"')
390    try:
391      os.spawnvp(os.P_NOWAIT, interpreter, [interpreter, source, 'server'])
392    except:
393      self.writeLogLine('CLIENT: os.spawnvp failed.\n \
394      This is a typical problem on CYGWIN systems.  If you are using CYGWIN,\n \
395      you can fix this problem by running /bin/rebaseall.  If you do not have\n \
396      this program, you can install it with the CYGWIN installer in the package\n \
397      Rebase, under the category System.  You must run /bin/rebaseall after\n \
398      turning off all cygwin services -- in particular sshd, if any such services\n \
399      are running.  For more information about rebase, go to http://www.cygwin.com')
400      print '\n \
401      This is a typical problem on CYGWIN systems.  If you are using CYGWIN,\n \
402      you can fix this problem by running /bin/rebaseall.  If you do not have\n \
403      this program, you can install it with the CYGWIN installer in the package\n \
404      Rebase, under the category System.  You must run /bin/rebaseall after\n \
405      turning off all cygwin services -- in particular sshd, if any such services\n \
406      are running.  For more information about rebase, go to http://www.cygwin.com\n'
407      raise
408    os.chdir(oldDir)
409    timeout = 1
410    for i in range(10):
411      time.sleep(timeout)
412      timeout *= 2
413      if timeout > 100: timeout = 100
414      if os.path.exists(addrFilename): return
415    self.writeLogLine('CLIENT: Could not start server')
416    return
417
418  def connectParent(self, addr, dir):
419    '''Try to connect to a parent RDict server
420       - If addr and dir are both None, this operation fails
421       - If addr is None, check for an address file in dir'''
422    if addr is None:
423      if dir is None: return 0
424      addr = self.getServerAddr(dir)
425
426    import socket
427    import errno
428    connected = 0
429    s         = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
430    timeout   = 1
431    for i in range(10):
432      try:
433        self.writeLogLine('CLIENT: Trying to connect to '+str(addr))
434        s.connect(addr)
435        connected = 1
436        break
437      except socket.error, e:
438        self.writeLogLine('CLIENT: Failed to connect: '+str(e))
439        if e[0] == errno.ECONNREFUSED:
440          try:
441            import time
442            time.sleep(timeout)
443            timeout *= 2
444            if timeout > 100: timeout = 100
445          except KeyboardInterrupt:
446            break
447          # Try to spawn parent
448          if dir:
449            filename = os.path.join(dir, self.addrFilename)
450            if os.path.isfile(filename):
451              os.remove(filename)
452            self.startServer(filename)
453      except Exception, e:
454        self.writeLogLine('CLIENT: Failed to connect: '+str(e.__class__)+': '+str(e))
455    if not connected:
456      self.writeLogLine('CLIENT: Failed to connect to parent')
457      return 0
458    self.parent = s
459    self.writeLogLine('CLIENT: Connected to '+str(self.parent))
460    return 1
461
462  def sendPacket(self, s, packet, source = 'Unknown', isPickled = 0):
463    '''Pickle the input packet. Send first the size of the pickled string in 32-bit integer, and then the string itself'''
464    self.writeLogLine(source+': Sending packet '+str(packet))
465    if isPickled:
466      p = packet
467    else:
468      p = cPickle.dumps(packet)
469    self.packer.reset()
470    self.packer.pack_uint(len(p))
471    if hasattr(s, 'write'):
472      s.write(self.packer.get_buffer())
473      s.write(p)
474    else:
475      s.sendall(self.packer.get_buffer())
476      s.sendall(p)
477    self.writeLogLine(source+': Sent packet')
478    return
479
480  def recvPacket(self, s, source = 'Unknown'):
481    '''Receive first the size of the pickled string in a 32-bit integer, and then the string itself. Return the unpickled object'''
482    self.writeLogLine(source+': Receiving packet')
483    if hasattr(s, 'read'):
484      s.read(4)
485      value = cPickle.load(s)
486    else:
487      # I probably need to check that it actually read these 4 bytes
488      self.unpacker.reset(s.recv(4))
489      length    = self.unpacker.unpack_uint()
490      objString = ''
491      while len(objString) < length:
492        objString += s.recv(length - len(objString))
493      value = cPickle.loads(objString)
494    self.writeLogLine(source+': Received packet '+str(value))
495    return value
496
497  def send(self, key = None, value = None, operation = None):
498    '''Send a request to the parent'''
499    import inspect
500
501    objString = ''
502    for i in range(3):
503      try:
504        packet = []
505        if operation is None:
506          operation = inspect.stack()[1][3]
507        packet.append(operation)
508        if not key is None:
509          packet.append(key)
510          if not value is None:
511            packet.append(value)
512        self.sendPacket(self.parent, tuple(packet), source = 'CLIENT')
513        response = self.recvPacket(self.parent, source = 'CLIENT')
514        break
515      except IOError, e:
516        self.writeLogLine('CLIENT: IOError '+str(e))
517        if e.errno == 32:
518          self.connectParent(self.parentAddr, self.parentDirectory)
519      except Exception, e:
520        self.writeLogLine('CLIENT: Exception '+str(e)+' '+str(e.__class__))
521    try:
522      if isinstance(response, Exception):
523        self.writeLogLine('CLIENT: Got an exception '+str(response))
524        raise response
525      else:
526        self.writeLogLine('CLIENT: Received value '+str(response)+' '+str(type(response)))
527    except UnboundLocalError:
528      self.writeLogLine('CLIENT: Could not unpickle response')
529      response  = None
530    return response
531
532  def serve(self):
533    '''Start a server'''
534    import socket
535    import SocketServer
536
537    if not useThreads:
538      raise RuntimeError('Cannot run a server if threads are disabled')
539
540    class ProcessHandler(SocketServer.StreamRequestHandler):
541      def handle(self):
542        import time
543
544        self.server.rdict.lastAccess = time.time()
545        self.server.rdict.writeLogLine('SERVER: Started new handler')
546        while 1:
547          try:
548            value = self.server.rdict.recvPacket(self.rfile, source = 'SERVER')
549          except EOFError, e:
550            self.server.rdict.writeLogLine('SERVER: EOFError receiving packet '+str(e)+' '+str(e.__class__))
551            return
552          except Exception, e:
553            self.server.rdict.writeLogLine('SERVER: Error receiving packet '+str(e)+' '+str(e.__class__))
554            self.server.rdict.sendPacket(self.wfile, e, source = 'SERVER')
555            continue
556          if value[0] == 'stop': break
557          try:
558            response = getattr(self.server.rdict, value[0])(*value[1:])
559          except Exception, e:
560            self.server.rdict.writeLogLine('SERVER: Error executing operation '+str(e)+' '+str(e.__class__))
561            self.server.rdict.sendPacket(self.wfile, e, source = 'SERVER')
562          else:
563            self.server.rdict.sendPacket(self.wfile, response, source = 'SERVER')
564        return
565
566    # check if server is running
567    if os.path.exists(self.addrFilename):
568      rdict     = RDict(parentDirectory = '.')
569      hasParent = rdict.hasParent()
570      del rdict
571      if hasParent:
572        self.writeLogLine('SERVER: Another server is already running')
573        raise RuntimeError('Server already running')
574
575    # Daemonize server
576    self.writeLogLine('SERVER: Daemonizing server')
577    if os.fork(): # Launch child
578      os._exit(0) # Kill off parent, so we are not a process group leader and get a new PID
579    os.setsid()   # Set session ID, so that we have no controlling terminal
580    # We choose to leave cwd at RDict.py: os.chdir('/') # Make sure root directory is not on a mounted drive
581    os.umask(077) # Fix creation mask
582    for i in range(3): # Crappy stopgap for closing descriptors
583      try:
584        os.close(i)
585      except OSError, e:
586        if e.errno != errno.EBADF:
587          raise RuntimeError('Could not close default descriptor '+str(i))
588
589    # wish there was a better way to get a usable socket
590    self.writeLogLine('SERVER: Establishing socket server')
591    basePort = 8000
592    flag     = 'nosocket'
593    p        = 1
594    while p < 1000 and flag == 'nosocket':
595      try:
596        server = SocketServer.ThreadingTCPServer((socket.gethostname(), basePort+p), ProcessHandler)
597        flag   = 'socket'
598      except Exception, e:
599        p = p + 1
600    if flag == 'nosocket':
601      p = 1
602      while p < 1000 and flag == 'nosocket':
603        try:
604          server = SocketServer.ThreadingTCPServer(('localhost', basePort+p), ProcessHandler)
605          flag   = 'socket'
606        except Exception, e:
607          p = p + 1
608    if flag == 'nosocket':
609      self.writeLogLine('SERVER: Could not established socket server on port '+str(basePort+p))
610      raise RuntimeError,'Cannot get available socket'
611    self.writeLogLine('SERVER: Established socket server on port '+str(basePort+p))
612
613    self.isServer = 1
614    self.writeServerAddr(server)
615    self.serverShutdown(os.getpid())
616
617    server.rdict = self
618    self.writeLogLine('SERVER: Started server')
619    server.serve_forever()
620    return
621
622  def load(self):
623    '''Load the saved dictionary'''
624    if not self.parentDirectory is None and os.path.samefile(os.getcwd(), self.parentDirectory):
625      return
626    self.saveFilename = os.path.abspath(self.saveFilename)
627    if os.path.exists(self.saveFilename):
628      try:
629        dbFile = file(self.saveFilename)
630        data   = cPickle.load(dbFile)
631        self.updateTypes(data)
632        dbFile.close()
633        self.writeLogLine('Loaded dictionary from '+self.saveFilename)
634      except Exception, e:
635        self.writeLogLine('Problem loading dictionary from '+self.saveFilename+'\n--> '+str(e))
636    else:
637      self.writeLogLine('No dictionary to load in this file: '+self.saveFilename)
638    return
639
640  def save(self, force = 0):
641    '''Save the dictionary after 5 seconds, ignoring all subsequent calls until the save
642       - Giving force = True will cause an immediate save'''
643    if self.readonly: return
644    if force:
645      self.saveTimer = None
646      # This should be a critical section
647      dbFile = file(self.saveFilename, 'w')
648      data   = dict(filter(lambda i: not i[1].getTemporary(), self.localitems()))
649      cPickle.dump(data, dbFile)
650      dbFile.close()
651      self.writeLogLine('Saved local dictionary to '+os.path.abspath(self.saveFilename))
652    elif not self.saveTimer:
653      import threading
654      self.saveTimer = threading.Timer(5, self.save, [], {'force': 1})
655      self.saveTimer.setDaemon(1)
656      self.saveTimer.start()
657    return
658
659  def shutdown(self):
660    '''Shutdown the dictionary, writing out changes and notifying parent'''
661    if self.saveTimer:
662      self.saveTimer.cancel()
663      self.save(force = 1)
664    if self.isServer and os.path.isfile(self.addrFilename):
665      os.remove(self.addrFilename)
666    if not self.parent is None:
667      self.sendPacket(self.parent, self.stopCmd, isPickled = 1)
668      self.parent.close()
669      self.parent = None
670    self.writeLogLine('Shutting down')
671    self.logFile.close()
672    return
673
674  def serverShutdown(self, pid, delay = shutdownDelay):
675    if self.shutdownTimer is None:
676      import threading
677
678      self.shutdownTimer = threading.Timer(delay, self.serverShutdown, [pid], {'delay': 0})
679      self.shutdownTimer.setDaemon(1)
680      self.shutdownTimer.start()
681      self.writeLogLine('SERVER: Set shutdown timer for process '+str(pid)+' at '+str(delay)+' seconds')
682    else:
683      try:
684        import signal
685        import time
686
687        idleTime = time.time() - self.lastAccess
688        self.writeLogLine('SERVER: Last access '+str(self.lastAccess))
689        self.writeLogLine('SERVER: Idle time '+str(idleTime))
690        if idleTime < RDict.shutdownDelay:
691          self.writeLogLine('SERVER: Extending shutdown timer for '+str(pid)+' by '+str(RDict.shutdownDelay - idleTime)+' seconds')
692          self.shutdownTimer = None
693          self.serverShutdown(pid, RDict.shutdownDelay - idleTime)
694        else:
695          self.writeLogLine('SERVER: Killing server '+str(pid))
696          os.kill(pid, signal.SIGTERM)
697      except Exception, e:
698        self.writeLogLine('SERVER: Exception killing server: '+str(e))
699    return
700
701if __name__ ==  '__main__':
702  import sys
703  try:
704    if len(sys.argv) < 2:
705      print 'RDict.py [server | client | clear | insert | remove] [parent]'
706    else:
707      action = sys.argv[1]
708      parent = None
709      if len(sys.argv) > 2:
710        if not sys.argv[2] == 'None': parent = sys.argv[2]
711      if action == 'server':
712        RDict(parentDirectory = parent).serve()
713      elif action == 'client':
714        print 'Entries in server dictionary'
715        rdict = RDict(parentDirectory = parent)
716        for key in rdict.types():
717          if not key.startswith('cacheKey') and not key.startswith('stamp-'):
718            print str(key)+' '+str(rdict.getType(key))
719      elif action == 'cacheClient':
720        print 'Cache entries in server dictionary'
721        rdict = RDict(parentDirectory = parent)
722        for key in rdict.types():
723          if key.startswith('cacheKey'):
724            print str(key)+' '+str(rdict.getType(key))
725      elif action == 'stampClient':
726        print 'Stamp entries in server dictionary'
727        rdict = RDict(parentDirectory = parent)
728        for key in rdict.types():
729          if key.startswith('stamp-'):
730            print str(key)+' '+str(rdict.getType(key))
731      elif action == 'clear':
732        print 'Clearing all dictionaries'
733        RDict(parentDirectory = parent).clear()
734      elif action == 'insert':
735        rdict = RDict(parentDirectory = parent)
736        rdict[sys.argv[3]] = sys.argv[4]
737      elif action == 'remove':
738        rdict = RDict(parentDirectory = parent)
739        del rdict[sys.argv[3]]
740      else:
741        sys.exit('Unknown action: '+action)
742  except Exception, e:
743    import traceback
744    print traceback.print_tb(sys.exc_info()[2])
745    sys.exit(str(e))
746  sys.exit(0)
747