metaprogramming and politics

Decentralize. Take the red pill.

execution locals: better than thread locals/globals

with 6 comments

While many agree that global state is evil, the so called “thread locals” are not much better. Even though they help to separate state on a per-thread or per-greenlet basis, they still are global within that context. In particular (thread) global state means that:

  • Invoked functions can change bindings of an invoking function as a side effect
  • thread locals may linger around even if their state is not used or became invalid

Meet “execution locals” which avoid these problems. Find the code released on PyPI:

http://pypi.python.org/pypi/xlocal

It’s some 60 lines of code and tested on python2.5 up to python3.3 and pypy and ready to be played with. I inline its README.txt below in case you can’t or don’t want to switch reading context. One more note: If I were to design a new language i’d probably remove “globals” all together and only offer something like the “xlocal” type with a more straight forward syntax.

execution locals: killing global state (including thread locals)

The xlocal module provides execution locals aka “xlocal” objects which implement a more restricted variant of “thread locals”. An “xlocal” instance allows to manage its attributes on a per-execution basis in a manner similar to how real locals work:

  • Invoked functions cannot change the binding for the invoking function
  • existence of a binding is local to a code block (and everything it calls)

Attribute bindings for an xlocal object will not leak outside a context-managed code block and they will not leak to other threads or greenlets. By contrast, both process-globals and “thread locals” do not implement these properties.

Let’s look at a basic example:

# content of example.py

from xlocal import xlocal

xcurrent = xlocal()

def output():
    print "hello world", xcurrent.x

if __name__ == "__main__":
    with xcurrent(x=1):
        output()

If we execute this module, the output() function will see a xcurrent.x==1 binding:

$ python example.py
hello world 1

Here is what happens in detail: xcurrent(x=1) returns a context manager which sets/resets the x attribute on the xcurrent object. While remaining in the same thread/greenlet, all code triggered by the with-body (in this case just the output() function) can access xcurrent.x. Outside the with- body xcurrent.x would raise an AttributeError. It is also not allowed to directly set xcurrent attributes; you always have to explicitely mark their life-cycle with a with-statement. This means that invoked code:

  • cannot rebind xlocal state of its invoking functions (no side effects, yay!)
  • xlocal state does not leak outside the with-context (lifecylcle control)

Another module may now reuse the example code:

# content of example_call.py
import example

with example.xcurrent(x=3):
    example.output()

which when running …:

$ python example_call.py
hello world 3

will cause the example.output() function to print the xcurrent.x binding as defined at the invoking with xcurrent(x=3) statement.

Other threads or greenlets will never see this xcurrent.x binding; they may even set and read their own distincit xcurrent.x object. This means that all threads/greenlets can concurrently call into a function which will always see the execution specific x attribute.

Usage in frameworks and libraries invoking “handlers”

When invoking plugin code or handler code to perform work, you may not want to pass around all state that might ever be needed. Instead of using a global or thread local you can safely pass around such state in execution locals. Here is a pseudo example:

xcurrent = xlocal()

def with_xlocal(func, **kwargs):
    with xcurrent(**kwargs):
        func()

def handle_request(request):
    func = gethandler(request)  # some user code
    spawn(with_xlocal(func, request=request))

handle_request will run a user-provided handler function in a newly spawned execution unit (for example spawn might map to threading.Thread(…).start() or to gevent.spawn(…)). The generic with_xlocal helper wraps the execution of the handler function so that it will see a xcurrent.request binding. Multiple spawns may execute concurrently and xcurrent.request will carry the execution-specific request object in each of them.

Issues worth noting

If a method decides to memorize an attribute of an execution local, for example the above xcurrent.request, then it will keep a reference to the exact request object, not the per-execution one. If you want to keep a per-execution local, you can do it this way for example:

Class Renderer:
    @property
    def request(self):
        return xcurrent.request

this means that Renderer instances will have an execution-local self.request object even if the life-cycle of the instance crosses execution units.

Another issue is that if you spawn new execution units, they will not implicitely inherit execution locals. Instead you have to wrap your spawning function to explicitely set execution locals, similar to what we did in the above “invoking handlers” section.

Written by holger krekel

November 16, 2012 at 2:22 pm

6 Responses

Subscribe to comments with RSS.

  1. From a (very) quick look seems like an independent reinvention of contextual: http://pypi.python.org/pypi/Contextual

    gsk

    November 16, 2012 at 8:18 pm

    • Indeed, Contextual looks similar but more convoluted. “xlocal” aims to be minimal, not requiring subclassing etc. Thanks for the reference.

      holger krekel

      November 19, 2012 at 8:12 am

  2. “as defined at the invoking with xcurrent(x=42) statement”

    It looks like you changed a 3 to a 42 (or vice versa).

    jma

    November 16, 2012 at 10:33 pm

  3. Thank for amaizing and useful module.
    May I suggest you to restrict xlocal.__setitem__ and xlocal.__delitem__ access to avoid situations like this:

    from xlocal import xlocal

    local = xlocal()
    with local(x=None):
    local._getlocals().__setitem__(‘x’, True)

    Aleksey

    November 21, 2012 at 12:38 pm

    • _getlocals() is not meant for usage from the outside, maybe it could be hidden better.

      holger krekel

      November 21, 2012 at 12:51 pm


Leave a Reply to holger krekel Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: