Skip to content
Andrew Geiger edited this page Jul 28, 2022 · 1 revision

Threading helpers

Threads are a bit tricky even on the best situations, and unlike the standard Python, Jython runs without a GIL and with real Java threads. Handling them is a bit different than perhaps expected, but it also can trivialize multiprocessing problems!

@async

Function signature: def async(startDelaySeconds=None, name=None, maxAllowedRuntime=None, killSwitch=None, ensureOnlyOne=False):

Decorator that elevates any function it decorates to invoke its execution in an asynchronous thread.

IFF the first argument is a str, then that will be understood as the name of the thread when launched.

Arguments

startDelaySeconds

Delay the start of the function. When the function is called, it will start a new thread and then wait startDelaySeconds before running the function with the given arguments.

Use this when you need to kick off a process in a bit, but without blocking the current execution. It may be easy to use this to fudge around loading/race conditions, but there are many legitimate problems where waiting a few seconds drastically simplifies the async problem. It's usually better to not depend on a time, but it's handy in a pinch.

name

Name the thread this function gets evoked from.

In general, name your stuff. If you look on a gateway and see Thread-23 that tells you nothing. But if you see, for example, ExtraGlobal-Monitor you know it came from ExtraGlobal. Naming things drastically simplifies debugging and makes it far, far easier to know what your threads are up to.

Plus, if you name your threads, then you can hunt them down and murder inspect them later if they get out of hand!

maxAllowedRuntime

If a maxAllowedRuntime is provided, then a watchdog thread is spawned to keep an eye on your function. If it takes too long, this will murder it in cold blood. Use this when you're waiting on a process you don't want to wait forever on but don't trust to finish in time. Or for a process that only needs to run for a limited amount of time.

killSwitch

In contrast to the max allowed runtime, the killSwitch is a function that when returns True will inform the watchdog to kill the thread. Sometimes you really don't know how long a thread will run for, but there is a clear signal for when it should end. The function provided should be argumentless - a simple lambda or a closure is appropriate here.

Consider the killSwitch a variant of the sentinel pattern, where if that signal shows up a KeyboardInterrupt is fired (similar to a generator's StopIteration).

ensureOnlyOne

When set, this prevents more than one copy of this named async function from running. When you want to be able to call this async function easily but don't want them to stack up, this will make sure that only one survives.

Note that there is a special case where if ensureOnlyOne is negative or reversed then it will always run the most recent invocation and kill older threads. Use this as a way to "try again". For example, a REST call may be made but new info came in that should cancel handling the previous call and repeat with the new values - simply call the function again with this set to reversed and the old call will be killed and the new attempt invoked.

@semaphore

findThreads

dangerouslyKillThreads

As the name implies, use this with caution: any pattern applied to it will subject all matching threads with that name to a KeyboardInterrupt.

If you're in the habit of naming your async threads - and you should be - then this is helpful to control/kill/clean long running threads. Or threads accidentally kicked off with a while True: in it...

NOTE: The bypass_interlock argument is non-optional: by default it causes the function to fail. Do NOT abuse this Molly Guard, please. It is there to make sure that if you run this function, it's not on accident!

Introspection

The following functions are useful for looking inside running threads or grabbing objects from them. In normal operations you'd absolutely not want to use these since they break all kinds of rules, detents, conventions, and probably some moral laws. But - on the other hand - they're super handy. For example, the Logger uses this to better understand the context it was called from (and to fill in blanks/details as needed).

getThreadState

getFromThreadScope

getThreadInfo