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