diff --git a/eventlet/saranwrap.py b/eventlet/saranwrap.py index 07e3124..8af56f3 100644 --- a/eventlet/saranwrap.py +++ b/eventlet/saranwrap.py @@ -86,8 +86,8 @@ The wire protocol is to pickle the Request class in this file. The request class is basically an action and a map of parameters' """ -import os from cPickle import dumps, loads +import os import struct import sys @@ -182,6 +182,8 @@ class Request(object): def _read_lp_hunk(stream): len_bytes = stream.read(4) + if len_bytes == '': + raise EOFError("No more data to read from %s" % stream) length = struct.unpack('I', len_bytes)[0] body = stream.read(length) return body @@ -247,6 +249,7 @@ def _unmunge_attr_name(name): name = name[len('_Proxy'):] if(name.startswith('_ObjectProxy')): name = name[len('_ObjectProxy'):] + return name class ChildProcess(object): @@ -282,6 +285,9 @@ class ChildProcess(object): return retval + def __del__(self): + self._in.close() + class Proxy(object): """\ @@ -320,7 +326,10 @@ not supported, so you have to know what has been exported. request = Request('del', {'id':dead_object}) my_cp.make_request(request) - _dead_list.remove(dead_object) + try: + _dead_list.remove(dead_object) + except KeyError: + pass # Pass all public attributes across to find out if it is # callable or a simple attribute. @@ -423,6 +432,11 @@ not need to deal with this class directly.""" def proxied_type(self): + """ Returns the type of the object in the child process. + + Calling type(obj) on a saranwrapped object will always return + , so this is a way to get at the + 'real' type value.""" if type(self) is not ObjectProxy: return type(self) @@ -431,6 +445,14 @@ def proxied_type(self): request = Request('type', {'id':my_id}) return my_cp.make_request(request) + +def getpid(self): + """ Returns the pid of the child process. The argument should be + a saranwrapped object.""" + my_cp = self.__local_dict['_cp'] + return my_cp._in.getpid() + + class CallableProxy(object): """\ @class CallableProxy @@ -527,7 +549,11 @@ when the id is None.""" _log("del %s from %s" % (id, self._objects)) # *TODO what does __del__ actually return? - del self._objects[id] + try: + del self._objects[id] + except KeyError: + pass + return None def handle_type(self, obj, req): @@ -547,7 +573,10 @@ when the id is None.""" try: str_ = _read_lp_hunk(self._in) except EOFError: - sys.exit(0) # normal exit + if _g_debug_mode: + _log("Exiting normally") + sys.exit(0) + request = loads(str_) _log("request: %s (%s)" % (request, self._objects)) req = request diff --git a/greentest/saranwrap_test.py b/greentest/saranwrap_test.py index 7ac229b..0109c05 100644 --- a/greentest/saranwrap_test.py +++ b/greentest/saranwrap_test.py @@ -338,6 +338,13 @@ sys_path = sys.path""") 'random' in obj_proxy.get_dict(), 'Coroutine in saranwrapped object did not run') + def test_child_process_death(self): + prox = saranwrap.wrap({}) + pid = saranwrap.getpid(prox) + self.assertEqual(os.kill(pid, 0), None) # assert that the process is running + del prox # removing all references to the proxy should kill the child process + self.assertRaises(OSError, os.kill, pid, 0) # raises OSError if pid doesn't exist + def test_detection_of_server_crash(self): # make the server crash here pass