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