Skip to content

Archive

Concurrency

172 articles
Database 15 Sep 2026 6 min read

PostgreSQL ON CONFLICT Arbitrates Concurrent Inserts

Two concurrent PostgreSQL transactions can attempt to insert the same unique key before either transaction has committed. INSERT ... ON CONFLICT resolves this race through the unique index chosen as the conflict arbiter, rather than by running a separate existence test before the insert. That distinction matters because a prior SELECT cannot reserve the absence of a row under ordinary Read Committed execution. Another transaction can insert the same key after the check. Conflict arbitration places the decision inside the write operation, where PostgreSQL can coordinate with concurrent index activity.

Software Engineering 15 Sep 2026 8 min read

HTTP Preconditions Turn Resource Versions Into Write Guards

A client reads a resource at revision 41, edits one field, and sends the whole representation back. During that interval, another client commits revision 42. If the server accepts the first client’s replacement without testing its source revision, revision 42 can disappear from the visible state even though both requests completed normally. This is the lost-update shape at an HTTP boundary. The notable detail is not simultaneous execution. The requests can arrive seconds apart. The conflict exists because a later mutation was derived from an earlier representation and the server has no condition connecting those two facts.

Software Engineering 15 Sep 2026 7 min read

Generation Counters Reject Stale Async Results

An asynchronous operation can start first and finish last. If every completion writes into the same state slot, completion order becomes state order even when the application intended request order to define authority. This race appears without shared-memory threads. Two network requests, background computations, database queries, or worker messages can overlap through an event loop and return in the opposite order from their initiation. The older result is not necessarily incorrect data. It is stale because a later request has superseded the state transition that originally authorized it.

Software Engineering 15 Sep 2026 7 min read

Fencing Tokens Reject Stale Lock Holders

A process acquires a distributed lease, pauses long enough for that lease to expire, then resumes. Another process has already acquired the same lease. At that moment both processes can execute code that was entered under an apparently valid acquisition, even though only the newer owner should retain authority. The lease itself cannot retract instructions from the paused process. Expiration changes coordination state; it does not erase local state, stop a suspended runtime, or cancel an operation already queued elsewhere. This gap is the central limitation of treating a distributed lock as a remote version of an in-process mutex.

Software Engineering 15 Sep 2026 6 min read

Fencing Tokens Reject Stale Lease Holders

A distributed lease can expire while its holder is paused. The holder may later resume with local state that still says it owns the lease, even though another client has acquired a newer lease. If the protected resource accepts commands solely because a client once acquired ownership, both clients can act during the same logical ownership interval. Fencing tokens move part of the ownership check to the resource receiving the mutation. Each successful lease acquisition receives a token greater than every token issued before it. The protected resource records the greatest token it has accepted and rejects operations carrying an older value.

Database 14 Sep 2026 5 min read

PostgreSQL Serializable Tracks Read-Write Conflicts

PostgreSQL Serializable isolation does not turn every read into a blocking lock. Transactions still execute against MVCC snapshots, while the database tracks read-write dependencies that can make concurrent execution inconsistent with every possible serial order. That distinction matters when an invariant spans multiple rows. Snapshot visibility can give each transaction a stable view and still permit a pair of writes whose combined result could not arise if the transactions had run one after another. Serializable Snapshot Isolation, or SSI, adds conflict detection around that snapshot model.

Software Engineering 13 Sep 2026 9 min read

Write Skew Across Disjoint Rows

Write Skew Across Disjoint Rows Two transactions read the same set of rows, reach compatible decisions, and then update different rows. Neither transaction overwrites the other’s write. Both commits can still leave the database in a state that violates a rule spanning those rows. That shape is write skew. It is easy to miss because many concurrency discussions center on two writers contending for one row. Write skew has no such collision. The conflict exists at the level of an invariant inferred from several records, while the physical writes remain disjoint.

Software Engineering 13 Sep 2026 10 min read

Version Columns Turn Lost Updates Into Conflicts

Two transactions can read the same row, compute different changes, and then write in sequence. If each update replaces values derived from its earlier read, the later write can erase part of the earlier one without either transaction observing a database error. A version column changes that interaction. The row carries a generation value alongside its domain fields, and an update is accepted only when the generation still matches the value observed by the writer. A stale writer no longer looks identical to a current writer at the storage boundary.

Software Engineering 13 Sep 2026 9 min read

Request Coalescing Turns Cache Miss Bursts Into Shared Work

