Skip to content

Archive

Concurrency

172 articles
Go 12 Sep 2026 4 min read

Detach Go Context Cancellation with context.WithoutCancel

A Go context usually ties a unit of work to the lifetime of its parent. Cancel an HTTP request context, and derived contexts observe that cancellation. This propagation is the normal contract, but some follow-up operations need a different lifetime while still carrying request-scoped values. Go 1.21 added context.WithoutCancel for that boundary. It returns a context that can resolve values through its parent but does not inherit the parent’s cancellation state or deadline.

Software Engineering 12 Sep 2026 7 min read

Deadline Propagation as a Request Boundary

Deadline Propagation as a Request Boundary A service can return after its caller has stopped waiting. The computation may still consume a connection, hold a concurrency slot, execute a database query, or start another remote call. A local timeout limits how long one caller waits; it does not, by itself, bound the lifetime of work already sent deeper into the system. An end-to-end deadline changes that boundary. Instead of giving each operation an independent duration, the request carries a point in time after which its result is no longer useful to the initiating operation. Each component can derive its remaining budget from that same boundary.

Software Engineering 12 Sep 2026 8 min read

Conditional HTTP Writes with Entity Tags

Conditional HTTP Writes with Entity Tags A client reads a resource, edits its local copy, and sends a replacement several seconds later. During that interval another client may have committed a different replacement. A plain PUT has no statement about the representation on which the edit was based, so the server can accept a request whose starting state is already obsolete. HTTP conditional requests can carry that missing premise. A response entity tag identifies a selected representation, and If-Match makes a later request conditional on a current representation matching one of the supplied tags. For state-changing methods, that turns representation identity into an explicit concurrency boundary.

Software Engineering 12 Sep 2026 10 min read

Cache Stampede Control with Early Refresh

Cache Stampede Control with Early Refresh A cache can remove enormous amounts of repeated work, yet a popular entry creates a sharp risk at expiry. If ten thousand requests depend on the same key and that key expires, many requests can discover the miss at nearly the same moment. Each request may then start the same database query, computation, or remote call. This event is commonly called a cache stampede. The cache works well during the entry lifetime, then abruptly stops protecting the dependency exactly when demand is high.

Go 12 Sep 2026 5 min read

Cache Concurrent Initialization Results with sync.OnceValue in Go

sync.OnceValue turns a function into a concurrency-safe, one-time computation whose result is returned on every call. The first caller performs the computation. Concurrent callers wait for that call to finish, and later callers receive the stored result without running the function again. This differs from using sync.Once with a separate result variable. The value and its one-time initialization are packaged behind a function, which makes the lifetime of the cached result explicit in the place where that function is stored.

Software Engineering 12 Sep 2026 10 min read

Backpressure: Match Producer Speed to Consumer Capacity

Backpressure: Match Producer Speed to Consumer Capacity A pipeline is stable only when work enters at a rate its downstream stages can sustain. That sounds obvious, yet many systems let producers run at full speed until a queue fills, memory grows, latency explodes, or a downstream service starts rejecting requests. The visible failure appears late. The actual mismatch began earlier: one stage could create work faster than the next stage could finish it.

Software Engineering 11 Sep 2026 9 min read

Fencing Tokens: Stop Stale Lock Holders from Writing

Fencing Tokens: Stop Stale Lock Holders from Writing A distributed lock can tell a client that it owns a resource for a limited period. That does not guarantee the client stops acting when the period ends. A process can pause for garbage collection, lose network access, become descheduled, or stall on an overloaded machine. During that pause, its lease can expire and another client can acquire the same lock. When the first process resumes, it may still believe it is entitled to write.

Software Engineering 11 Sep 2026 13 min read

Concurrency Limits: Bound In-Flight Work

Concurrency Limits: Bound In-Flight Work A service can receive traffic at an acceptable average rate and still collapse because too many operations overlap. The issue is not only how many requests arrive per second. It is also how many requests are active at the same time. A concurrency limit places a cap on active work. When all slots are occupied, additional work must wait, fail fast, or take another explicit path. This simple control can protect database connections, CPU-heavy routines, remote dependencies, worker capacity, and memory that grows with each active operation.

Software Engineering 11 Sep 2026 9 min read

Cache-Aside Consistency: Prevent Stale Overwrites

