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