220 likes | 341 Views
Death to tasklets!. Why killing tasklets is good practice. History. Multiprocessing Master process spawns off multiple processes Waits for them to finish Multi-threading Within a process, threads are spawned „main thread“ waits for their results. A typical problem.
E N D
Death to tasklets! Why killing tasklets is good practice
History • Multiprocessing • Master process spawns off multiple processes • Waits for them to finish • Multi-threading • Within a process, threads are spawned • „main thread“ waits for their results
A typical problem • Main „process“ starts a „server process“ • Main process wants to stop server process
How? • Ask the server to quit • Message passing is nice • Politeness is nice • Good and tidy programing practice • Kill it with death! • Asynchronous (where does it get killed?) • Impolite (will it clean up?) • Impossible (what API is that?)
MEGADEATH • Processes: • Unix signals: • SIGINT, can cause system calls to fail with an EINTERRUPTED error code. • SIGKILL or SIGTERM just kills them outright • Files are closed, memory is released. • Buffers are not flushed • Windows: • TerminateProcess() kills a process outright • Files, sockets, are closed, memory released, but buffers are not flushed
MEGADEATH • Threads: • pthreads • pthread_cancel() on cancellation points. • Win32 • TerminateThread() kills the thread dead. • Python • Threading.Thread has no kill() method • Python resources (references) would not be cleaned up by a hard kill • Pthread_cancel() could be used to implement a ‚soft‘ kill but is not available on all platforms. • CPython thread switching is synchronized (via GIL) so sending exceptions to threads is technically possible • CPython devs haven‘t drunk the Svali yet.
MEGADEATH • Killing processes/threads: • If at all possible, it is messy (portability) • C++ exception handling is usually not invoked • Resources are released, but cleanup not executed • Sockets are aborted, causing errors • Locks are left un-released • Buffers are left un-flushed • Karma is left un-balanced
MEGADEATH • Tasklets? • Tasklet.kill() • Sends a TaskletExit exception to the tasklet • Immediatelly awakes the target • Optional „pending“ delivery mode of exception • TaskletExit is BaseException • Not caught by regular exception filters which catch Exception objects • Normally causes the exception to bubble to the top • Finally clauses and context managers run as expected
Microlife • Killing tasklets is synchronous • It is like sending a message to a blocking method • Killing tasklets is not killing • It is merely raising an exception • It can even be caught, logged, handled, etc. • Killing tasklet is not a system operation • It invokes the language‘s native exception handling (unlike, e.g. C++)
Microlife • Now we have an out-of-band way of sending messages to workers! • Code written with proper try/finally scopes works as expected. • Other apis and other exceptions can be used: • Tasklet.kill(pending=True) # new feature • Tasklet.throw(exc, val, tb, pending=False)
Microlife • Examples: SocketServer.py defserve_forever(self, poll_interval=0.5): """Handle one request at a time until shutdown. Polls for shutdown every poll_interval seconds. Ignores self.timeout. If you need to do periodic tasks, do them in another thread. """ self.__is_shut_down.clear() try: whilenotself.__shutdown_request: # XXX: Consider using another file descriptor or # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. r, w, e =select.select([self], [], [], poll_interval) ifselfin r: self._handle_request_noblock() finally: self.__shutdown_request=False self.__is_shut_down.set()
Microlife • Examples: baseCorporation.py whileTrue: self.LogInfo("RunContactUpdates sleeping for", CONTACT_UPDATE_SLEEPTIME, "minutes") blue.pyos.synchro.SleepWallclock(1000*60* CONTACT_UPDATE_SLEEPTIME) ifnotself.running: return …
Dust UI • UI code can be many tasklets doing things, e.g. Waiting for input. • Complex UI state must be shut down e.g. When disconnection occurs • UI tasklets register with the UI manager • UI code carefully uses try:/finally: or context managers to release UI elements. • UI manager can kill all registered tasklets, causing UI to be shut down gracefully.
Dust UI defUIDialogBoxWorker(): with UIManager.register_tasklet(): with UI.DialogueBox() as box: input = box.GetResult() # blocks here class UIManager(object): defOnClear(self): for worker in self.GetWorkers(): worker.kill()
Servitor • Gated access to a service defStartProcess(self): self.servitor = sake.servitor.DirectServitor() defServitorCall(self, function, args): """ Wrap the call. This can involve a servitor, for example. The purpose of this is to allow Crust to cancel any calls that are in progress. """ # Use the DirectServitor. This causes TaskletExit calls to result # in a stacklesslib.errors.CancelledError to be raised. return self.servitor.call(function, args) defDisconnect(self, *reasons): # We have decided to disconnect. Kill all calls currently in action self.servitor.kill_servitors(args=reasons)
Servitor • Clients is aware of CancelledError (or not): defGetThrused(): svc = app.GetService('ThrusingService') try: return svc.DoTheThrus('I am the bethrused. Thrus me!') except stacklesslib.errors.CancelledError: # handle this error here: return None
Servitor • Server wraps its api: class ThrusingService(service): defStartService(self): self.servitor = servitor.SimpleServitor() defGetAPI(self): return servitor.WrapAPI(self) defCancelClients(self): self.servitor.cancel_clients() defDoTheThrus(self, subject): return sys.network.interact(self.target, subject)
Softkill • Tasklet kill is a soft kill • A gentle nudge of a rocking boat on a summer evening • Requests a tasklet to politely stop what it is doing • TaskletExit is a special exception that is silently ignored at the top level. • Like a gentle rustle in the autumn leaves
Softkill • What to be aware of: • Every stackless switching function can raise an exception. • Running under watchdog, every instruction can raise an exception. (see: atomic) • Must be aware of this if writing low-level stackless code, such as when using channels. • stacklesslib.locks classes are exception-aware • So are uthread.locks and others in EVE, I believe.
Softkill • Best practices: • Use context managers to tear down resources • Acquire locks, open files, etc. • Know that blocking calls can raise exceptions • But everyone knew that, right? • Use „with stacklesslib.util.atomic():“ to guard critical stackless framework code. • Because you just love to write your own synchronization primitives
Do It! • Never again write polling loops with exit request states • Grow a moustache or plant an herb garden. • Go forth and procreate.