Cache-Aside Consistency: Prevent Stale Overwrites Cache-aside is attractive because the application controls a simple protocol. A read checks the cache first. On a miss, it reads the database and places the result in the cache. A write updates the database and then invalidates or refreshes the cache. Each step is easy to describe. Concurrency makes the combined behavior less obvious. A delayed cache fill can publish an older database value after a newer write has already completed. The database remains correct, yet later readers can receive stale data from the cache. This article develops the race precisely and presents practical designs that keep an old fill from replacing a newer state.

Go 10 Sep 2026 7 min read

Test Concurrent Go Code with testing/synctest

Tests for concurrent Go code often become timing tests by accident. A goroutine starts, the test sleeps for 20 milliseconds, then checks whether something happened. That can pass thousands of times and still fail on a loaded CI runner because the sleep never proved the goroutine reached the state you cared about. Go’s testing/synctest package gives these tests a better model. It runs code inside an isolated bubble where time is virtualized, and synctest.Wait can synchronize the test with goroutines in that bubble. The result is especially useful for code built around timers, context deadlines, retries, and background goroutines.

Python 09 Sep 2026 11 min read

Stop Stuck Process Pools with Python 3.14

ProcessPoolExecutor is a convenient way to spread CPU-bound Python work across multiple processes. Most of the time, its normal shutdown behavior is exactly what an application wants: stop accepting work, let running tasks finish, and clean up worker processes. Some failures do not fit that model. A worker can become stuck in native code, wait indefinitely on an external resource, or execute a task whose runtime has exceeded the application’s operational deadline. At that point, cancelling a Future may not stop work that is already running, and waiting for an orderly pool shutdown may take too long.

Python 09 Sep 2026 9 min read

Shut Down asyncio Worker Queues Cleanly

Asynchronous worker pools often start with a simple pattern: producers put jobs into an asyncio.Queue, consumers loop over get(), and the application waits for join() before exiting. The awkward part is shutdown. Older designs commonly put one sentinel value into the queue for each worker, cancel consumers after join(), or maintain a separate stop event. Each approach can work, but each adds a second protocol beside the queue itself. Python 3.13 added asyncio.Queue.shutdown() and the asyncio.QueueShutDown exception. They let the queue represent its own lifecycle: open for producers, shutting down while existing work drains, and finally closed to consumers.

Python 09 Sep 2026 10 min read

Run CPU-Bound Python with InterpreterPoolExecutor

For CPU-heavy Python work, I usually reach for ProcessPoolExecutor. Threads are convenient, but ordinary CPython threads do not give CPU-bound Python code the kind of multi-core parallelism people often expect. Python 3.14 adds another option: concurrent.futures.InterpreterPoolExecutor. It looks deliberately familiar. You still submit callables and receive futures, but every worker thread owns a separate Python interpreter. Each interpreter has its own GIL, so Python code in different workers can execute on different CPU cores at the same time.

Python 09 Sep 2026 10 min read

Reload Process Environment Variables with Python 3.14

Python applications often treat environment variables as if every read goes straight to the operating system. In CPython, that mental model is incomplete. os.environ is a mapping captured when the os module is first imported, normally during interpreter startup. If the process environment later changes through something outside that mapping, Python’s cached view can become stale. Python 3.14 adds os.reload_environ() for the unusual cases where an application really needs to refresh that view.

Python 09 Sep 2026 11 min read

Manage Subinterpreters Directly with Python 3.14

Python 3.14 gives application code a new way to work directly with multiple interpreters in one process. The concurrent.interpreters module exposes a high-level API for creating interpreters, running code inside them, and communicating through cross-interpreter queues. It sits below InterpreterPoolExecutor: instead of submitting independent jobs to a ready-made pool, you own the interpreter lifecycle and decide how work reaches each isolated execution context. That extra control is useful, but it also removes several conveniences an executor normally provides. A subinterpreter is not a lightweight thread with shared globals, and creating one does not automatically create concurrency.

Python 09 Sep 2026 13 min read

Make Warning Tests Concurrency-Safe with Context-Aware Warnings

Python’s warnings.catch_warnings() is convenient in tests, compatibility shims, and small diagnostic scopes. It lets code temporarily change warning filters and then restore the previous state. That model becomes harder to reason about when several threads or asynchronous tasks use it at the same time. Historically, catch_warnings() manipulated process-global state in the warnings module. Two overlapping contexts could therefore interfere with each other. Python 3.14 adds an opt-in context-aware mode that changes this behavior. When sys.flags.context_aware_warnings is true, catch_warnings() stores its filtering state in a context variable instead of mutating the same global warning state for every concurrent execution path.