A cache entry expires at a single instant, but requests for that entry do not necessarily arrive one at a time. If twenty callers observe the same miss before any caller has repopulated the cache, a conventional cache-aside path can send twenty equivalent reads to the origin. The cache is functioning according to its contract; the concurrency around the miss is creating duplicated work. Request coalescing changes that boundary. Instead of treating each miss as permission to start an origin operation, callers for the same key can share one in-flight operation. One caller becomes the producer of the pending result. Other callers wait for that result rather than starting equivalent work.

Software Engineering 13 Sep 2026 7 min read

Monotonic Time Belongs in Elapsed-Time Measurement

A timeout can be represented by an ordinary timestamp comparison: record a start value, read the clock later, subtract, and compare the result with a limit. The arithmetic looks complete. The clock model is not. Civil time exists to place events on a shared calendar. It can be corrected to stay aligned with an external time reference. Elapsed-time measurement has a different requirement: later observations within one running system need an ordering suitable for measuring an interval. A clock correction that improves civil-time accuracy can therefore be harmful when the same reading is treated as a stopwatch.

Software Engineering 13 Sep 2026 6 min read

Fencing Tokens Make Expired Leases Observable

A process can hold a distributed lease, pause long enough for that lease to expire, then resume with local state that still says it owns the resource. Another process may already have acquired a newer lease during the pause. At that point, mutual exclusion in the lock service is not enough: two processes can each act as if they have authority, even though only one lease is current. This is a boundary problem between coordination and the resource being protected. A lease service can decide which holder is current according to its own state. It cannot retroactively erase instructions already held by an old process, nor can it stop that process from sending a request after a long pause.

Software Engineering 13 Sep 2026 8 min read

Circuit Breakers Bound Failed Call Admission

A remote call can fail in a few milliseconds or consume its entire timeout budget before returning an error. If callers keep issuing equivalent requests while the dependency remains unable to serve them, each attempt spends resources on an outcome that recent evidence already suggests is unavailable. Retries can increase that pressure because one logical operation may create several physical calls. A circuit breaker changes call admission rather than the remote protocol. It records recent outcomes, moves between explicit states, and can reject new calls locally for a bounded period. After that period, it permits limited probes to test whether normal traffic can resume.

Software Engineering 13 Sep 2026 9 min read

Cancellation Is a Protocol, Not a Thread Kill

A caller can stop waiting for a result while the operation producing that result continues to run. The distinction is easy to miss because many APIs expose cancellation through a single method, token, context, or signal. That surface can look like a command to terminate work. In most cooperative designs, it is closer to a state transition: further work is no longer wanted. The gap matters once an operation owns resources, crosses process boundaries, or has already produced side effects. A cancelled HTTP request does not retroactively erase a committed database transaction. A task that notices a cancellation token between two writes cannot make the first write disappear. A parent that abandons a child computation also needs a rule for who observes the child’s eventual completion and who releases anything the child owns.

Software Engineering 12 Sep 2026 8 min read

Vector Clocks and the Shape of Concurrent State

A single version number can state that one value came after another only when every update participates in the same ordered sequence. Replicated state breaks that assumption as soon as independent writers can accept changes without first agreeing on one global next version. Two replicas can each move forward from the same ancestor. Calling one state version 8 and the other version 9 creates an order, but that order may describe the numbering scheme rather than the causal relation between the writes. Vector clocks represent a different fact: which update history a state has observed.

Go 12 Sep 2026 4 min read

Track Goroutine Lifetimes with sync.WaitGroup.Go in Go

sync.WaitGroup.Go combines goroutine creation with task accounting. Added in Go 1.25, the method removes a small but consequential gap between incrementing a wait-group counter and starting the goroutine that will eventually decrement it. The method does not change what a WaitGroup represents. It still tracks a set of tasks and lets another goroutine block until that set is complete. The difference is that registration and goroutine launch now share one operation.

Software Engineering 12 Sep 2026 8 min read

The ABA Problem Behind Successful Compare-and-Swap

A compare-and-swap operation answers a narrow question: does a memory location contain the expected bit pattern at the instant of the atomic operation? If it does, the replacement can proceed. The operation does not establish that the location remained unchanged between an earlier read and the later comparison. That distinction creates the ABA problem. A thread observes value A, pauses, and later performs a compare-and-swap expecting A. During the pause, other work changes the location from A to B and then back to A. The comparison succeeds because the current representation matches the expected representation, even though the shared state passed through a transition that may invalidate assumptions attached to the first observation.

Go 12 Sep 2026 5 min read

Run Cancellation Callbacks with context.AfterFunc in Go

context.AfterFunc attaches a callback to context cancellation without adding a goroutine that waits only on ctx.Done(). When the context becomes done, the callback starts in its own goroutine. The small API hides a concurrency boundary that matters when the callback mutates shared state, interrupts blocking I/O, or competes with normal completion. The function arrived in Go 1.21 and returns a stop function. That return value is not a general cancellation handle for the callback. It controls the association between the context and the callback, with precise behavior once cancellation and callback startup begin to race.

Software Engineering 12 Sep 2026 10 min read

Request Coalescing at Hot Cache Misses

Request Coalescing at Hot Cache Misses A cache entry expires at 22:00:00.000. Ten milliseconds later, two hundred requests ask for the same key. A cache-aside implementation sees two hundred misses. If every caller independently reads the backing service, a single expiration event becomes two hundred concurrent backend operations. Nothing is wrong with the cache lookup itself. The amplification comes from treating identical in-flight work as unrelated. Request coalescing changes that boundary. Callers that need the same absent key share one active load, while requests for other keys continue independently. The mechanism is small, but its semantics reach beyond a mutex: it defines which operations may share a result, how failures fan out, what cancellation means, and when another load may begin.

Software Engineering 12 Sep 2026 10 min read

Prevent Write Skew with Serializable Transactions

Database transactions make many state changes easier to reason about, but transaction boundaries alone do not guarantee that every business invariant survives concurrency. A particularly subtle failure is write skew: two transactions read overlapping state, update different rows, and both commit even though their combined result violates a rule. This anomaly matters because each transaction can look correct in isolation. The defect appears only when valid decisions are made from snapshots that become incompatible once both writes are accepted.

Go 12 Sep 2026 5 min read

Preserve Cancellation Causes in Go Contexts

A canceled Go context normally reports one of two broad states through ctx.Err(): context.Canceled or context.DeadlineExceeded. That is enough to stop work, but it can discard the event that triggered cancellation. A worker failure, shutdown request, quota rejection, and explicit abort can all collapse into the same context.Canceled value. Go provides cause-aware context functions for cases where the cancellation signal and the diagnostic error need to travel together. context.WithCancelCause creates a derived context whose cancel function accepts an error, while context.Cause retrieves the recorded cause.

Software Engineering 12 Sep 2026 10 min read

Phantom Rows and the Limits of Row-Level Locking

A transaction can lock every row it reads and still leave a business rule exposed. The gap appears when the rule is about a set described by a predicate, not only the rows that currently satisfy it. Suppose an application limits a small allocation group to four active reservations. A transaction queries the active rows, sees three, and decides that one more reservation is valid. If another transaction inserts a new matching row before the first transaction commits, both decisions may have been based on a set that no longer represents the committed state.

Software Engineering 12 Sep 2026 9 min read

Optimistic Concurrency with Version Columns

A row can be read correctly, modified correctly, and still be written incorrectly. The problem appears when another transaction changes the same logical record between the read and the write. A plain UPDATE often has no memory of the state on which the new values were based, so the later writer can replace an earlier change without detecting the race. A version column turns that hidden assumption into a predicate. The update says, in effect, that the write is valid only while the row remains at the version that the caller observed. The database then evaluates the state check and the mutation as one atomic statement.

Software Engineering 12 Sep 2026 9 min read

Fencing Tokens: Block Stale Lease Holders

Fencing Tokens: Block Stale Lease Holders A distributed lease can grant one process temporary permission to act, but expiration alone cannot stop that process from acting after its lease has ended. A long pause, network delay, overloaded runtime, or suspended virtual machine can leave an old holder unaware that another process has already acquired the lease. This creates a subtle safety gap. Two processes can both believe they are entitled to modify the same resource, even when the lease service itself grants ownership correctly.

Software Engineering 12 Sep 2026 8 min read

Fencing Tokens for Expiring Distributed Leases

A lease can expire while its holder is still running. That single property separates a distributed lease from an ordinary in-process mutex. The coordinator may grant ownership to another client after a deadline, yet the former holder can resume after a long pause and continue issuing operations based on authority it no longer has. The coordinator has done its job: it stopped treating the old client as the current holder. The shared resource has a different problem. Unless operations carry evidence of ownership order, the resource may have no basis for distinguishing a current holder from a stale one.