-r"""Threaded Socket Server"""
+r"""Threaded Socket Server
+This server listens for connections, and then sends plot data as requested.
+Each connection request is treated as a single request -- multiple requests
+require multiple connections.
+* Allow the server to respond to a limited set of requests, such as a query for
+ information about the process running etc. (Each server should provide a bit
+ of human-readable meta-data so that clients can select from the appropriate
+ server if a machine is running multiple servers on multiple ports.
+* Ensure that a simple default blocking mode works.
+* Choose the next avialable port if the default port is blocked.
r"""Return `port` or the default value if `port` is `None`."""
Call :meth:`update` to update the plot data.
+ If `True`, then the data is pickled *every time* a client requests the
+ data, otherwise, it is only pickled after :meth:`update` is called. It
+ might be slightly dangerous to use this mode because the computational
+ (main) thread could be modifying the data while the pickle is being
+ created. (I.e. the main thread does not use locks.) This can be useful,
+ however, if the main computation updates the data directly because plot
+ clients can update as fast as possible -- even if the server only calls
+ :meth:`update` infrequently.
+ Setting `continuous` to `False` will prevent unecessary pickling from
+ occuring, but then the clients will only see new data after
+ :meth:`update` is called.
+ * Send the `_id` with the pickle to the clients. In return, the client
+ should sent the `_id` of the last request so that new data is sent only if
- def __init__(self, host=None, port=None):
+ def __init__(self, host=None, port=None):
self.lock = threading.Lock() # RLock()??
self.have_data = threading.Event()
+ self.continuous = continuous
+ self._id = 0 # Each time update is called, _id changes
+ self._pickle_cache = None # Pickle cache to prevent repickling
self.daemon_threads = True # Don't keep python alive.
self.allow_reuse_address = True # Allow port reuse after termination.
- SocketServer.TCPServer.__init__(self, (get_host(host), get_port(port)),
+ self, (get_host(host), get_port(port)), PlotRequestHandler)
+ # To Do: If this fails because the port is in use, then try a
+ # different port and tell the user. Probably do this with a for
+ # loop over a few ports.
- def _get_data(self
r"""The object is pickled here."""
- return cPickle.dumps(self.data)
+ if (self._pickle_cache is None
+ or self._pickle_cache != self._id
+ self._id, cPickle.dumps(self.data))
+ return self._pickle_cache
def update(self, *v, **kw):
self.have_data.set() # This is very slow!
- r"""If you pass `spawn_client_command`, then a plot client will be spawned
+ r"""If you pass `client_cmd`, then a plot client will be spawned in a
+ This server acts as both a `Thread`, and a context manager. The thread part
+ spawns the socket server that listens for connections, creates pickles, and
+ sends them. The context manager provides a mechanism for ensuring
+ everything is shut down. Typical usage is as follows::
+ # Start of the server context...
+ for n in xrange(iterations):
+ <do stuff, computing x, y, data etc.>
+ s.update(x, y, data=data, <all args required by Plotter.draw>)
+ # ... end of the server context. Server will be shutdown when
+ * Make launching client much more robust!