Search code examples
pythonmultithreadingloggingeventlet

Is it safe to inject context into a eventlet thread like this?


I need to inject thread level context information for logging and debugging purposes. I've been told that this is potentially unsafe.

greenthread = worker_pool.spawn(run_worker, args, handle_result)
# this logging_context attribute will be accessed by the logging library
greenthread.__dict__['logging_context'] = 'data for logging'
greenthread.link()

While certainly not something you would want to do often, it was the only way I could set a thread local global constant, which the logger could access.

This can then be accessed later by the logger via

eventlet.getcurrent().logging_context

As far as my knowledge in python goes, I don't see how this is unsafe, why do others say this is potentially a recipe for disaster?


While I see it as a rather ugly monkey patch, I am not creating global mutable state. I am creating a thread-local constant that is instantiated before the thread is even run.


Solution

  • I agree, "safe/unsafe" is for obsessed parents. As programmers we can define expectancy for troubles in a much more strict and useful way.

    • That approach works now, so while eventlet and greenlet versions are locked, it will work and everything is great.
    • You're changing Greenthread attributes after spawn. Right now it takes sleep() or other way to yield to "event loop" before newly spawned greenthread executes, but that behavior may change in future. Which means that run_worker may already be running. You can use eventlet.pools.Pool() to manage proper setup and link of greenthreads before allowing them to run.
    • In future it may break because greenthread would get __slots__ to save memory, or particular __setattr__ implementation for other reasons.
    • There is tested and supported API for what you're doing, it's called thread local storage. You use it like regular Python threadlocal object, but use patched version of threading[1].
    • You may like Logbook[2] more, it has cleaner way to do the same (using threadlocal under the hood).

    Sample code for threadlocal route:

    def add_logging_context(context, fun, *a, **kw):
      tl = threading.local()
      tl.logging_context = context
      return fun(*a, **kw)
    
    pool.spawn(add_logging_context, 'data for logging', run_worker, args, handle_result)
    

    [1] http://eventlet.net/doc/patching.html

    [2] https://logbook.readthedocs.io/en/stable/