Python 09 Sep 2026 10 min read

Control asyncio Task Startup with eager_start

Creating an asyncio task usually feels like a clean scheduling boundary: call asyncio.create_task(), keep the returned task, and let the event loop run the coroutine soon. Python also supports eager task execution, where a coroutine can begin running immediately during task creation. Python 3.14 makes that choice directly available through the eager_start keyword on asyncio.create_task() and through task-group task creation. That can remove scheduling overhead for coroutines that often complete without blocking. It can also change program ordering in ways that matter much more than the performance gain.

Software Engineering 09 Sep 2026 10 min read

Coalescing Duplicate In-Flight Work

A service can receive many requests for the same expensive result at almost the same time. If every request starts identical work, a brief traffic burst can become a much larger burst against a database, remote API, filesystem, or CPU-heavy computation. Caching can help after a result exists. It does not necessarily help when the result is missing and many callers discover that miss together. Request coalescing solves this narrower problem. While an operation for a particular key is already running, later callers for the same key join that operation instead of starting another one. When it finishes, the waiting callers receive the same outcome.

Go 08 Sep 2026 9 min read

Use Go Timers Correctly After Go 1.23

Timer code in Go has accumulated a surprising amount of folklore. Older examples warn that time.After leaks resources, insist that every stopped timer channel must be drained, and wrap Timer.Reset in careful stop-and-drain sequences. Those rules were important for older Go programs. They are not all current rules. Go 1.23 changed the implementation and guarantees of channel-based timers. Unreferenced timers can now be garbage collected before they fire, and timer channels use synchronous semantics that prevent stale values after Stop or Reset returns. The result is simpler timer code—but only when the program is actually using the new semantics.

Python 08 Sep 2026 11 min read

Run CPU-Bound Python Work with InterpreterPoolExecutor

Python has traditionally offered two familiar high-level choices for parallel work: threads and processes. Python 3.14 adds a third option to concurrent.futures: InterpreterPoolExecutor. It runs workers in separate Python interpreters inside one process. Each worker has its own interpreter state and its own Global Interpreter Lock (GIL), so pure Python code can execute on multiple CPU cores at the same time. That makes the executor interesting for CPU-bound workloads, but it is not a drop-in way to make arbitrary threaded code parallel. Interpreter isolation changes the programming model. Mutable Python objects are not simply shared between workers, submitted work crosses a serialization boundary, imports and module globals are interpreter-local, and extension compatibility deserves deliberate testing.

Go 08 Sep 2026 7 min read

Run Cleanup After Context Cancellation with context.AfterFunc

Cancellation often means more than telling a goroutine to stop. A blocked operation may need to be interrupted, a temporary resource may need cleanup, or some state may need to be released as soon as a request deadline expires. A common approach is to start another goroutine that waits on ctx.Done(). That works, but it adds lifecycle code every time you need cancellation-triggered behavior. Since Go 1.21, the standard context package provides context.AfterFunc for this job.

Python 08 Sep 2026 6 min read

Keep Request State Local with Python contextvars

Passing a request ID through every function is explicit, but after a few layers it can become noise. Logging is the example I keep running into: the logger needs the request ID, while most business functions do not actually care about it. A global variable looks tempting until two requests run concurrently. threading.local() fixes a different problem, but one event-loop thread can execute many asyncio tasks. Python’s contextvars module is designed for this kind of context-local state.

Go 08 Sep 2026 7 min read

Detach Go Work Safely with context.WithoutCancel

Request cancellation is usually exactly what a Go service wants. When a client disconnects or a request deadline expires, database calls, HTTP requests, and other downstream work should normally stop too. Sometimes one small piece of work has a different lifetime. A handler may need to enqueue an audit record, finish a bounded cache update, or send a best-effort notification after the request itself is no longer alive. Passing the request context directly makes that work inherit cancellation. Replacing it with context.Background() avoids cancellation, but also throws away useful request-scoped values.

Python 08 Sep 2026 7 min read

Budget Async Work with asyncio.timeout

Timeouts in asynchronous programs are easy to scatter and surprisingly hard to compose. A service call gets five seconds, a database query gets five more, and a retry gets another five. Each individual limit looks reasonable, yet the whole request can run far beyond the caller’s budget. Python 3.11 added asyncio.timeout(), an asynchronous context manager that makes a different model practical: put a time budget around a block of work, not just around one awaitable.