Fear and Loathing in Python: Building a Distributed Context System for Wool

bzurak1 pts1 comments

Adapting Python's `contextvars` library for use in distributed systems · GitHub

/" data-turbo-transient="true" />

Skip to content

-->

Search Gists

Search Gists

Sign in

Sign up

You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

{{ message }}

Instantly share code, notes, and snippets.

conradbzura/building-a-distributed-context-system-for-wool.md

Last active<br>June 30, 2026 21:43

Show Gist options

Download ZIP

Star

(0)

You must be signed in to star a gist

Fork

(0)

You must be signed in to fork a gist

Embed

Select an option

Embed<br>Embed this gist in your website.

Share<br>Copy sharable link for this gist.

Clone via HTTPS<br>Clone using the web URL.

No results found

Learn more about clone URLs

Clone this repository at &lt;script src=&quot;https://gist.github.com/conradbzura/885a542ff0ccd548aa16fd05525a7a71.js&quot;&gt;&lt;/script&gt;

" readonly="readonly" data-autoselect="true" data-target="primer-text-field.inputElement " aria-describedby="validation-8df743ca-c018-4247-b0f7-2430511992e0" class="form-control FormControl-monospace FormControl-input FormControl-small rounded-left-0 rounded-right-0 border-right-0" type="text" name="gist-share-url-sized-down" />

Save conradbzura/885a542ff0ccd548aa16fd05525a7a71 to your computer and use it in GitHub Desktop.

Embed

Select an option

Embed<br>Embed this gist in your website.

Share<br>Copy sharable link for this gist.

Clone via HTTPS<br>Clone using the web URL.

No results found

Learn more about clone URLs

Clone this repository at &lt;script src=&quot;https://gist.github.com/conradbzura/885a542ff0ccd548aa16fd05525a7a71.js&quot;&gt;&lt;/script&gt;

" readonly="readonly" data-autoselect="true" data-target="primer-text-field.inputElement " aria-describedby="validation-2df38407-189c-4c51-a5a5-6d1c65a1db01" class="form-control FormControl-monospace FormControl-input FormControl-small rounded-left-0 rounded-right-0 border-right-0" type="text" name="gist-share-url-original" />

Save conradbzura/885a542ff0ccd548aa16fd05525a7a71 to your computer and use it in GitHub Desktop.

Download ZIP

Adapting Python's `contextvars` library for use in distributed systems

Raw

building-a-distributed-context-system-for-wool.md

Fear and Loathing in Python: Building a Distributed Context System for Wool

I would like to share one of the more interesting features I've implemented for Wool recently: a distributed context system with (near) complete parity to Python's native contextvars library. I undertook this endeavor thinking it'd be a neat little feature enabling Wool's users to get more creative with how they design their distributed applications. Well, it took me two attempts to get it right (I hope), and it was enough of an ordeal that I decided to put the experience to paper. Enjoy.

False start

When I first approached the challenge of extending Python's contextvars library to Wool, I'll admit I didn't have an adequate understanding of how it interfaced with asyncio—I was under the impression that newly constructed asyncio tasks always got a shallow copy of their parent context. This is evident in the fact that I chose to store worker proxies in a context var. While in the vast majority of situations my solution would have worked just fine, the real deficiency was the risk of silent corruption in the cases where it didn't.

You see, while Python provides sane defaults that keep context inheritance and mutations coherent, the language provides the capability for consenting adults to specify their own desired context when scheduling asynchronous tasks and callbacks:

import asyncio<br>import contextvars

async def main():<br>ctx = contextvars.Context()<br>loop = asyncio.get_running_loop()

# A task or a callback can be handed an explicit context to<br># run in, instead of the default copy of the current one...<br>asyncio.create_task(worker(), context=ctx)<br>loop.call_soon(callback, context=ctx)

Herein lies the problem. Imagine a developer decides to schedule a dozen concurrent coroutines using a task group—and they give each one the same context instance:

import asyncio<br>import contextvars

async def main():<br>ctx = contextvars.Context()

# Twelve coroutines, every one handed the same context instance...<br>async with asyncio.TaskGroup() as tg:<br>for n in range(12):<br>tg.create_task(handle(n), context=ctx)

In a vanilla, single-process event loop, the concurrent context var mutations would race and the context state of each task would reflect the latest mutation across the fan-out:

import asyncio<br>import contextvars

var = contextvars.ContextVar("request_id", default="?")

async def handler(request_id, work):<br>token = var.set(request_id) # claim the slot...<br>await asyncio.sleep(work) # ...do some async work...<br>print(f"set {request_id!r}, read back {var.get()!r}")<br>var.reset(token) # ...release it

async def...

context gist contextvars asyncio distributed clone

Related Articles