1 """Management for daemon processes
3 This module provides some client support for the daemon management
4 provided in the pdm.srv module.
7 import socket, pickle, struct, select, threading
9 __all__ = ["client", "replclient", "perfclient"]
11 class protoerr(Exception):
12 """Raised on protocol errors"""
16 if isinstance(spec, socket.socket):
21 sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
24 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
25 sk.connect(("localhost", int(spec)))
27 sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
29 sk.connect((spec[:p], int(spec[p + 1:])))
31 raise Exception("Unknown target specification %r" % spec)
35 if sk is not None: sk.close()
41 This class provides general facilities to speak to PDM servers,
42 and is mainly intended to be subclassed to provide for the
43 specific protocols, such as replclient and perfclient do.
45 `client' instances can be passed as arguments to select.select(),
46 and can be used in `with' statements.
48 def __init__(self, sk, proto = None):
49 """Create a client object connected to the specified
50 server. `sk' can either be a socket object, which is used as
51 it is, or a string specification very similar to the
52 specification for pdm.srv.listen, so see its documentation for
53 details. The differences are only that this function does not
54 take arguments specific to socket creation, like the mode and
55 group arguments for Unix sockets. If `proto' is given, that
56 subprotocol will negotiated with the server (by calling the
61 line = self.readline()
63 raise protoerr("Illegal protocol signature")
68 """Close this connection"""
72 """Return the file descriptor of the underlying socket."""
73 return self.sk.fileno()
76 """Read a single NL-terminated line and return it."""
78 p = self.buf.find("\n")
81 self.buf = self.buf[p + 1:]
83 ret = self.sk.recv(1024)
88 def select(self, proto):
89 """Negotiate the given subprotocol with the server"""
91 raise Exception("Illegal protocol specified: %r" % proto)
92 self.sk.send(proto + "\n")
94 if len(rep) < 1 or rep[0] != "+":
95 raise protoerr("Error reply when selecting protocol %s: %s" % (proto, rep[1:]))
100 def __exit__(self, *excinfo):
104 class replclient(client):
105 """REPL protocol client
107 Implements the client side of the REPL protocol; see pdm.srv.repl
108 for details on the protocol and its functionality.
110 def __init__(self, sk):
111 """Create a connected client as documented in the `client' class."""
112 super(replclient, self).__init__(sk, "repl")
115 """Run a single block of Python code on the server. Returns
116 the output of the command (as documented in pdm.srv.repl) as a
120 ncode = code.replace("\n\n", "\n")
121 if ncode == code: break
123 while len(code) > 0 and code[-1] == "\n":
125 self.sk.send(code + "\n\n")
134 raise protoerr("Error reply: %s" % ln[1:])
136 raise protoerr("Illegal reply: %s" % ln)
138 class perfproxy(object):
139 def __init__(self, cl, id, proto):
143 self.subscribers = set()
145 def lookup(self, name):
146 self.cl.lock.acquire()
151 self.cl.lock.release()
152 (proto,) = self.cl.run("lookup", id, self.id, name)
153 proxy = perfproxy(self.cl, id, proto)
154 self.cl.proxies[id] = proxy
158 return self.cl.run("ls", self.id)[0]
161 return self.cl.run("readattr", self.id)[0]
164 return self.cl.run("attrinfo", self.id)[0]
166 def invoke(self, method, *args, **kwargs):
167 return self.cl.run("invoke", self.id, method, args, kwargs)[0]
169 def subscribe(self, cb):
170 if cb in self.subscribers:
171 raise ValueError("Already subscribed")
172 if len(self.subscribers) == 0:
173 self.cl.run("subs", self.id)
174 self.subscribers.add(cb)
176 def unsubscribe(self, cb):
177 if cb not in self.subscribers:
178 raise ValueError("Not subscribed")
179 self.subscribers.remove(cb)
180 if len(self.subscribers) == 0:
181 self.cl.run("unsubs", self.id)
183 def notify(self, ev):
184 for cb in self.subscribers:
190 if self.id is not None:
191 self.cl.run("unbind", self.id)
192 del self.cl.proxies[self.id]
201 def __exit__(self, *excinfo):
205 class perfclient(client):
206 """PERF protocol client
208 Implements the client side of the PERF protocol; see pdm.srv.perf
209 for details on the protocol and its functionality.
211 This client class implements functions for finding PERF objects on
212 the server, and returns, for each server-side object looked up, a
213 proxy object that mimics exactly the PERF interfaces that the
214 object implements. As the proxy objects reference live objects on
215 the server, they should be released when they are no longer used;
216 they implement a close() method for that purpose, and can also be
217 used in `with' statements.
219 See pdm.srv.perf for details on the various PERF interfaces that
220 the proxy objects might implement.
222 def __init__(self, sk):
223 """Create a connected client as documented in the `client' class."""
224 super(perfclient, self).__init__(sk, "perf")
226 self.lock = threading.Lock()
231 buf = pickle.dumps(ob)
232 buf = struct.pack(">l", len(buf)) + buf
235 def recvb(self, num):
237 while len(buf) < num:
238 data = self.sk.recv(num - len(buf))
245 return pickle.loads(self.recvb(struct.unpack(">l", self.recvb(4))[0]))
247 def event(self, id, ev):
248 proxy = self.proxies.get(id)
249 if proxy is None: return
252 def dispatch(self, timeout = None):
253 """Wait for an incoming notification from the server, and
254 dispatch it to the callback functions that have been
255 registered for it. If `timeout' is specified, wait no longer
256 than so many seconds; otherwise, wait forever. This client
257 object may also be used as argument to select.select().
259 rfd, wfd, efd = select.select([self.sk], [], [], timeout)
263 self.event(msg[1], msg[2])
265 raise ValueError("Unexpected non-event message: %r" % msg[0])
270 if reply[0] in ("+", "-"):
272 elif reply[0] == "*":
273 self.event(reply[1], reply[2])
275 raise ValueError("Illegal reply header: %r" % reply[0])
277 def run(self, cmd, *args):
280 self.send((cmd,) + args)
281 reply = self.recvreply()
289 def lookup(self, module, obnm):
290 """Look up a single server-side object by the given name in
291 the given module. Will return a new proxy object for each
292 call when called multiple times for the same name.
300 (proto,) = self.run("bind", id, module, obnm)
301 proxy = perfproxy(self, id, proto)
302 self.proxies[id] = proxy
305 def find(self, name):
306 """Convenience function for looking up server-side objects
307 through PERF directories and for multiple uses. The object
308 name can be given as "MODULE.OBJECT", which will look up the
309 named OBJECT in the named MODULE, and can be followed by any
310 number of slash-separated names, which will assume that the
311 object to the left of the slash is a PERF directory, and will
312 return the object in that directory by the name to the right
313 of the slash. For instance, find("pdm.perf.sysres/cputime")
314 will return the built-in attribute for reading the CPU time
315 used by the server process.
317 ret = self.names.get(name)
321 ret = self.find(name[:p]).lookup(name[p + 1:])
324 ret = self.lookup(name[:p], name[p + 1:])
325 self.names[name] = ret