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