xref: /petsc/config/BuildSystem/RDict.py (revision 31f4da61a633f1068caa3cf5a1c2175372d30cb2)
1179860b2SJed Brown#!/usr/bin/env python
2179860b2SJed Brown'''A remote dictionary server
3179860b2SJed Brown
4179860b2SJed Brown    RDict is a typed, hierarchical, persistent dictionary intended to manage
5179860b2SJed Brown    all arguments or options for a program. The interface remains exactly the
6179860b2SJed Brown    same as dict, but the storage is more complicated.
7179860b2SJed Brown
8179860b2SJed Brown    Argument typing is handled by wrapping all values stored in the dictionary
9179860b2SJed Brown    with nargs.Arg or a subclass. A user can call setType() to set the type of
10179860b2SJed Brown    an argument without any value being present. Whenever __getitem__() or
11179860b2SJed Brown    __setitem__() is called, values are extracted or replaced in the wrapper.
12179860b2SJed Brown    These wrappers can be accessed directly using getType(), setType(), and
13179860b2SJed Brown    types().
14179860b2SJed Brown
15179860b2SJed Brown    Hierarchy is allowed using a single "parent" dictionary. All operations
16179860b2SJed Brown    cascade to the parent. For instance, the length of the dictionary is the
17179860b2SJed Brown    number of local keys plus the number of keys in the parent, and its
18179860b2SJed Brown    parent, etc. Also, a dictionary need not have a parent. If a key does not
19179860b2SJed Brown    appear in the local dicitonary, the call if passed to the parent. However,
20179860b2SJed Brown    in this case we see that local keys can shadow those in a parent.
21179860b2SJed Brown    Communication with the parent is handled using sockets, with the parent
22179860b2SJed Brown    being a server and the interactive dictionary a client.
23179860b2SJed Brown
24179860b2SJed Brown    The default persistence mechanism is a pickle file, RDict.db, written
25179860b2SJed Brown    whenever an argument is changed locally. A timer thread is created after
26179860b2SJed Brown    an initial change, so that many rapid changes do not cause many writes.
27179860b2SJed Brown    Each dictionary only saves its local entries, so all parents also
28179860b2SJed Brown    separately save data in different RDict.db files. Each time a dictionary
29179860b2SJed Brown    is created, the current directory is searched for an RDict.db file, and
30179860b2SJed Brown    if found the contents are loaded into the dictionary.
31179860b2SJed Brown
32179860b2SJed Brown    This script also provides some default actions:
33179860b2SJed Brown
34179860b2SJed Brown      - server [parent]
35179860b2SJed Brown        Starts a server in the current directory with an optional parent. This
36179860b2SJed Brown        server will accept socket connections from other dictionaries and act
37179860b2SJed Brown        as a parent.
38179860b2SJed Brown
39179860b2SJed Brown      - client [parent]
40179860b2SJed Brown        Creates a dictionary in the current directory with an optional parent
41179860b2SJed Brown        and lists the contents. Notice that the contents may come from either
42179860b2SJed Brown        an RDict.db file in the current directory, or from the parent.
43179860b2SJed Brown
44179860b2SJed Brown      - clear [parent]
45179860b2SJed Brown        Creates a dictionary in the current directory with an optional parent
46179860b2SJed Brown        and clears the contents. Notice that this will also clear the parent.
47179860b2SJed Brown
48179860b2SJed Brown      - insert <parent> <key> <value>
49179860b2SJed Brown        Creates a dictionary in the current directory with a parent, and inserts
50179860b2SJed Brown        the key-value pair. If "parent" is "None", no parent is assigned.
51179860b2SJed Brown
52179860b2SJed Brown      - remove <parent> <key>
53179860b2SJed Brown        Creates a dictionary in the current directory with a parent, and removes
54179860b2SJed Brown        the given key. If "parent" is "None", no parent is assigned.
55179860b2SJed Brown'''
56179860b2SJed Browntry:
57179860b2SJed Brown  import project          # This is necessary for us to create Project objects on load
58179860b2SJed Brown  import build.buildGraph # This is necessary for us to create BuildGraph objects on load
59179860b2SJed Brownexcept ImportError:
60179860b2SJed Brown  pass
61179860b2SJed Brownimport nargs
62179860b2SJed Brown
63179860b2SJed Brownimport cPickle
64179860b2SJed Brownimport os
65179860b2SJed Brownimport sys
66179860b2SJed BrownuseThreads = nargs.Arg.findArgument('useThreads', sys.argv[1:])
67179860b2SJed Brownif useThreads is None:
6892626d4aSBarry Smith  useThreads = 0 # workaround issue with parallel configure
6992626d4aSBarry Smithelif useThreads == 'no' or useThreads == '0':
7092626d4aSBarry Smith  useThreads = 0
7192626d4aSBarry Smithelif useThreads == 'yes' or useThreads == '1':
7231de54b5SMatthew G. Knepley  useThreads = 1
73179860b2SJed Brownelse:
7492626d4aSBarry Smith  raise RuntimeError('Unknown option value for --useThreads ',useThreads)
75179860b2SJed Brown
76179860b2SJed Brownclass RDict(dict):
77179860b2SJed Brown  '''An RDict is a typed dictionary, which may be hierarchically composed. All elements derive from the
78179860b2SJed BrownArg class, which wraps the usual value.'''
79179860b2SJed Brown  # The server will self-shutdown after this many seconds
80179860b2SJed Brown  shutdownDelay = 60*60*5
81179860b2SJed Brown
82179860b2SJed Brown  def __init__(self, parentAddr = None, parentDirectory = None, load = 1, autoShutdown = 1, readonly = False):
83179860b2SJed Brown    import atexit
84179860b2SJed Brown    import time
85179860b2SJed Brown    import xdrlib
86179860b2SJed Brown
87179860b2SJed Brown    self.logFile         = None
88179860b2SJed Brown    self.setupLogFile()
89179860b2SJed Brown    self.target          = ['default']
90179860b2SJed Brown    self.parent          = None
91179860b2SJed Brown    self.saveTimer       = None
92179860b2SJed Brown    self.shutdownTimer   = None
93179860b2SJed Brown    self.lastAccess      = time.time()
94179860b2SJed Brown    self.saveFilename    = 'RDict.db'
95179860b2SJed Brown    self.addrFilename    = 'RDict.loc'
96179860b2SJed Brown    self.parentAddr      = parentAddr
97179860b2SJed Brown    self.isServer        = 0
98179860b2SJed Brown    self.readonly        = readonly
99179860b2SJed Brown    self.parentDirectory = parentDirectory
100179860b2SJed Brown    self.packer          = xdrlib.Packer()
101179860b2SJed Brown    self.unpacker        = xdrlib.Unpacker('')
102179860b2SJed Brown    self.stopCmd         = cPickle.dumps(('stop',))
103179860b2SJed Brown    self.writeLogLine('Greetings')
104179860b2SJed Brown    self.connectParent(self.parentAddr, self.parentDirectory)
105179860b2SJed Brown    if load: self.load()
106179860b2SJed Brown    if autoShutdown and useThreads:
107179860b2SJed Brown      atexit.register(self.shutdown)
108179860b2SJed Brown    self.writeLogLine('SERVER: Last access '+str(self.lastAccess))
109179860b2SJed Brown    return
110179860b2SJed Brown
111179860b2SJed Brown  def __getstate__(self):
112179860b2SJed Brown    '''Remove any parent socket object, the XDR translators, and the log file from the dictionary before pickling'''
113179860b2SJed Brown    self.writeLogLine('Pickling RDict')
114179860b2SJed Brown    d = self.__dict__.copy()
115179860b2SJed Brown    if 'parent'    in d: del d['parent']
116179860b2SJed Brown    if 'saveTimer' in d: del d['saveTimer']
117179860b2SJed Brown    if '_setCommandLine' in d: del d['_setCommandLine']
118179860b2SJed Brown    del d['packer']
119179860b2SJed Brown    del d['unpacker']
120179860b2SJed Brown    del d['logFile']
121179860b2SJed Brown    return d
122179860b2SJed Brown
123179860b2SJed Brown  def __setstate__(self, d):
124179860b2SJed Brown    '''Reconnect the parent socket object, recreate the XDR translators and reopen the log file after unpickling'''
125179860b2SJed Brown    self.logFile  = file('RDict.log', 'a')
126179860b2SJed Brown    self.writeLogLine('Unpickling RDict')
127179860b2SJed Brown    self.__dict__.update(d)
128179860b2SJed Brown    import xdrlib
129179860b2SJed Brown    self.packer   = xdrlib.Packer()
130179860b2SJed Brown    self.unpacker = xdrlib.Unpacker('')
131179860b2SJed Brown    self.connectParent(self.parentAddr, self.parentDirectory)
132179860b2SJed Brown    return
133179860b2SJed Brown
134179860b2SJed Brown  def setupLogFile(self, filename = 'RDict.log'):
135179860b2SJed Brown    if not self.logFile is None:
136179860b2SJed Brown      self.logFile.close()
137179860b2SJed Brown    if os.path.isfile(filename) and os.stat(filename).st_size > 10*1024*1024:
138179860b2SJed Brown      if os.path.isfile(filename+'.bkp'):
139179860b2SJed Brown        os.remove(filename+'.bkp')
140179860b2SJed Brown      os.rename(filename, filename+'.bkp')
141179860b2SJed Brown      self.logFile = file(filename, 'w')
142179860b2SJed Brown    else:
143179860b2SJed Brown      self.logFile = file(filename, 'a')
144179860b2SJed Brown    return
145179860b2SJed Brown
146179860b2SJed Brown  def writeLogLine(self, message):
147179860b2SJed Brown    '''Writes the message to the log along with the current time'''
148179860b2SJed Brown    import time
149179860b2SJed Brown    self.logFile.write('('+str(os.getpid())+')('+str(id(self))+')'+message+' ['+time.asctime(time.localtime())+']\n')
150179860b2SJed Brown    self.logFile.flush()
151179860b2SJed Brown    return
152179860b2SJed Brown
153179860b2SJed Brown  def __len__(self):
154179860b2SJed Brown    '''Returns the length of both the local and parent dictionaries'''
155179860b2SJed Brown    length = dict.__len__(self)
156179860b2SJed Brown    if not self.parent is None:
157179860b2SJed Brown      length = length + self.send()
158179860b2SJed Brown    return length
159179860b2SJed Brown
160179860b2SJed Brown  def getType(self, key):
161179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Returns the Arg object or None if not found.'''
162179860b2SJed Brown    if dict.has_key(self, key):
163179860b2SJed Brown      self.writeLogLine('getType: Getting local type for '+key+' '+str(dict.__getitem__(self, key)))
164179860b2SJed Brown      return dict.__getitem__(self, key)
165179860b2SJed Brown    elif not self.parent is None:
166179860b2SJed Brown      return self.send(key)
167179860b2SJed Brown    return None
168179860b2SJed Brown
169179860b2SJed Brown  def __getitem__(self, key):
170179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Returns the value of the Arg.
171179860b2SJed Brown       - If the value has not been set, the user will be prompted for input'''
172179860b2SJed Brown    if dict.has_key(self, key):
173179860b2SJed Brown      self.writeLogLine('__getitem__: '+key+' has local type')
174179860b2SJed Brown      pass
175179860b2SJed Brown    elif not self.parent is None:
176179860b2SJed Brown      self.writeLogLine('__getitem__: Checking parent value')
177179860b2SJed Brown      if self.send(key, operation = 'has_key'):
178179860b2SJed Brown        self.writeLogLine('__getitem__: Parent has value')
179179860b2SJed Brown        return self.send(key)
180179860b2SJed Brown      else:
181179860b2SJed Brown        self.writeLogLine('__getitem__: Checking parent type')
182179860b2SJed Brown        arg = self.send(key, operation = 'getType')
183179860b2SJed Brown        if not arg:
184179860b2SJed Brown          self.writeLogLine('__getitem__: Parent has no type')
185179860b2SJed Brown          arg = nargs.Arg(key)
186179860b2SJed Brown        try:
187179860b2SJed Brown          value = arg.getValue()
188179860b2SJed Brown        except AttributeError, e:
189179860b2SJed Brown          self.writeLogLine('__getitem__: Parent had invalid entry: '+str(e))
190179860b2SJed Brown          arg   = nargs.Arg(key)
191179860b2SJed Brown          value = arg.getValue()
192179860b2SJed Brown        self.writeLogLine('__getitem__: Setting parent value '+str(value))
193179860b2SJed Brown        self.send(key, value, operation = '__setitem__')
194179860b2SJed Brown        return value
195179860b2SJed Brown    else:
196179860b2SJed Brown      self.writeLogLine('__getitem__: Setting local type for '+key)
197179860b2SJed Brown      dict.__setitem__(self, key, nargs.Arg(key))
198*31f4da61SSatish Balay      #self.save()
199179860b2SJed Brown    self.writeLogLine('__getitem__: Setting local value for '+key)
200179860b2SJed Brown    return dict.__getitem__(self, key).getValue()
201179860b2SJed Brown
202179860b2SJed Brown  def setType(self, key, value, forceLocal = 0):
203179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Sets the type for this key.
204179860b2SJed Brown       - If a value for the key already exists, it is converted to the new type'''
205179860b2SJed Brown    if not isinstance(value, nargs.Arg):
206179860b2SJed Brown      raise TypeError('An argument type must be a subclass of Arg')
207179860b2SJed Brown    value.setKey(key)
208179860b2SJed Brown    if forceLocal or self.parent is None or dict.has_key(self, key):
209179860b2SJed Brown      if dict.has_key(self, key):
210179860b2SJed Brown        v = dict.__getitem__(self, key)
211179860b2SJed Brown        if v.isValueSet():
212179860b2SJed Brown          try:
213179860b2SJed Brown            value.setValue(v.getValue())
214a5737712SSatish Balay          except TypeError:
215a5737712SSatish Balay            print value.__class__.__name__[3:]
216a5737712SSatish Balay            print '-----------------------------------------------------------------------'
217a5737712SSatish Balay            print 'Warning! Incorrect argument type specified: -'+str(key)+'='+str(v.getValue())+' - expecting type '+value.__class__.__name__[3:]+'.'
218a5737712SSatish Balay            print '-----------------------------------------------------------------------'
219a5737712SSatish Balay            pass
220179860b2SJed Brown      dict.__setitem__(self, key, value)
221*31f4da61SSatish Balay      #self.save()
222179860b2SJed Brown    else:
223179860b2SJed Brown      return self.send(key, value)
224179860b2SJed Brown    return
225179860b2SJed Brown
226179860b2SJed Brown  def __setitem__(self, key, value):
227179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Sets the value of the Arg.'''
228179860b2SJed Brown    if not dict.has_key(self, key):
229179860b2SJed Brown      if not self.parent is None:
230179860b2SJed Brown        return self.send(key, value)
231179860b2SJed Brown      else:
232179860b2SJed Brown        dict.__setitem__(self, key, nargs.Arg(key))
233179860b2SJed Brown    dict.__getitem__(self, key).setValue(value)
234179860b2SJed Brown    self.writeLogLine('__setitem__: Set value for '+key+' to '+str(dict.__getitem__(self, key)))
235*31f4da61SSatish Balay    #self.save()
236179860b2SJed Brown    return
237179860b2SJed Brown
238179860b2SJed Brown  def __delitem__(self, key):
239179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Deletes the Arg completely.'''
240179860b2SJed Brown    if dict.has_key(self, key):
241179860b2SJed Brown      dict.__delitem__(self, key)
242*31f4da61SSatish Balay      #self.save()
243179860b2SJed Brown    elif not self.parent is None:
244179860b2SJed Brown      self.send(key)
245179860b2SJed Brown    return
246179860b2SJed Brown
247179860b2SJed Brown  def clear(self):
248179860b2SJed Brown    '''Clears both the local and parent dictionaries'''
249179860b2SJed Brown    if dict.__len__(self):
250179860b2SJed Brown      dict.clear(self)
251*31f4da61SSatish Balay      #self.save()
252179860b2SJed Brown    if not self.parent is None:
253179860b2SJed Brown      self.send()
254179860b2SJed Brown    return
255179860b2SJed Brown
256179860b2SJed Brown  def __contains__(self, key):
257179860b2SJed Brown    '''This method just calls self.has_key(key)'''
258179860b2SJed Brown    return self.has_key(key)
259179860b2SJed Brown
260179860b2SJed Brown  def has_key(self, key):
261179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Then checks whether the value has been set'''
262179860b2SJed Brown    if dict.has_key(self, key):
263179860b2SJed Brown      if dict.__getitem__(self, key).isValueSet():
264179860b2SJed Brown        self.writeLogLine('has_key: Have value for '+key)
265179860b2SJed Brown      else:
266179860b2SJed Brown        self.writeLogLine('has_key: Do not have value for '+key)
267179860b2SJed Brown      return dict.__getitem__(self, key).isValueSet()
268179860b2SJed Brown    elif not self.parent is None:
269179860b2SJed Brown      return self.send(key)
270179860b2SJed Brown    return 0
271179860b2SJed Brown
272179860b2SJed Brown  def get(self, key, default=None):
273179860b2SJed Brown    if self.has_key(key):
274179860b2SJed Brown      return self.__getitem__(key)
275179860b2SJed Brown    else:
276179860b2SJed Brown      return default
277179860b2SJed Brown
278179860b2SJed Brown  def hasType(self, key):
279179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Then checks whether the type has been set'''
280179860b2SJed Brown    if dict.has_key(self, key):
281179860b2SJed Brown      return 1
282179860b2SJed Brown    elif not self.parent is None:
283179860b2SJed Brown      return self.send(key)
284179860b2SJed Brown    return 0
285179860b2SJed Brown
286179860b2SJed Brown  def items(self):
287179860b2SJed Brown    '''Return a list of all accessible items, as (key, value) pairs.'''
288179860b2SJed Brown    l = dict.items(self)
289179860b2SJed Brown    if not self.parent is None:
290179860b2SJed Brown      l.extend(self.send())
291179860b2SJed Brown    return l
292179860b2SJed Brown
293179860b2SJed Brown  def localitems(self):
294179860b2SJed Brown    '''Return a list of all the items stored locally, as (key, value) pairs.'''
295179860b2SJed Brown    return dict.items(self)
296179860b2SJed Brown
297179860b2SJed Brown  def keys(self):
298179860b2SJed Brown    '''Returns the list of keys in both the local and parent dictionaries'''
299179860b2SJed Brown    keyList = filter(lambda key: dict.__getitem__(self, key).isValueSet(), dict.keys(self))
300179860b2SJed Brown    if not self.parent is None:
301179860b2SJed Brown      keyList.extend(self.send())
302179860b2SJed Brown    return keyList
303179860b2SJed Brown
304179860b2SJed Brown  def types(self):
305179860b2SJed Brown    '''Returns the list of keys for which types are defined in both the local and parent dictionaries'''
306179860b2SJed Brown    keyList = dict.keys(self)
307179860b2SJed Brown    if not self.parent is None:
308179860b2SJed Brown      keyList.extend(self.send())
309179860b2SJed Brown    return keyList
310179860b2SJed Brown
311179860b2SJed Brown  def update(self, d):
312179860b2SJed Brown    '''Update the dictionary with the contents of d'''
313179860b2SJed Brown    for k in d:
314179860b2SJed Brown      self[k] = d[k]
315179860b2SJed Brown    return
316179860b2SJed Brown
317179860b2SJed Brown  def updateTypes(self, d):
318179860b2SJed Brown    '''Update types locally, which is equivalent to the dict.update() method'''
319179860b2SJed Brown    return dict.update(self, d)
320179860b2SJed Brown
321179860b2SJed Brown  def insertArg(self, key, value, arg):
322179860b2SJed Brown    '''Insert a (key, value) pair into the dictionary. If key is None, arg is put into the target list.'''
323179860b2SJed Brown    if not key is None:
324179860b2SJed Brown      self[key] = value
325179860b2SJed Brown    else:
326179860b2SJed Brown      if not self.target == ['default']:
327179860b2SJed Brown        self.target.append(arg)
328179860b2SJed Brown      else:
329179860b2SJed Brown        self.target = [arg]
330179860b2SJed Brown    return
331179860b2SJed Brown
332179860b2SJed Brown  def insertArgs(self, args):
333179860b2SJed Brown    '''Insert some text arguments into the dictionary (list and dictionaries are recognized)'''
334179860b2SJed Brown    import UserDict
335179860b2SJed Brown
336179860b2SJed Brown    if isinstance(args, list):
337179860b2SJed Brown      for arg in args:
338179860b2SJed Brown        (key, value) = nargs.Arg.parseArgument(arg)
339179860b2SJed Brown        self.insertArg(key, value, arg)
340179860b2SJed Brown    # Necessary since os.environ is a UserDict
341179860b2SJed Brown    elif isinstance(args, dict) or isinstance(args, UserDict.UserDict):
342179860b2SJed Brown      for key in args.keys():
343179860b2SJed Brown        if isinstance(args[key], str):
344179860b2SJed Brown          value = nargs.Arg.parseValue(args[key])
345179860b2SJed Brown        else:
346179860b2SJed Brown          value = args[key]
347179860b2SJed Brown        self.insertArg(key, value, None)
348179860b2SJed Brown    elif isinstance(args, str):
349179860b2SJed Brown        (key, value) = nargs.Arg.parseArgument(args)
350179860b2SJed Brown        self.insertArg(key, value, args)
351179860b2SJed Brown    return
352179860b2SJed Brown
353179860b2SJed Brown  def hasParent(self):
354179860b2SJed Brown    '''Return True if this RDict has a parent dictionary'''
355179860b2SJed Brown    return not self.parent is None
356179860b2SJed Brown
357179860b2SJed Brown  def getServerAddr(self, dir):
358179860b2SJed Brown    '''Read the server socket address (in pickled form) from a file, usually RDict.loc
359179860b2SJed Brown       - If we fail to connect to the server specified in the file, we spawn it using startServer()'''
360179860b2SJed Brown    filename = os.path.join(dir, self.addrFilename)
361179860b2SJed Brown    if not os.path.exists(filename):
362179860b2SJed Brown      self.startServer(filename)
363179860b2SJed Brown    if not os.path.exists(filename):
364179860b2SJed Brown      raise RuntimeError('Server address file does not exist: '+filename)
365179860b2SJed Brown    try:
366179860b2SJed Brown      f    = open(filename, 'r')
367179860b2SJed Brown      addr = cPickle.load(f)
368179860b2SJed Brown      f.close()
369179860b2SJed Brown      return addr
370179860b2SJed Brown    except Exception, e:
371179860b2SJed Brown      self.writeLogLine('CLIENT: Exception during server address determination: '+str(e.__class__)+': '+str(e))
372179860b2SJed Brown    raise RuntimeError('Could not get server address in '+filename)
373179860b2SJed Brown
374179860b2SJed Brown  def writeServerAddr(self, server):
375179860b2SJed Brown    '''Write the server socket address (in pickled form) to a file, usually RDict.loc.'''
376179860b2SJed Brown    f = file(self.addrFilename, 'w')
377179860b2SJed Brown    cPickle.dump(server.server_address, f)
378179860b2SJed Brown    f.close()
379179860b2SJed Brown    self.writeLogLine('SERVER: Wrote lock file '+os.path.abspath(self.addrFilename))
380179860b2SJed Brown    return
381179860b2SJed Brown
382179860b2SJed Brown  def startServer(self, addrFilename):
383179860b2SJed Brown    '''Spawn a new RDict server in the parent directory'''
384179860b2SJed Brown    import RDict # Need this to locate server script
385179860b2SJed Brown    import sys
386179860b2SJed Brown    import time
387179860b2SJed Brown    import distutils.sysconfig
388179860b2SJed Brown
389179860b2SJed Brown    self.writeLogLine('CLIENT: Spawning a new server with lock file '+os.path.abspath(addrFilename))
390179860b2SJed Brown    if os.path.exists(addrFilename):
391179860b2SJed Brown      os.remove(addrFilename)
392179860b2SJed Brown    oldDir      = os.getcwd()
393179860b2SJed Brown    source      = os.path.join(os.path.dirname(os.path.abspath(sys.modules['RDict'].__file__)), 'RDict.py')
394179860b2SJed Brown    interpreter = os.path.join(distutils.sysconfig.get_config_var('BINDIR'), distutils.sysconfig.get_config_var('PYTHON'))
395179860b2SJed Brown    if not os.path.isfile(interpreter):
396179860b2SJed Brown      interpreter = 'python'
397179860b2SJed Brown    os.chdir(os.path.dirname(addrFilename))
398179860b2SJed Brown    self.writeLogLine('CLIENT: Executing '+interpreter+' '+source+' server"')
399179860b2SJed Brown    try:
400179860b2SJed Brown      os.spawnvp(os.P_NOWAIT, interpreter, [interpreter, source, 'server'])
401179860b2SJed Brown    except:
402179860b2SJed Brown      self.writeLogLine('CLIENT: os.spawnvp failed.\n \
403179860b2SJed Brown      This is a typical problem on CYGWIN systems.  If you are using CYGWIN,\n \
404179860b2SJed Brown      you can fix this problem by running /bin/rebaseall.  If you do not have\n \
405179860b2SJed Brown      this program, you can install it with the CYGWIN installer in the package\n \
406179860b2SJed Brown      Rebase, under the category System.  You must run /bin/rebaseall after\n \
407179860b2SJed Brown      turning off all cygwin services -- in particular sshd, if any such services\n \
408179860b2SJed Brown      are running.  For more information about rebase, go to http://www.cygwin.com')
409179860b2SJed Brown      print '\n \
410179860b2SJed Brown      This is a typical problem on CYGWIN systems.  If you are using CYGWIN,\n \
411179860b2SJed Brown      you can fix this problem by running /bin/rebaseall.  If you do not have\n \
412179860b2SJed Brown      this program, you can install it with the CYGWIN installer in the package\n \
413179860b2SJed Brown      Rebase, under the category System.  You must run /bin/rebaseall after\n \
414179860b2SJed Brown      turning off all cygwin services -- in particular sshd, if any such services\n \
415179860b2SJed Brown      are running.  For more information about rebase, go to http://www.cygwin.com\n'
416179860b2SJed Brown      raise
417179860b2SJed Brown    os.chdir(oldDir)
418179860b2SJed Brown    timeout = 1
419179860b2SJed Brown    for i in range(10):
420179860b2SJed Brown      time.sleep(timeout)
421179860b2SJed Brown      timeout *= 2
422179860b2SJed Brown      if timeout > 100: timeout = 100
423179860b2SJed Brown      if os.path.exists(addrFilename): return
424179860b2SJed Brown    self.writeLogLine('CLIENT: Could not start server')
425179860b2SJed Brown    return
426179860b2SJed Brown
427179860b2SJed Brown  def connectParent(self, addr, dir):
428179860b2SJed Brown    '''Try to connect to a parent RDict server
429179860b2SJed Brown       - If addr and dir are both None, this operation fails
430179860b2SJed Brown       - If addr is None, check for an address file in dir'''
431179860b2SJed Brown    if addr is None:
432179860b2SJed Brown      if dir is None: return 0
433179860b2SJed Brown      addr = self.getServerAddr(dir)
434179860b2SJed Brown
435179860b2SJed Brown    import socket
436179860b2SJed Brown    import errno
437179860b2SJed Brown    connected = 0
438179860b2SJed Brown    s         = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
439179860b2SJed Brown    timeout   = 1
440179860b2SJed Brown    for i in range(10):
441179860b2SJed Brown      try:
442179860b2SJed Brown        self.writeLogLine('CLIENT: Trying to connect to '+str(addr))
443179860b2SJed Brown        s.connect(addr)
444179860b2SJed Brown        connected = 1
445179860b2SJed Brown        break
446179860b2SJed Brown      except socket.error, e:
447179860b2SJed Brown        self.writeLogLine('CLIENT: Failed to connect: '+str(e))
448179860b2SJed Brown        if e[0] == errno.ECONNREFUSED:
449179860b2SJed Brown          try:
450179860b2SJed Brown            import time
451179860b2SJed Brown            time.sleep(timeout)
452179860b2SJed Brown            timeout *= 2
453179860b2SJed Brown            if timeout > 100: timeout = 100
454179860b2SJed Brown          except KeyboardInterrupt:
455179860b2SJed Brown            break
456179860b2SJed Brown          # Try to spawn parent
457179860b2SJed Brown          if dir:
458179860b2SJed Brown            filename = os.path.join(dir, self.addrFilename)
459179860b2SJed Brown            if os.path.isfile(filename):
460179860b2SJed Brown              os.remove(filename)
461179860b2SJed Brown            self.startServer(filename)
462179860b2SJed Brown      except Exception, e:
463179860b2SJed Brown        self.writeLogLine('CLIENT: Failed to connect: '+str(e.__class__)+': '+str(e))
464179860b2SJed Brown    if not connected:
465179860b2SJed Brown      self.writeLogLine('CLIENT: Failed to connect to parent')
466179860b2SJed Brown      return 0
467179860b2SJed Brown    self.parent = s
468179860b2SJed Brown    self.writeLogLine('CLIENT: Connected to '+str(self.parent))
469179860b2SJed Brown    return 1
470179860b2SJed Brown
471179860b2SJed Brown  def sendPacket(self, s, packet, source = 'Unknown', isPickled = 0):
472179860b2SJed Brown    '''Pickle the input packet. Send first the size of the pickled string in 32-bit integer, and then the string itself'''
473179860b2SJed Brown    self.writeLogLine(source+': Sending packet '+str(packet))
474179860b2SJed Brown    if isPickled:
475179860b2SJed Brown      p = packet
476179860b2SJed Brown    else:
477179860b2SJed Brown      p = cPickle.dumps(packet)
478179860b2SJed Brown    self.packer.reset()
479179860b2SJed Brown    self.packer.pack_uint(len(p))
480179860b2SJed Brown    if hasattr(s, 'write'):
481179860b2SJed Brown      s.write(self.packer.get_buffer())
482179860b2SJed Brown      s.write(p)
483179860b2SJed Brown    else:
484179860b2SJed Brown      s.sendall(self.packer.get_buffer())
485179860b2SJed Brown      s.sendall(p)
486179860b2SJed Brown    self.writeLogLine(source+': Sent packet')
487179860b2SJed Brown    return
488179860b2SJed Brown
489179860b2SJed Brown  def recvPacket(self, s, source = 'Unknown'):
490179860b2SJed Brown    '''Receive first the size of the pickled string in a 32-bit integer, and then the string itself. Return the unpickled object'''
491179860b2SJed Brown    self.writeLogLine(source+': Receiving packet')
492179860b2SJed Brown    if hasattr(s, 'read'):
493179860b2SJed Brown      s.read(4)
494179860b2SJed Brown      value = cPickle.load(s)
495179860b2SJed Brown    else:
496179860b2SJed Brown      # I probably need to check that it actually read these 4 bytes
497179860b2SJed Brown      self.unpacker.reset(s.recv(4))
498179860b2SJed Brown      length    = self.unpacker.unpack_uint()
499179860b2SJed Brown      objString = ''
500179860b2SJed Brown      while len(objString) < length:
501179860b2SJed Brown        objString += s.recv(length - len(objString))
502179860b2SJed Brown      value = cPickle.loads(objString)
503179860b2SJed Brown    self.writeLogLine(source+': Received packet '+str(value))
504179860b2SJed Brown    return value
505179860b2SJed Brown
506179860b2SJed Brown  def send(self, key = None, value = None, operation = None):
507179860b2SJed Brown    '''Send a request to the parent'''
508179860b2SJed Brown    import inspect
509179860b2SJed Brown
510179860b2SJed Brown    objString = ''
511179860b2SJed Brown    for i in range(3):
512179860b2SJed Brown      try:
513179860b2SJed Brown        packet = []
514179860b2SJed Brown        if operation is None:
515179860b2SJed Brown          operation = inspect.stack()[1][3]
516179860b2SJed Brown        packet.append(operation)
517179860b2SJed Brown        if not key is None:
518179860b2SJed Brown          packet.append(key)
519179860b2SJed Brown          if not value is None:
520179860b2SJed Brown            packet.append(value)
521179860b2SJed Brown        self.sendPacket(self.parent, tuple(packet), source = 'CLIENT')
522179860b2SJed Brown        response = self.recvPacket(self.parent, source = 'CLIENT')
523179860b2SJed Brown        break
524179860b2SJed Brown      except IOError, e:
525179860b2SJed Brown        self.writeLogLine('CLIENT: IOError '+str(e))
526179860b2SJed Brown        if e.errno == 32:
527179860b2SJed Brown          self.connectParent(self.parentAddr, self.parentDirectory)
528179860b2SJed Brown      except Exception, e:
529179860b2SJed Brown        self.writeLogLine('CLIENT: Exception '+str(e)+' '+str(e.__class__))
530179860b2SJed Brown    try:
531179860b2SJed Brown      if isinstance(response, Exception):
532179860b2SJed Brown        self.writeLogLine('CLIENT: Got an exception '+str(response))
533179860b2SJed Brown        raise response
534179860b2SJed Brown      else:
535179860b2SJed Brown        self.writeLogLine('CLIENT: Received value '+str(response)+' '+str(type(response)))
536179860b2SJed Brown    except UnboundLocalError:
537179860b2SJed Brown      self.writeLogLine('CLIENT: Could not unpickle response')
538179860b2SJed Brown      response  = None
539179860b2SJed Brown    return response
540179860b2SJed Brown
541179860b2SJed Brown  def serve(self):
542179860b2SJed Brown    '''Start a server'''
543179860b2SJed Brown    import socket
544179860b2SJed Brown    import SocketServer
545179860b2SJed Brown
546179860b2SJed Brown    if not useThreads:
547179860b2SJed Brown      raise RuntimeError('Cannot run a server if threads are disabled')
548179860b2SJed Brown
549179860b2SJed Brown    class ProcessHandler(SocketServer.StreamRequestHandler):
550179860b2SJed Brown      def handle(self):
551179860b2SJed Brown        import time
552179860b2SJed Brown
553179860b2SJed Brown        self.server.rdict.lastAccess = time.time()
554179860b2SJed Brown        self.server.rdict.writeLogLine('SERVER: Started new handler')
555179860b2SJed Brown        while 1:
556179860b2SJed Brown          try:
557179860b2SJed Brown            value = self.server.rdict.recvPacket(self.rfile, source = 'SERVER')
558179860b2SJed Brown          except EOFError, e:
559179860b2SJed Brown            self.server.rdict.writeLogLine('SERVER: EOFError receiving packet '+str(e)+' '+str(e.__class__))
560179860b2SJed Brown            return
561179860b2SJed Brown          except Exception, e:
562179860b2SJed Brown            self.server.rdict.writeLogLine('SERVER: Error receiving packet '+str(e)+' '+str(e.__class__))
563179860b2SJed Brown            self.server.rdict.sendPacket(self.wfile, e, source = 'SERVER')
564179860b2SJed Brown            continue
565179860b2SJed Brown          if value[0] == 'stop': break
566179860b2SJed Brown          try:
567179860b2SJed Brown            response = getattr(self.server.rdict, value[0])(*value[1:])
568179860b2SJed Brown          except Exception, e:
569179860b2SJed Brown            self.server.rdict.writeLogLine('SERVER: Error executing operation '+str(e)+' '+str(e.__class__))
570179860b2SJed Brown            self.server.rdict.sendPacket(self.wfile, e, source = 'SERVER')
571179860b2SJed Brown          else:
572179860b2SJed Brown            self.server.rdict.sendPacket(self.wfile, response, source = 'SERVER')
573179860b2SJed Brown        return
574179860b2SJed Brown
575179860b2SJed Brown    # check if server is running
576179860b2SJed Brown    if os.path.exists(self.addrFilename):
577179860b2SJed Brown      rdict     = RDict(parentDirectory = '.')
578179860b2SJed Brown      hasParent = rdict.hasParent()
579179860b2SJed Brown      del rdict
580179860b2SJed Brown      if hasParent:
581179860b2SJed Brown        self.writeLogLine('SERVER: Another server is already running')
582179860b2SJed Brown        raise RuntimeError('Server already running')
583179860b2SJed Brown
584179860b2SJed Brown    # Daemonize server
585179860b2SJed Brown    self.writeLogLine('SERVER: Daemonizing server')
586179860b2SJed Brown    if os.fork(): # Launch child
587179860b2SJed Brown      os._exit(0) # Kill off parent, so we are not a process group leader and get a new PID
588179860b2SJed Brown    os.setsid()   # Set session ID, so that we have no controlling terminal
589179860b2SJed Brown    # We choose to leave cwd at RDict.py: os.chdir('/') # Make sure root directory is not on a mounted drive
590179860b2SJed Brown    os.umask(077) # Fix creation mask
591179860b2SJed Brown    for i in range(3): # Crappy stopgap for closing descriptors
592179860b2SJed Brown      try:
593179860b2SJed Brown        os.close(i)
594179860b2SJed Brown      except OSError, e:
595179860b2SJed Brown        if e.errno != errno.EBADF:
596179860b2SJed Brown          raise RuntimeError('Could not close default descriptor '+str(i))
597179860b2SJed Brown
598179860b2SJed Brown    # wish there was a better way to get a usable socket
599179860b2SJed Brown    self.writeLogLine('SERVER: Establishing socket server')
600179860b2SJed Brown    basePort = 8000
601179860b2SJed Brown    flag     = 'nosocket'
602179860b2SJed Brown    p        = 1
603179860b2SJed Brown    while p < 1000 and flag == 'nosocket':
604179860b2SJed Brown      try:
605179860b2SJed Brown        server = SocketServer.ThreadingTCPServer((socket.gethostname(), basePort+p), ProcessHandler)
606179860b2SJed Brown        flag   = 'socket'
607179860b2SJed Brown      except Exception, e:
608179860b2SJed Brown        p = p + 1
609179860b2SJed Brown    if flag == 'nosocket':
610179860b2SJed Brown      p = 1
611179860b2SJed Brown      while p < 1000 and flag == 'nosocket':
612179860b2SJed Brown        try:
613179860b2SJed Brown          server = SocketServer.ThreadingTCPServer(('localhost', basePort+p), ProcessHandler)
614179860b2SJed Brown          flag   = 'socket'
615179860b2SJed Brown        except Exception, e:
616179860b2SJed Brown          p = p + 1
617179860b2SJed Brown    if flag == 'nosocket':
618179860b2SJed Brown      self.writeLogLine('SERVER: Could not established socket server on port '+str(basePort+p))
619179860b2SJed Brown      raise RuntimeError,'Cannot get available socket'
620179860b2SJed Brown    self.writeLogLine('SERVER: Established socket server on port '+str(basePort+p))
621179860b2SJed Brown
622179860b2SJed Brown    self.isServer = 1
623179860b2SJed Brown    self.writeServerAddr(server)
624179860b2SJed Brown    self.serverShutdown(os.getpid())
625179860b2SJed Brown
626179860b2SJed Brown    server.rdict = self
627179860b2SJed Brown    self.writeLogLine('SERVER: Started server')
628179860b2SJed Brown    server.serve_forever()
629179860b2SJed Brown    return
630179860b2SJed Brown
631179860b2SJed Brown  def load(self):
632179860b2SJed Brown    '''Load the saved dictionary'''
633179860b2SJed Brown    if not self.parentDirectory is None and os.path.samefile(os.getcwd(), self.parentDirectory):
634179860b2SJed Brown      return
635179860b2SJed Brown    self.saveFilename = os.path.abspath(self.saveFilename)
636179860b2SJed Brown    if os.path.exists(self.saveFilename):
637179860b2SJed Brown      try:
638179860b2SJed Brown        dbFile = file(self.saveFilename)
639179860b2SJed Brown        data   = cPickle.load(dbFile)
640179860b2SJed Brown        self.updateTypes(data)
641179860b2SJed Brown        dbFile.close()
642179860b2SJed Brown        self.writeLogLine('Loaded dictionary from '+self.saveFilename)
643179860b2SJed Brown      except Exception, e:
644179860b2SJed Brown        self.writeLogLine('Problem loading dictionary from '+self.saveFilename+'\n--> '+str(e))
645179860b2SJed Brown    else:
646179860b2SJed Brown      self.writeLogLine('No dictionary to load in this file: '+self.saveFilename)
647179860b2SJed Brown    return
648179860b2SJed Brown
649603e7a67SSatish Balay  def save(self, force = 1):
650179860b2SJed Brown    '''Save the dictionary after 5 seconds, ignoring all subsequent calls until the save
651179860b2SJed Brown       - Giving force = True will cause an immediate save'''
652179860b2SJed Brown    if self.readonly: return
65308eb64ffSMatthew G. Knepley    if force:
654179860b2SJed Brown      self.saveTimer = None
655179860b2SJed Brown      # This should be a critical section
656179860b2SJed Brown      dbFile = file(self.saveFilename, 'w')
657179860b2SJed Brown      data   = dict(filter(lambda i: not i[1].getTemporary(), self.localitems()))
658179860b2SJed Brown      cPickle.dump(data, dbFile)
659179860b2SJed Brown      dbFile.close()
660179860b2SJed Brown      self.writeLogLine('Saved local dictionary to '+os.path.abspath(self.saveFilename))
661179860b2SJed Brown    elif not self.saveTimer:
662179860b2SJed Brown      import threading
663179860b2SJed Brown      self.saveTimer = threading.Timer(5, self.save, [], {'force': 1})
664179860b2SJed Brown      self.saveTimer.setDaemon(1)
665179860b2SJed Brown      self.saveTimer.start()
666179860b2SJed Brown    return
667179860b2SJed Brown
668179860b2SJed Brown  def shutdown(self):
669179860b2SJed Brown    '''Shutdown the dictionary, writing out changes and notifying parent'''
670179860b2SJed Brown    if self.saveTimer:
671179860b2SJed Brown      self.saveTimer.cancel()
672179860b2SJed Brown      self.save(force = 1)
673179860b2SJed Brown    if self.isServer and os.path.isfile(self.addrFilename):
674179860b2SJed Brown      os.remove(self.addrFilename)
675179860b2SJed Brown    if not self.parent is None:
676179860b2SJed Brown      self.sendPacket(self.parent, self.stopCmd, isPickled = 1)
677179860b2SJed Brown      self.parent.close()
678179860b2SJed Brown      self.parent = None
679179860b2SJed Brown    self.writeLogLine('Shutting down')
680179860b2SJed Brown    self.logFile.close()
681179860b2SJed Brown    return
682179860b2SJed Brown
683179860b2SJed Brown  def serverShutdown(self, pid, delay = shutdownDelay):
684179860b2SJed Brown    if self.shutdownTimer is None:
685179860b2SJed Brown      import threading
686179860b2SJed Brown
687179860b2SJed Brown      self.shutdownTimer = threading.Timer(delay, self.serverShutdown, [pid], {'delay': 0})
688179860b2SJed Brown      self.shutdownTimer.setDaemon(1)
689179860b2SJed Brown      self.shutdownTimer.start()
690179860b2SJed Brown      self.writeLogLine('SERVER: Set shutdown timer for process '+str(pid)+' at '+str(delay)+' seconds')
691179860b2SJed Brown    else:
692179860b2SJed Brown      try:
693179860b2SJed Brown        import signal
694179860b2SJed Brown        import time
695179860b2SJed Brown
696179860b2SJed Brown        idleTime = time.time() - self.lastAccess
697179860b2SJed Brown        self.writeLogLine('SERVER: Last access '+str(self.lastAccess))
698179860b2SJed Brown        self.writeLogLine('SERVER: Idle time '+str(idleTime))
699179860b2SJed Brown        if idleTime < RDict.shutdownDelay:
700179860b2SJed Brown          self.writeLogLine('SERVER: Extending shutdown timer for '+str(pid)+' by '+str(RDict.shutdownDelay - idleTime)+' seconds')
701179860b2SJed Brown          self.shutdownTimer = None
702179860b2SJed Brown          self.serverShutdown(pid, RDict.shutdownDelay - idleTime)
703179860b2SJed Brown        else:
704179860b2SJed Brown          self.writeLogLine('SERVER: Killing server '+str(pid))
705179860b2SJed Brown          os.kill(pid, signal.SIGTERM)
706179860b2SJed Brown      except Exception, e:
707179860b2SJed Brown        self.writeLogLine('SERVER: Exception killing server: '+str(e))
708179860b2SJed Brown    return
709179860b2SJed Brown
710179860b2SJed Brownif __name__ ==  '__main__':
711179860b2SJed Brown  import sys
712179860b2SJed Brown  try:
713179860b2SJed Brown    if len(sys.argv) < 2:
714179860b2SJed Brown      print 'RDict.py [server | client | clear | insert | remove] [parent]'
715179860b2SJed Brown    else:
716179860b2SJed Brown      action = sys.argv[1]
717179860b2SJed Brown      parent = None
718179860b2SJed Brown      if len(sys.argv) > 2:
719179860b2SJed Brown        if not sys.argv[2] == 'None': parent = sys.argv[2]
720179860b2SJed Brown      if action == 'server':
721179860b2SJed Brown        RDict(parentDirectory = parent).serve()
722179860b2SJed Brown      elif action == 'client':
723179860b2SJed Brown        print 'Entries in server dictionary'
724179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
725179860b2SJed Brown        for key in rdict.types():
726179860b2SJed Brown          if not key.startswith('cacheKey') and not key.startswith('stamp-'):
727179860b2SJed Brown            print str(key)+' '+str(rdict.getType(key))
728179860b2SJed Brown      elif action == 'cacheClient':
729179860b2SJed Brown        print 'Cache entries in server dictionary'
730179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
731179860b2SJed Brown        for key in rdict.types():
732179860b2SJed Brown          if key.startswith('cacheKey'):
733179860b2SJed Brown            print str(key)+' '+str(rdict.getType(key))
734179860b2SJed Brown      elif action == 'stampClient':
735179860b2SJed Brown        print 'Stamp entries in server dictionary'
736179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
737179860b2SJed Brown        for key in rdict.types():
738179860b2SJed Brown          if key.startswith('stamp-'):
739179860b2SJed Brown            print str(key)+' '+str(rdict.getType(key))
740179860b2SJed Brown      elif action == 'clear':
741179860b2SJed Brown        print 'Clearing all dictionaries'
742179860b2SJed Brown        RDict(parentDirectory = parent).clear()
743179860b2SJed Brown      elif action == 'insert':
744179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
745179860b2SJed Brown        rdict[sys.argv[3]] = sys.argv[4]
746179860b2SJed Brown      elif action == 'remove':
747179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
748179860b2SJed Brown        del rdict[sys.argv[3]]
749179860b2SJed Brown      else:
750179860b2SJed Brown        sys.exit('Unknown action: '+action)
751179860b2SJed Brown  except Exception, e:
752179860b2SJed Brown    import traceback
753179860b2SJed Brown    print traceback.print_tb(sys.exc_info()[2])
754179860b2SJed Brown    sys.exit(str(e))
755179860b2SJed Brown  sys.exit(0)
756