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