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