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