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