xref: /petsc/config/BuildSystem/RDict.py (revision b8b3d02162ac08342dddb7705a08d0ca340cb2ee)
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'''
565b6bfdb9SJed Brownfrom __future__ import print_function
575b6bfdb9SJed Brownfrom __future__ import absolute_import
58179860b2SJed Browntry:
59179860b2SJed Brown  import project          # This is necessary for us to create Project objects on load
60179860b2SJed Brown  import build.buildGraph # This is necessary for us to create BuildGraph objects on load
61179860b2SJed Brownexcept ImportError:
62179860b2SJed Brown  pass
63179860b2SJed Brownimport nargs
64179860b2SJed Brown
65492432c8SJed Brownimport pickle
66179860b2SJed Brownimport os
67179860b2SJed Brownimport sys
68179860b2SJed BrownuseThreads = nargs.Arg.findArgument('useThreads', sys.argv[1:])
69179860b2SJed Brownif useThreads is None:
7092626d4aSBarry Smith  useThreads = 0 # workaround issue with parallel configure
7192626d4aSBarry Smithelif useThreads == 'no' or useThreads == '0':
7292626d4aSBarry Smith  useThreads = 0
7392626d4aSBarry Smithelif useThreads == 'yes' or useThreads == '1':
7431de54b5SMatthew G. Knepley  useThreads = 1
75179860b2SJed Brownelse:
7692626d4aSBarry Smith  raise RuntimeError('Unknown option value for --useThreads ',useThreads)
77179860b2SJed Brown
78179860b2SJed Brownclass RDict(dict):
79179860b2SJed Brown  '''An RDict is a typed dictionary, which may be hierarchically composed. All elements derive from the
80179860b2SJed BrownArg class, which wraps the usual value.'''
81179860b2SJed Brown  # The server will self-shutdown after this many seconds
82179860b2SJed Brown  shutdownDelay = 60*60*5
83179860b2SJed Brown
84179860b2SJed Brown  def __init__(self, parentAddr = None, parentDirectory = None, load = 1, autoShutdown = 1, readonly = False):
85179860b2SJed Brown    import atexit
86179860b2SJed Brown    import time
87179860b2SJed Brown    import xdrlib
88179860b2SJed Brown
89179860b2SJed Brown    self.logFile         = None
90179860b2SJed Brown    self.setupLogFile()
91179860b2SJed Brown    self.target          = ['default']
92179860b2SJed Brown    self.parent          = None
93179860b2SJed Brown    self.saveTimer       = None
94179860b2SJed Brown    self.shutdownTimer   = None
95179860b2SJed Brown    self.lastAccess      = time.time()
96179860b2SJed Brown    self.saveFilename    = 'RDict.db'
97179860b2SJed Brown    self.addrFilename    = 'RDict.loc'
98179860b2SJed Brown    self.parentAddr      = parentAddr
99179860b2SJed Brown    self.isServer        = 0
100179860b2SJed Brown    self.readonly        = readonly
101179860b2SJed Brown    self.parentDirectory = parentDirectory
102179860b2SJed Brown    self.packer          = xdrlib.Packer()
103179860b2SJed Brown    self.unpacker        = xdrlib.Unpacker('')
104492432c8SJed Brown    self.stopCmd         = pickle.dumps(('stop',))
105179860b2SJed Brown    self.writeLogLine('Greetings')
106179860b2SJed Brown    self.connectParent(self.parentAddr, self.parentDirectory)
107179860b2SJed Brown    if load: self.load()
108179860b2SJed Brown    if autoShutdown and useThreads:
109179860b2SJed Brown      atexit.register(self.shutdown)
110179860b2SJed Brown    self.writeLogLine('SERVER: Last access '+str(self.lastAccess))
111179860b2SJed Brown    return
112179860b2SJed Brown
113179860b2SJed Brown  def __getstate__(self):
114179860b2SJed Brown    '''Remove any parent socket object, the XDR translators, and the log file from the dictionary before pickling'''
115179860b2SJed Brown    self.writeLogLine('Pickling RDict')
116179860b2SJed Brown    d = self.__dict__.copy()
117179860b2SJed Brown    if 'parent'    in d: del d['parent']
118179860b2SJed Brown    if 'saveTimer' in d: del d['saveTimer']
119179860b2SJed Brown    if '_setCommandLine' in d: del d['_setCommandLine']
120179860b2SJed Brown    del d['packer']
121179860b2SJed Brown    del d['unpacker']
122179860b2SJed Brown    del d['logFile']
123179860b2SJed Brown    return d
124179860b2SJed Brown
125179860b2SJed Brown  def __setstate__(self, d):
126179860b2SJed Brown    '''Reconnect the parent socket object, recreate the XDR translators and reopen the log file after unpickling'''
127c6ef1b5bSJed Brown    self.logFile  = open('RDict.log', 'a')
128179860b2SJed Brown    self.writeLogLine('Unpickling RDict')
129179860b2SJed Brown    self.__dict__.update(d)
130179860b2SJed Brown    import xdrlib
131179860b2SJed Brown    self.packer   = xdrlib.Packer()
132179860b2SJed Brown    self.unpacker = xdrlib.Unpacker('')
133179860b2SJed Brown    self.connectParent(self.parentAddr, self.parentDirectory)
134179860b2SJed Brown    return
135179860b2SJed Brown
136179860b2SJed Brown  def setupLogFile(self, filename = 'RDict.log'):
137179860b2SJed Brown    if not self.logFile is None:
138179860b2SJed Brown      self.logFile.close()
139179860b2SJed Brown    if os.path.isfile(filename) and os.stat(filename).st_size > 10*1024*1024:
140179860b2SJed Brown      if os.path.isfile(filename+'.bkp'):
141179860b2SJed Brown        os.remove(filename+'.bkp')
142179860b2SJed Brown      os.rename(filename, filename+'.bkp')
143c6ef1b5bSJed Brown      self.logFile = open(filename, 'w')
144179860b2SJed Brown    else:
145c6ef1b5bSJed Brown      self.logFile = open(filename, 'a')
146179860b2SJed Brown    return
147179860b2SJed Brown
148179860b2SJed Brown  def writeLogLine(self, message):
149179860b2SJed Brown    '''Writes the message to the log along with the current time'''
150179860b2SJed Brown    import time
151179860b2SJed Brown    self.logFile.write('('+str(os.getpid())+')('+str(id(self))+')'+message+' ['+time.asctime(time.localtime())+']\n')
152179860b2SJed Brown    self.logFile.flush()
153179860b2SJed Brown    return
154179860b2SJed Brown
155179860b2SJed Brown  def __len__(self):
156179860b2SJed Brown    '''Returns the length of both the local and parent dictionaries'''
157179860b2SJed Brown    length = dict.__len__(self)
158179860b2SJed Brown    if not self.parent is None:
159179860b2SJed Brown      length = length + self.send()
160179860b2SJed Brown    return length
161179860b2SJed Brown
162179860b2SJed Brown  def getType(self, key):
163179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Returns the Arg object or None if not found.'''
1643b049ba2SJed Brown    try:
1653b049ba2SJed Brown      value = dict.__getitem__(self, key)
1663b049ba2SJed Brown      self.writeLogLine('getType: Getting local type for '+key+' '+str(value))
1673b049ba2SJed Brown      return value
1683b049ba2SJed Brown    except KeyError:
1693b049ba2SJed Brown      pass
1703b049ba2SJed Brown    if self.parent:
171179860b2SJed Brown      return self.send(key)
172179860b2SJed Brown    return None
173179860b2SJed Brown
1743b049ba2SJed Brown  def dict_has_key(self, key):
1753b049ba2SJed Brown    """Utility to check whether the key is present in the dictionary without RDict side-effects."""
1763b049ba2SJed Brown    return key in dict(self)
1773b049ba2SJed Brown
178179860b2SJed Brown  def __getitem__(self, key):
179179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Returns the value of the Arg.
180179860b2SJed Brown       - If the value has not been set, the user will be prompted for input'''
1813b049ba2SJed Brown    if self.dict_has_key(key):
182179860b2SJed Brown      self.writeLogLine('__getitem__: '+key+' has local type')
183179860b2SJed Brown      pass
184179860b2SJed Brown    elif not self.parent is None:
185179860b2SJed Brown      self.writeLogLine('__getitem__: Checking parent value')
186179860b2SJed Brown      if self.send(key, operation = 'has_key'):
187179860b2SJed Brown        self.writeLogLine('__getitem__: Parent has value')
188179860b2SJed Brown        return self.send(key)
189179860b2SJed Brown      else:
190179860b2SJed Brown        self.writeLogLine('__getitem__: Checking parent type')
191179860b2SJed Brown        arg = self.send(key, operation = 'getType')
192179860b2SJed Brown        if not arg:
193179860b2SJed Brown          self.writeLogLine('__getitem__: Parent has no type')
194179860b2SJed Brown          arg = nargs.Arg(key)
195179860b2SJed Brown        try:
196179860b2SJed Brown          value = arg.getValue()
1975b6bfdb9SJed Brown        except AttributeError as e:
198179860b2SJed Brown          self.writeLogLine('__getitem__: Parent had invalid entry: '+str(e))
199179860b2SJed Brown          arg   = nargs.Arg(key)
200179860b2SJed Brown          value = arg.getValue()
201179860b2SJed Brown        self.writeLogLine('__getitem__: Setting parent value '+str(value))
202179860b2SJed Brown        self.send(key, value, operation = '__setitem__')
203179860b2SJed Brown        return value
204179860b2SJed Brown    else:
205179860b2SJed Brown      self.writeLogLine('__getitem__: Setting local type for '+key)
206179860b2SJed Brown      dict.__setitem__(self, key, nargs.Arg(key))
20731f4da61SSatish Balay      #self.save()
208179860b2SJed Brown    self.writeLogLine('__getitem__: Setting local value for '+key)
209179860b2SJed Brown    return dict.__getitem__(self, key).getValue()
210179860b2SJed Brown
211179860b2SJed Brown  def setType(self, key, value, forceLocal = 0):
212179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Sets the type for this key.
213179860b2SJed Brown       - If a value for the key already exists, it is converted to the new type'''
214179860b2SJed Brown    if not isinstance(value, nargs.Arg):
215179860b2SJed Brown      raise TypeError('An argument type must be a subclass of Arg')
216179860b2SJed Brown    value.setKey(key)
2173b049ba2SJed Brown    if forceLocal or self.parent is None or self.dict_has_key(key):
2183b049ba2SJed Brown      if self.dict_has_key(key):
219179860b2SJed Brown        v = dict.__getitem__(self, key)
220179860b2SJed Brown        if v.isValueSet():
221179860b2SJed Brown          try:
222179860b2SJed Brown            value.setValue(v.getValue())
223a5737712SSatish Balay          except TypeError:
2245b6bfdb9SJed Brown            print(value.__class__.__name__[3:])
2255b6bfdb9SJed Brown            print('-----------------------------------------------------------------------')
2265b6bfdb9SJed Brown            print('Warning! Incorrect argument type specified: -'+str(key)+'='+str(v.getValue())+' - expecting type '+value.__class__.__name__[3:]+'.')
2275b6bfdb9SJed Brown            print('-----------------------------------------------------------------------')
228a5737712SSatish Balay            pass
229179860b2SJed Brown      dict.__setitem__(self, key, value)
23031f4da61SSatish Balay      #self.save()
231179860b2SJed Brown    else:
232179860b2SJed Brown      return self.send(key, value)
233179860b2SJed Brown    return
234179860b2SJed Brown
235179860b2SJed Brown  def __setitem__(self, key, value):
236179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Sets the value of the Arg.'''
2373b049ba2SJed Brown    if not self.dict_has_key(key):
238179860b2SJed Brown      if not self.parent is None:
239179860b2SJed Brown        return self.send(key, value)
240179860b2SJed Brown      else:
241179860b2SJed Brown        dict.__setitem__(self, key, nargs.Arg(key))
242179860b2SJed Brown    dict.__getitem__(self, key).setValue(value)
243179860b2SJed Brown    self.writeLogLine('__setitem__: Set value for '+key+' to '+str(dict.__getitem__(self, key)))
24431f4da61SSatish Balay    #self.save()
245179860b2SJed Brown    return
246179860b2SJed Brown
247179860b2SJed Brown  def __delitem__(self, key):
248179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Deletes the Arg completely.'''
2493b049ba2SJed Brown    if self.dict_has_key(key):
250179860b2SJed Brown      dict.__delitem__(self, key)
25131f4da61SSatish Balay      #self.save()
252179860b2SJed Brown    elif not self.parent is None:
253179860b2SJed Brown      self.send(key)
254179860b2SJed Brown    return
255179860b2SJed Brown
256179860b2SJed Brown  def clear(self):
257179860b2SJed Brown    '''Clears both the local and parent dictionaries'''
258179860b2SJed Brown    if dict.__len__(self):
259179860b2SJed Brown      dict.clear(self)
26031f4da61SSatish Balay      #self.save()
261179860b2SJed Brown    if not self.parent is None:
262179860b2SJed Brown      self.send()
263179860b2SJed Brown    return
264179860b2SJed Brown
265179860b2SJed Brown  def __contains__(self, key):
266179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Then checks whether the value has been set'''
2673b049ba2SJed Brown    if self.dict_has_key(key):
268179860b2SJed Brown      if dict.__getitem__(self, key).isValueSet():
269179860b2SJed Brown        self.writeLogLine('has_key: Have value for '+key)
270179860b2SJed Brown      else:
271179860b2SJed Brown        self.writeLogLine('has_key: Do not have value for '+key)
272179860b2SJed Brown      return dict.__getitem__(self, key).isValueSet()
273179860b2SJed Brown    elif not self.parent is None:
274179860b2SJed Brown      return self.send(key)
275179860b2SJed Brown    return 0
276179860b2SJed Brown
277179860b2SJed Brown  def get(self, key, default=None):
2785b6bfdb9SJed Brown    if key in self:
279179860b2SJed Brown      return self.__getitem__(key)
280179860b2SJed Brown    else:
281179860b2SJed Brown      return default
282179860b2SJed Brown
283179860b2SJed Brown  def hasType(self, key):
284179860b2SJed Brown    '''Checks for the key locally, and if not found consults the parent. Then checks whether the type has been set'''
2853b049ba2SJed Brown    if self.dict_has_key(key):
286179860b2SJed Brown      return 1
287179860b2SJed Brown    elif not self.parent is None:
288179860b2SJed Brown      return self.send(key)
289179860b2SJed Brown    return 0
290179860b2SJed Brown
291179860b2SJed Brown  def items(self):
292179860b2SJed Brown    '''Return a list of all accessible items, as (key, value) pairs.'''
293179860b2SJed Brown    l = dict.items(self)
294179860b2SJed Brown    if not self.parent is None:
295179860b2SJed Brown      l.extend(self.send())
296179860b2SJed Brown    return l
297179860b2SJed Brown
298179860b2SJed Brown  def localitems(self):
299179860b2SJed Brown    '''Return a list of all the items stored locally, as (key, value) pairs.'''
300179860b2SJed Brown    return dict.items(self)
301179860b2SJed Brown
302179860b2SJed Brown  def keys(self):
303179860b2SJed Brown    '''Returns the list of keys in both the local and parent dictionaries'''
304bb3dd2f6SJed Brown    keyList = [key for key in dict.keys(self) if dict.__getitem__(self, key).isValueSet()]
305179860b2SJed Brown    if not self.parent is None:
306179860b2SJed Brown      keyList.extend(self.send())
307179860b2SJed Brown    return keyList
308179860b2SJed Brown
309179860b2SJed Brown  def types(self):
310179860b2SJed Brown    '''Returns the list of keys for which types are defined in both the local and parent dictionaries'''
311179860b2SJed Brown    keyList = dict.keys(self)
312179860b2SJed Brown    if not self.parent is None:
313179860b2SJed Brown      keyList.extend(self.send())
314179860b2SJed Brown    return keyList
315179860b2SJed Brown
316179860b2SJed Brown  def update(self, d):
317179860b2SJed Brown    '''Update the dictionary with the contents of d'''
318179860b2SJed Brown    for k in d:
319179860b2SJed Brown      self[k] = d[k]
320179860b2SJed Brown    return
321179860b2SJed Brown
322179860b2SJed Brown  def updateTypes(self, d):
323179860b2SJed Brown    '''Update types locally, which is equivalent to the dict.update() method'''
324179860b2SJed Brown    return dict.update(self, d)
325179860b2SJed Brown
326179860b2SJed Brown  def insertArg(self, key, value, arg):
327179860b2SJed Brown    '''Insert a (key, value) pair into the dictionary. If key is None, arg is put into the target list.'''
328179860b2SJed Brown    if not key is None:
329179860b2SJed Brown      self[key] = value
330179860b2SJed Brown    else:
331179860b2SJed Brown      if not self.target == ['default']:
332179860b2SJed Brown        self.target.append(arg)
333179860b2SJed Brown      else:
334179860b2SJed Brown        self.target = [arg]
335179860b2SJed Brown    return
336179860b2SJed Brown
337179860b2SJed Brown  def insertArgs(self, args):
338179860b2SJed Brown    '''Insert some text arguments into the dictionary (list and dictionaries are recognized)'''
339179860b2SJed Brown
340179860b2SJed Brown    if isinstance(args, list):
341179860b2SJed Brown      for arg in args:
342179860b2SJed Brown        (key, value) = nargs.Arg.parseArgument(arg)
343179860b2SJed Brown        self.insertArg(key, value, arg)
3443b049ba2SJed Brown    elif hasattr(args, keys):
345179860b2SJed Brown      for key in args.keys():
346179860b2SJed Brown        if isinstance(args[key], str):
347179860b2SJed Brown          value = nargs.Arg.parseValue(args[key])
348179860b2SJed Brown        else:
349179860b2SJed Brown          value = args[key]
350179860b2SJed Brown        self.insertArg(key, value, None)
351179860b2SJed Brown    elif isinstance(args, str):
352179860b2SJed Brown        (key, value) = nargs.Arg.parseArgument(args)
353179860b2SJed Brown        self.insertArg(key, value, args)
354179860b2SJed Brown    return
355179860b2SJed Brown
356179860b2SJed Brown  def hasParent(self):
357179860b2SJed Brown    '''Return True if this RDict has a parent dictionary'''
358179860b2SJed Brown    return not self.parent is None
359179860b2SJed Brown
360179860b2SJed Brown  def getServerAddr(self, dir):
361179860b2SJed Brown    '''Read the server socket address (in pickled form) from a file, usually RDict.loc
362179860b2SJed Brown       - If we fail to connect to the server specified in the file, we spawn it using startServer()'''
363179860b2SJed Brown    filename = os.path.join(dir, self.addrFilename)
364179860b2SJed Brown    if not os.path.exists(filename):
365179860b2SJed Brown      self.startServer(filename)
366179860b2SJed Brown    if not os.path.exists(filename):
367179860b2SJed Brown      raise RuntimeError('Server address file does not exist: '+filename)
368179860b2SJed Brown    try:
369179860b2SJed Brown      f    = open(filename, 'r')
370492432c8SJed Brown      addr = pickle.load(f)
371179860b2SJed Brown      f.close()
372179860b2SJed Brown      return addr
3735b6bfdb9SJed Brown    except Exception as e:
374179860b2SJed Brown      self.writeLogLine('CLIENT: Exception during server address determination: '+str(e.__class__)+': '+str(e))
375179860b2SJed Brown    raise RuntimeError('Could not get server address in '+filename)
376179860b2SJed Brown
377179860b2SJed Brown  def writeServerAddr(self, server):
378179860b2SJed Brown    '''Write the server socket address (in pickled form) to a file, usually RDict.loc.'''
379c6ef1b5bSJed Brown    f = open(self.addrFilename, 'w')
380492432c8SJed Brown    pickle.dump(server.server_address, f)
381179860b2SJed Brown    f.close()
382179860b2SJed Brown    self.writeLogLine('SERVER: Wrote lock file '+os.path.abspath(self.addrFilename))
383179860b2SJed Brown    return
384179860b2SJed Brown
385179860b2SJed Brown  def startServer(self, addrFilename):
386179860b2SJed Brown    '''Spawn a new RDict server in the parent directory'''
387179860b2SJed Brown    import RDict # Need this to locate server script
388179860b2SJed Brown    import sys
389179860b2SJed Brown    import time
390179860b2SJed Brown    import distutils.sysconfig
391179860b2SJed Brown
392179860b2SJed Brown    self.writeLogLine('CLIENT: Spawning a new server with lock file '+os.path.abspath(addrFilename))
393179860b2SJed Brown    if os.path.exists(addrFilename):
394179860b2SJed Brown      os.remove(addrFilename)
395179860b2SJed Brown    oldDir      = os.getcwd()
396179860b2SJed Brown    source      = os.path.join(os.path.dirname(os.path.abspath(sys.modules['RDict'].__file__)), 'RDict.py')
397179860b2SJed Brown    interpreter = os.path.join(distutils.sysconfig.get_config_var('BINDIR'), distutils.sysconfig.get_config_var('PYTHON'))
398179860b2SJed Brown    if not os.path.isfile(interpreter):
399179860b2SJed Brown      interpreter = 'python'
400179860b2SJed Brown    os.chdir(os.path.dirname(addrFilename))
401179860b2SJed Brown    self.writeLogLine('CLIENT: Executing '+interpreter+' '+source+' server"')
402179860b2SJed Brown    try:
403179860b2SJed Brown      os.spawnvp(os.P_NOWAIT, interpreter, [interpreter, source, 'server'])
404179860b2SJed Brown    except:
405179860b2SJed Brown      self.writeLogLine('CLIENT: os.spawnvp failed.\n \
406179860b2SJed Brown      This is a typical problem on CYGWIN systems.  If you are using CYGWIN,\n \
407179860b2SJed Brown      you can fix this problem by running /bin/rebaseall.  If you do not have\n \
408179860b2SJed Brown      this program, you can install it with the CYGWIN installer in the package\n \
409179860b2SJed Brown      Rebase, under the category System.  You must run /bin/rebaseall after\n \
410179860b2SJed Brown      turning off all cygwin services -- in particular sshd, if any such services\n \
411179860b2SJed Brown      are running.  For more information about rebase, go to http://www.cygwin.com')
4125b6bfdb9SJed Brown      print('\n \
413179860b2SJed Brown      This is a typical problem on CYGWIN systems.  If you are using CYGWIN,\n \
414179860b2SJed Brown      you can fix this problem by running /bin/rebaseall.  If you do not have\n \
415179860b2SJed Brown      this program, you can install it with the CYGWIN installer in the package\n \
416179860b2SJed Brown      Rebase, under the category System.  You must run /bin/rebaseall after\n \
417179860b2SJed Brown      turning off all cygwin services -- in particular sshd, if any such services\n \
4185b6bfdb9SJed Brown      are running.  For more information about rebase, go to http://www.cygwin.com\n')
419179860b2SJed Brown      raise
420179860b2SJed Brown    os.chdir(oldDir)
421179860b2SJed Brown    timeout = 1
422179860b2SJed Brown    for i in range(10):
423179860b2SJed Brown      time.sleep(timeout)
424179860b2SJed Brown      timeout *= 2
425179860b2SJed Brown      if timeout > 100: timeout = 100
426179860b2SJed Brown      if os.path.exists(addrFilename): return
427179860b2SJed Brown    self.writeLogLine('CLIENT: Could not start server')
428179860b2SJed Brown    return
429179860b2SJed Brown
430179860b2SJed Brown  def connectParent(self, addr, dir):
431179860b2SJed Brown    '''Try to connect to a parent RDict server
432179860b2SJed Brown       - If addr and dir are both None, this operation fails
433179860b2SJed Brown       - If addr is None, check for an address file in dir'''
434179860b2SJed Brown    if addr is None:
435179860b2SJed Brown      if dir is None: return 0
436179860b2SJed Brown      addr = self.getServerAddr(dir)
437179860b2SJed Brown
438179860b2SJed Brown    import socket
439179860b2SJed Brown    import errno
440179860b2SJed Brown    connected = 0
441179860b2SJed Brown    s         = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
442179860b2SJed Brown    timeout   = 1
443179860b2SJed Brown    for i in range(10):
444179860b2SJed Brown      try:
445179860b2SJed Brown        self.writeLogLine('CLIENT: Trying to connect to '+str(addr))
446179860b2SJed Brown        s.connect(addr)
447179860b2SJed Brown        connected = 1
448179860b2SJed Brown        break
4495b6bfdb9SJed Brown      except socket.error as e:
450179860b2SJed Brown        self.writeLogLine('CLIENT: Failed to connect: '+str(e))
451179860b2SJed Brown        if e[0] == errno.ECONNREFUSED:
452179860b2SJed Brown          try:
453179860b2SJed Brown            import time
454179860b2SJed Brown            time.sleep(timeout)
455179860b2SJed Brown            timeout *= 2
456179860b2SJed Brown            if timeout > 100: timeout = 100
457179860b2SJed Brown          except KeyboardInterrupt:
458179860b2SJed Brown            break
459179860b2SJed Brown          # Try to spawn parent
460179860b2SJed Brown          if dir:
461179860b2SJed Brown            filename = os.path.join(dir, self.addrFilename)
462179860b2SJed Brown            if os.path.isfile(filename):
463179860b2SJed Brown              os.remove(filename)
464179860b2SJed Brown            self.startServer(filename)
4655b6bfdb9SJed Brown      except Exception as e:
466179860b2SJed Brown        self.writeLogLine('CLIENT: Failed to connect: '+str(e.__class__)+': '+str(e))
467179860b2SJed Brown    if not connected:
468179860b2SJed Brown      self.writeLogLine('CLIENT: Failed to connect to parent')
469179860b2SJed Brown      return 0
470179860b2SJed Brown    self.parent = s
471179860b2SJed Brown    self.writeLogLine('CLIENT: Connected to '+str(self.parent))
472179860b2SJed Brown    return 1
473179860b2SJed Brown
474179860b2SJed Brown  def sendPacket(self, s, packet, source = 'Unknown', isPickled = 0):
475179860b2SJed Brown    '''Pickle the input packet. Send first the size of the pickled string in 32-bit integer, and then the string itself'''
476179860b2SJed Brown    self.writeLogLine(source+': Sending packet '+str(packet))
477179860b2SJed Brown    if isPickled:
478179860b2SJed Brown      p = packet
479179860b2SJed Brown    else:
480492432c8SJed Brown      p = pickle.dumps(packet)
481179860b2SJed Brown    self.packer.reset()
482179860b2SJed Brown    self.packer.pack_uint(len(p))
483179860b2SJed Brown    if hasattr(s, 'write'):
484179860b2SJed Brown      s.write(self.packer.get_buffer())
485179860b2SJed Brown      s.write(p)
486179860b2SJed Brown    else:
487179860b2SJed Brown      s.sendall(self.packer.get_buffer())
488179860b2SJed Brown      s.sendall(p)
489179860b2SJed Brown    self.writeLogLine(source+': Sent packet')
490179860b2SJed Brown    return
491179860b2SJed Brown
492179860b2SJed Brown  def recvPacket(self, s, source = 'Unknown'):
493179860b2SJed Brown    '''Receive first the size of the pickled string in a 32-bit integer, and then the string itself. Return the unpickled object'''
494179860b2SJed Brown    self.writeLogLine(source+': Receiving packet')
495179860b2SJed Brown    if hasattr(s, 'read'):
496179860b2SJed Brown      s.read(4)
497492432c8SJed Brown      value = pickle.load(s)
498179860b2SJed Brown    else:
499179860b2SJed Brown      # I probably need to check that it actually read these 4 bytes
500179860b2SJed Brown      self.unpacker.reset(s.recv(4))
501179860b2SJed Brown      length    = self.unpacker.unpack_uint()
502179860b2SJed Brown      objString = ''
503179860b2SJed Brown      while len(objString) < length:
504179860b2SJed Brown        objString += s.recv(length - len(objString))
505492432c8SJed Brown      value = pickle.loads(objString)
506179860b2SJed Brown    self.writeLogLine(source+': Received packet '+str(value))
507179860b2SJed Brown    return value
508179860b2SJed Brown
509179860b2SJed Brown  def send(self, key = None, value = None, operation = None):
510179860b2SJed Brown    '''Send a request to the parent'''
511179860b2SJed Brown    import inspect
512179860b2SJed Brown
513179860b2SJed Brown    objString = ''
514179860b2SJed Brown    for i in range(3):
515179860b2SJed Brown      try:
516179860b2SJed Brown        packet = []
517179860b2SJed Brown        if operation is None:
518179860b2SJed Brown          operation = inspect.stack()[1][3]
519179860b2SJed Brown        packet.append(operation)
520179860b2SJed Brown        if not key is None:
521179860b2SJed Brown          packet.append(key)
522179860b2SJed Brown          if not value is None:
523179860b2SJed Brown            packet.append(value)
524179860b2SJed Brown        self.sendPacket(self.parent, tuple(packet), source = 'CLIENT')
525179860b2SJed Brown        response = self.recvPacket(self.parent, source = 'CLIENT')
526179860b2SJed Brown        break
5275b6bfdb9SJed Brown      except IOError as e:
528179860b2SJed Brown        self.writeLogLine('CLIENT: IOError '+str(e))
529179860b2SJed Brown        if e.errno == 32:
530179860b2SJed Brown          self.connectParent(self.parentAddr, self.parentDirectory)
5315b6bfdb9SJed Brown      except Exception as e:
532179860b2SJed Brown        self.writeLogLine('CLIENT: Exception '+str(e)+' '+str(e.__class__))
533179860b2SJed Brown    try:
534179860b2SJed Brown      if isinstance(response, Exception):
535179860b2SJed Brown        self.writeLogLine('CLIENT: Got an exception '+str(response))
536179860b2SJed Brown        raise response
537179860b2SJed Brown      else:
538179860b2SJed Brown        self.writeLogLine('CLIENT: Received value '+str(response)+' '+str(type(response)))
539179860b2SJed Brown    except UnboundLocalError:
540179860b2SJed Brown      self.writeLogLine('CLIENT: Could not unpickle response')
541179860b2SJed Brown      response  = None
542179860b2SJed Brown    return response
543179860b2SJed Brown
544179860b2SJed Brown  def serve(self):
545179860b2SJed Brown    '''Start a server'''
546179860b2SJed Brown    import socket
547179860b2SJed Brown    import SocketServer
548179860b2SJed Brown
549179860b2SJed Brown    if not useThreads:
550179860b2SJed Brown      raise RuntimeError('Cannot run a server if threads are disabled')
551179860b2SJed Brown
552179860b2SJed Brown    class ProcessHandler(SocketServer.StreamRequestHandler):
553179860b2SJed Brown      def handle(self):
554179860b2SJed Brown        import time
555179860b2SJed Brown
556179860b2SJed Brown        self.server.rdict.lastAccess = time.time()
557179860b2SJed Brown        self.server.rdict.writeLogLine('SERVER: Started new handler')
558179860b2SJed Brown        while 1:
559179860b2SJed Brown          try:
560179860b2SJed Brown            value = self.server.rdict.recvPacket(self.rfile, source = 'SERVER')
5615b6bfdb9SJed Brown          except EOFError as e:
562179860b2SJed Brown            self.server.rdict.writeLogLine('SERVER: EOFError receiving packet '+str(e)+' '+str(e.__class__))
563179860b2SJed Brown            return
5645b6bfdb9SJed Brown          except Exception as e:
565179860b2SJed Brown            self.server.rdict.writeLogLine('SERVER: Error receiving packet '+str(e)+' '+str(e.__class__))
566179860b2SJed Brown            self.server.rdict.sendPacket(self.wfile, e, source = 'SERVER')
567179860b2SJed Brown            continue
568179860b2SJed Brown          if value[0] == 'stop': break
569179860b2SJed Brown          try:
570179860b2SJed Brown            response = getattr(self.server.rdict, value[0])(*value[1:])
5715b6bfdb9SJed Brown          except Exception as e:
572179860b2SJed Brown            self.server.rdict.writeLogLine('SERVER: Error executing operation '+str(e)+' '+str(e.__class__))
573179860b2SJed Brown            self.server.rdict.sendPacket(self.wfile, e, source = 'SERVER')
574179860b2SJed Brown          else:
575179860b2SJed Brown            self.server.rdict.sendPacket(self.wfile, response, source = 'SERVER')
576179860b2SJed Brown        return
577179860b2SJed Brown
578179860b2SJed Brown    # check if server is running
579179860b2SJed Brown    if os.path.exists(self.addrFilename):
580179860b2SJed Brown      rdict     = RDict(parentDirectory = '.')
581179860b2SJed Brown      hasParent = rdict.hasParent()
582179860b2SJed Brown      del rdict
583179860b2SJed Brown      if hasParent:
584179860b2SJed Brown        self.writeLogLine('SERVER: Another server is already running')
585179860b2SJed Brown        raise RuntimeError('Server already running')
586179860b2SJed Brown
587179860b2SJed Brown    # Daemonize server
588179860b2SJed Brown    self.writeLogLine('SERVER: Daemonizing server')
589179860b2SJed Brown    if os.fork(): # Launch child
590179860b2SJed Brown      os._exit(0) # Kill off parent, so we are not a process group leader and get a new PID
591179860b2SJed Brown    os.setsid()   # Set session ID, so that we have no controlling terminal
592179860b2SJed Brown    # We choose to leave cwd at RDict.py: os.chdir('/') # Make sure root directory is not on a mounted drive
5935b6bfdb9SJed Brown    os.umask(0o77) # Fix creation mask
594179860b2SJed Brown    for i in range(3): # Crappy stopgap for closing descriptors
595179860b2SJed Brown      try:
596179860b2SJed Brown        os.close(i)
5975b6bfdb9SJed Brown      except OSError as e:
598179860b2SJed Brown        if e.errno != errno.EBADF:
599179860b2SJed Brown          raise RuntimeError('Could not close default descriptor '+str(i))
600179860b2SJed Brown
601179860b2SJed Brown    # wish there was a better way to get a usable socket
602179860b2SJed Brown    self.writeLogLine('SERVER: Establishing socket server')
603179860b2SJed Brown    basePort = 8000
604179860b2SJed Brown    flag     = 'nosocket'
605179860b2SJed Brown    p        = 1
606179860b2SJed Brown    while p < 1000 and flag == 'nosocket':
607179860b2SJed Brown      try:
608179860b2SJed Brown        server = SocketServer.ThreadingTCPServer((socket.gethostname(), basePort+p), ProcessHandler)
609179860b2SJed Brown        flag   = 'socket'
6105b6bfdb9SJed Brown      except Exception as e:
611179860b2SJed Brown        p = p + 1
612179860b2SJed Brown    if flag == 'nosocket':
613179860b2SJed Brown      p = 1
614179860b2SJed Brown      while p < 1000 and flag == 'nosocket':
615179860b2SJed Brown        try:
616179860b2SJed Brown          server = SocketServer.ThreadingTCPServer(('localhost', basePort+p), ProcessHandler)
617179860b2SJed Brown          flag   = 'socket'
6185b6bfdb9SJed Brown        except Exception as e:
619179860b2SJed Brown          p = p + 1
620179860b2SJed Brown    if flag == 'nosocket':
621179860b2SJed Brown      self.writeLogLine('SERVER: Could not established socket server on port '+str(basePort+p))
6225b6bfdb9SJed Brown      raise RuntimeError('Cannot get available socket')
623179860b2SJed Brown    self.writeLogLine('SERVER: Established socket server on port '+str(basePort+p))
624179860b2SJed Brown
625179860b2SJed Brown    self.isServer = 1
626179860b2SJed Brown    self.writeServerAddr(server)
627179860b2SJed Brown    self.serverShutdown(os.getpid())
628179860b2SJed Brown
629179860b2SJed Brown    server.rdict = self
630179860b2SJed Brown    self.writeLogLine('SERVER: Started server')
631179860b2SJed Brown    server.serve_forever()
632179860b2SJed Brown    return
633179860b2SJed Brown
634179860b2SJed Brown  def load(self):
635179860b2SJed Brown    '''Load the saved dictionary'''
636179860b2SJed Brown    if not self.parentDirectory is None and os.path.samefile(os.getcwd(), self.parentDirectory):
637179860b2SJed Brown      return
638179860b2SJed Brown    self.saveFilename = os.path.abspath(self.saveFilename)
639179860b2SJed Brown    if os.path.exists(self.saveFilename):
640179860b2SJed Brown      try:
641c6ef1b5bSJed Brown        dbFile = open(self.saveFilename)
642492432c8SJed Brown        data   = pickle.load(dbFile)
643179860b2SJed Brown        self.updateTypes(data)
644179860b2SJed Brown        dbFile.close()
645179860b2SJed Brown        self.writeLogLine('Loaded dictionary from '+self.saveFilename)
6465b6bfdb9SJed Brown      except Exception as e:
647179860b2SJed Brown        self.writeLogLine('Problem loading dictionary from '+self.saveFilename+'\n--> '+str(e))
648179860b2SJed Brown    else:
649179860b2SJed Brown      self.writeLogLine('No dictionary to load in this file: '+self.saveFilename)
650179860b2SJed Brown    return
651179860b2SJed Brown
652603e7a67SSatish Balay  def save(self, force = 1):
653179860b2SJed Brown    '''Save the dictionary after 5 seconds, ignoring all subsequent calls until the save
654179860b2SJed Brown       - Giving force = True will cause an immediate save'''
655179860b2SJed Brown    if self.readonly: return
65608eb64ffSMatthew G. Knepley    if force:
657179860b2SJed Brown      self.saveTimer = None
658179860b2SJed Brown      # This should be a critical section
659*b8b3d021SJed Brown      dbFile = open(self.saveFilename, 'wb')
660bb3dd2f6SJed Brown      data   = dict([i for i in self.localitems() if not i[1].getTemporary()])
661492432c8SJed Brown      pickle.dump(data, dbFile)
662179860b2SJed Brown      dbFile.close()
663179860b2SJed Brown      self.writeLogLine('Saved local dictionary to '+os.path.abspath(self.saveFilename))
664179860b2SJed Brown    elif not self.saveTimer:
665179860b2SJed Brown      import threading
666179860b2SJed Brown      self.saveTimer = threading.Timer(5, self.save, [], {'force': 1})
667179860b2SJed Brown      self.saveTimer.setDaemon(1)
668179860b2SJed Brown      self.saveTimer.start()
669179860b2SJed Brown    return
670179860b2SJed Brown
671179860b2SJed Brown  def shutdown(self):
672179860b2SJed Brown    '''Shutdown the dictionary, writing out changes and notifying parent'''
673179860b2SJed Brown    if self.saveTimer:
674179860b2SJed Brown      self.saveTimer.cancel()
675179860b2SJed Brown      self.save(force = 1)
676179860b2SJed Brown    if self.isServer and os.path.isfile(self.addrFilename):
677179860b2SJed Brown      os.remove(self.addrFilename)
678179860b2SJed Brown    if not self.parent is None:
679179860b2SJed Brown      self.sendPacket(self.parent, self.stopCmd, isPickled = 1)
680179860b2SJed Brown      self.parent.close()
681179860b2SJed Brown      self.parent = None
682179860b2SJed Brown    self.writeLogLine('Shutting down')
683179860b2SJed Brown    self.logFile.close()
684179860b2SJed Brown    return
685179860b2SJed Brown
686179860b2SJed Brown  def serverShutdown(self, pid, delay = shutdownDelay):
687179860b2SJed Brown    if self.shutdownTimer is None:
688179860b2SJed Brown      import threading
689179860b2SJed Brown
690179860b2SJed Brown      self.shutdownTimer = threading.Timer(delay, self.serverShutdown, [pid], {'delay': 0})
691179860b2SJed Brown      self.shutdownTimer.setDaemon(1)
692179860b2SJed Brown      self.shutdownTimer.start()
693179860b2SJed Brown      self.writeLogLine('SERVER: Set shutdown timer for process '+str(pid)+' at '+str(delay)+' seconds')
694179860b2SJed Brown    else:
695179860b2SJed Brown      try:
696179860b2SJed Brown        import signal
697179860b2SJed Brown        import time
698179860b2SJed Brown
699179860b2SJed Brown        idleTime = time.time() - self.lastAccess
700179860b2SJed Brown        self.writeLogLine('SERVER: Last access '+str(self.lastAccess))
701179860b2SJed Brown        self.writeLogLine('SERVER: Idle time '+str(idleTime))
702179860b2SJed Brown        if idleTime < RDict.shutdownDelay:
703179860b2SJed Brown          self.writeLogLine('SERVER: Extending shutdown timer for '+str(pid)+' by '+str(RDict.shutdownDelay - idleTime)+' seconds')
704179860b2SJed Brown          self.shutdownTimer = None
705179860b2SJed Brown          self.serverShutdown(pid, RDict.shutdownDelay - idleTime)
706179860b2SJed Brown        else:
707179860b2SJed Brown          self.writeLogLine('SERVER: Killing server '+str(pid))
708179860b2SJed Brown          os.kill(pid, signal.SIGTERM)
7095b6bfdb9SJed Brown      except Exception as e:
710179860b2SJed Brown        self.writeLogLine('SERVER: Exception killing server: '+str(e))
711179860b2SJed Brown    return
712179860b2SJed Brown
713179860b2SJed Brownif __name__ ==  '__main__':
714179860b2SJed Brown  import sys
715179860b2SJed Brown  try:
716179860b2SJed Brown    if len(sys.argv) < 2:
7175b6bfdb9SJed Brown      print('RDict.py [server | client | clear | insert | remove] [parent]')
718179860b2SJed Brown    else:
719179860b2SJed Brown      action = sys.argv[1]
720179860b2SJed Brown      parent = None
721179860b2SJed Brown      if len(sys.argv) > 2:
722179860b2SJed Brown        if not sys.argv[2] == 'None': parent = sys.argv[2]
723179860b2SJed Brown      if action == 'server':
724179860b2SJed Brown        RDict(parentDirectory = parent).serve()
725179860b2SJed Brown      elif action == 'client':
7265b6bfdb9SJed Brown        print('Entries in server dictionary')
727179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
728179860b2SJed Brown        for key in rdict.types():
729179860b2SJed Brown          if not key.startswith('cacheKey') and not key.startswith('stamp-'):
7305b6bfdb9SJed Brown            print(str(key)+' '+str(rdict.getType(key)))
731179860b2SJed Brown      elif action == 'cacheClient':
7325b6bfdb9SJed Brown        print('Cache entries in server dictionary')
733179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
734179860b2SJed Brown        for key in rdict.types():
735179860b2SJed Brown          if key.startswith('cacheKey'):
7365b6bfdb9SJed Brown            print(str(key)+' '+str(rdict.getType(key)))
737179860b2SJed Brown      elif action == 'stampClient':
7385b6bfdb9SJed Brown        print('Stamp entries in server dictionary')
739179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
740179860b2SJed Brown        for key in rdict.types():
741179860b2SJed Brown          if key.startswith('stamp-'):
7425b6bfdb9SJed Brown            print(str(key)+' '+str(rdict.getType(key)))
743179860b2SJed Brown      elif action == 'clear':
7445b6bfdb9SJed Brown        print('Clearing all dictionaries')
745179860b2SJed Brown        RDict(parentDirectory = parent).clear()
746179860b2SJed Brown      elif action == 'insert':
747179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
748179860b2SJed Brown        rdict[sys.argv[3]] = sys.argv[4]
749179860b2SJed Brown      elif action == 'remove':
750179860b2SJed Brown        rdict = RDict(parentDirectory = parent)
751179860b2SJed Brown        del rdict[sys.argv[3]]
752179860b2SJed Brown      else:
753179860b2SJed Brown        sys.exit('Unknown action: '+action)
7545b6bfdb9SJed Brown  except Exception as e:
755179860b2SJed Brown    import traceback
7565b6bfdb9SJed Brown    print(traceback.print_tb(sys.exc_info()[2]))
757179860b2SJed Brown    sys.exit(str(e))
758179860b2SJed Brown  sys.exit(0)
759