Skip to content

Archive

Concurrency

172 articles
Software Engineering 17 Sep 2026 5 min read

Linux io_uring Separates Submission From Completion Ownership

Linux io_uring Separates Submission From Completion Ownership An io_uring request can remain in flight after the application has finished constructing its submission queue entry. That creates a lifetime boundary absent from a simple synchronous call: request metadata may become stable at submission, while memory used as the actual I/O payload can still be accessed until the operation completes. The completion queue is therefore not only a result channel. For many operations, it marks the point at which application-owned operation state can be reclaimed or reused.

Software Engineering 17 Sep 2026 5 min read

Linux eventfd Makes Counter State Pollable

A Linux eventfd can collapse many notifications into one kernel-maintained counter while still participating in poll(), select(), and epoll. Writers add unsigned 64-bit values to the counter; readiness reports whether that state can be consumed. The interface carries arithmetic state rather than a byte stream or a queue of individual messages. That distinction matters at process and thread boundaries. A wakeup says that the counter is nonzero. It does not preserve the number of write operations, writer identities, or ordering among independent notification sources.

Software Engineering 17 Sep 2026 6 min read

Linux epoll Edge Triggering Reports Readiness Transitions, Not Work Units

Linux epoll Edge Triggering Reports Readiness Transitions, Not Work Units With EPOLLET, an epoll interest does not behave like a queue containing one event for each byte, packet, connection, or application message. It reports changes in readiness state. Once a file descriptor is ready, additional work can accumulate without producing another edge that an application may rely on. The handler therefore has to consume available work until the nonblocking operation reports that progress would block.

Software Engineering 17 Sep 2026 8 min read

io_uring Shared Rings Make Memory Ordering Part of the ABI

An io_uring queue is shared memory with two independent execution domains changing its state. User space prepares submission entries and advances queue metadata; the kernel consumes those submissions and later publishes completion entries. The ring layout removes a copy boundary, but it also makes memory visibility part of the interface contract. A plain source-level assignment to a queue tail is not sufficient as a portable model of publication. The entry data must become visible before the tail value that makes the entry eligible for consumption. On the completion side, user space must observe the kernel’s publication of a completion before reading fields from that completion. The ordering relation is part of correctness, not merely an optimization detail.

Software Engineering 17 Sep 2026 9 min read

Epoll Edge Triggering Turns Readiness Into a Drain Obligation

An edge-triggered epoll registration can stop producing notifications while unread bytes still remain in a socket or pipe. The descriptor is still usable for I/O, but the event loop has already consumed the notification associated with the readiness transition. If the handler reads only part of the available data and returns to epoll_wait(), no fresh transition is required to occur, so the pending bytes can remain untouched indefinitely. This behavior makes EPOLLET more than a notification preference. It changes the contract between the kernel’s ready list and application state. A level-triggered loop can repeatedly receive a descriptor while the requested I/O condition remains true. An edge-triggered loop must preserve enough local state to treat a delivered event as an obligation to exhaust the currently available nonblocking I/O, normally until an operation reports EAGAIN.

Software Engineering 16 Sep 2026 7 min read

PostgreSQL SKIP LOCKED Turns Row Contention Into Visible Omission

A PostgreSQL query using FOR UPDATE SKIP LOCKED can omit a row that satisfies its predicate solely because another transaction already holds a conflicting row lock. The omitted row has not stopped matching the query. It is absent from that execution because lock acquisition would wait. That behavior changes the meaning of a locking read. Ordinary selection asks which visible rows satisfy a predicate. SKIP LOCKED adds an operational condition: among qualifying rows, return only those whose requested locks can be acquired without waiting at the point PostgreSQL attempts to lock them.

Software Engineering 16 Sep 2026 6 min read

PostgreSQL Serializable Reads Track Conflicts Without Blocking Writers

A PostgreSQL transaction at SERIALIZABLE isolation can read a set of rows while a concurrent transaction writes data relevant to that read without the reader taking a blocking row lock. PostgreSQL preserves serializable outcomes by tracking read-write dependencies and rejecting a transaction when the observed dependency structure could admit a serialization anomaly. That mechanism differs from treating every read predicate as a barrier against matching writes. The database keeps MVCC snapshot behavior, adds SIReadLock state for dependency detection, and makes transaction retry part of the isolation contract.

Software Engineering 16 Sep 2026 7 min read

PostgreSQL Sequence Values Survive Transaction Rollback

A PostgreSQL transaction can call nextval, roll back every row change it made, and still leave the allocated sequence value consumed. The row state returns to its earlier transactional form; the sequence allocation does not. This asymmetry is intentional and places sequence generators outside the rollback semantics developers often associate with database writes. That boundary matters whenever a generated identifier is treated as more than an opaque key. A sequence provides concurrent value allocation with atomic nextval calls. It does not provide a gapless ledger, a count of committed rows, or a transactionally reversible numbering stream.

Software Engineering 16 Sep 2026 7 min read

PostgreSQL Savepoint Rollback Releases Later Locks

A PostgreSQL transaction can remain open while a lock acquired during part of that transaction is released. If the lock was acquired after a savepoint and execution rolls back to that savepoint, PostgreSQL releases the lock immediately rather than retaining it until the outer transaction ends. That behavior creates a lock-lifetime boundary inside a transaction. The common rule that transaction locks last until commit or rollback remains useful, but savepoints add a narrower scope for locks acquired after the marked point.

Software Engineering 16 Sep 2026 5 min read

PostgreSQL Foreign Keys Turn Reference Checks Into Row Locks

A PostgreSQL insert into a child table can block a concurrent transaction that tries to delete the referenced parent row, even though the two statements modify different tables. Foreign key enforcement is not only a value lookup. The database must also prevent the referenced key from disappearing before the referencing transaction reaches its boundary. That requirement creates a concurrency relationship between child writes and parent-row changes. The relationship is narrower than a general parent-row write lock: PostgreSQL has a row-lock mode specifically compatible with updates that leave key columns intact.

Software Engineering 16 Sep 2026 6 min read

PostgreSQL Advisory Lock Lifetime Follows Acquisition Scope

A PostgreSQL advisory lock can survive a transaction rollback when it was acquired at session scope. The SQL transaction may have ended with no committed data changes, yet the same database session can continue holding the application-defined lock until an explicit release or session termination. That behavior places lock lifetime at an interface boundary that is easy to blur in pooled applications. Advisory locks have application-defined meaning, but PostgreSQL still gives each acquisition precise server-side scope.

Software Engineering 16 Sep 2026 7 min read

If-Match Turns Stale HTTP Writes Into Precondition Failures

Two clients can read the same HTTP resource, compute different replacements, and send those replacements minutes apart. If the origin accepts both writes without a precondition, the later request can overwrite the earlier result even though it was computed from stale state. HTTP provides a protocol-level guard for this case. A client can retain an entity tag from the representation it read and send that tag in If-Match with a later state-changing request. The origin evaluates the precondition before applying the method. If no listed tag strongly matches the current selected representation, the method is not performed because of that precondition.

Go 16 Sep 2026 5 min read

Go WaitGroup Reuse Requires Completed Wait Boundaries

A sync.WaitGroup can be reused after a wait phase completes, but a new independent task set cannot begin while calls to Wait from the prior phase are still active. The boundary is the return of every earlier Wait, not merely the instant at which the internal task counter reaches zero. This constraint matters when one WaitGroup instance is retained across batches, epochs, request waves, or repeated coordination cycles. Reuse is supported, but phases must not overlap at the zero-to-positive counter transition.

Go 16 Sep 2026 4 min read

Go sync.Cond Wait Rechecks Shared State

sync.Cond.Wait resumes after a notification, but the notification does not assert that a caller-specific condition is still true when the goroutine reacquires the lock. The shared predicate remains the source of truth, so a waiter checks it again after every return from Wait. This boundary separates notification from state. Signal and Broadcast announce that relevant state may have changed; they do not transfer ownership of that state or reserve it for a particular waiter.

Go 16 Sep 2026 5 min read

Go singleflight Shares Only In-Flight Results

singleflight.Group suppresses duplicate function executions only while an operation for the same key is in flight. Concurrent callers can receive one shared result, but a caller arriving after completion starts a new execution. The group is therefore a request-coalescing mechanism, not a result cache. This boundary affects cache fills, metadata refreshes, backend reads, and other keyed operations that can attract bursts of identical concurrent work. A group can reduce simultaneous pressure on the backing operation without extending the lifetime of its returned value.

Go 16 Sep 2026 5 min read

Go Nil Channels Disable Select Cases

A send or receive on a nil Go channel can never proceed. Inside a select, that property removes the associated communication case from the set of cases eligible to run, without requiring a separate condition around the select. This behavior follows directly from the channel contract. The zero value of a channel is nil, and a nil channel is never ready for communication. A standalone send or receive therefore blocks indefinitely. A select treats the same operation as a case that cannot currently proceed.

Go 16 Sep 2026 6 min read

Go errgroup SetLimit Blocks Submitters at the Concurrency Cap

errgroup.Group.SetLimit can block the goroutine that calls Group.Go. The limit is enforced before a new worker goroutine starts, so a full group applies backpressure at task submission rather than building an internal queue. That behavior matters when submission is part of another control path. A loop that appears to launch work asynchronously can itself stop at g.Go(...) until one active function returns. The limit sits on admission A zero-value errgroup.Group has no concurrency limit. After SetLimit(n), at most n functions started by Go are active at once. A negative limit restores unlimited admission, while a zero limit prevents every later Go call from starting a function.

Go 16 Sep 2026 3 min read

Go context.AfterFunc Stop Does Not Wait for Callback Completion

The stop function returned by Go’s context.AfterFunc does not wait for a callback that has already started. A false result therefore marks a state boundary, not a completion barrier: the callback may be running concurrently when stop returns. context.AfterFunc(ctx, f) associates f with cancellation of ctx. Cancellation starts f in its own goroutine. If the context is already canceled at registration time, the callback is started promptly in a new goroutine rather than being invoked synchronously by the caller.

Go 16 Sep 2026 4 min read

Go Context Cancellation Cause Follows the First Cancellation

A Go context records its cancellation cause when cancellation first reaches that context. Later cancellation attempts do not replace the recorded cause. This makes context.Cause a record of the winning cancellation event rather than a mutable error slot. The distinction matters in context trees because parent and child cancellation can race. The first event to cancel a given node fixes that node’s cause, while another node in the same tree can retain a different cause.

Linux 16 Sep 2026 5 min read

epoll Edge-Triggered Readiness Requires Draining

An edge-triggered epoll consumer can block while unread data is still buffered. The failure appears when an event is consumed, only part of the available input is read, and the event loop returns to epoll_wait() expecting another notification for the bytes that remain. This behavior follows directly from EPOLLET. Edge-triggered notification reports changes in readiness rather than continuously reporting a ready condition. Once a readiness transition has produced an event, leaving the file descriptor ready does not itself create a new transition.

Software Engineering 15 Sep 2026 7 min read

Write Skew Escapes Row-Level Conflict Detection

Two concurrent transactions can each read a valid database state, update different rows, and both commit without colliding on a written row. The final state can still violate a constraint that spans those rows. No lost update is required; each transaction can preserve every value written by the other and still produce an invalid result. This anomaly is commonly called write skew. Its defining feature is that the conflict lives in the relationship among values rather than in two writes aimed at the same row. Isolation mechanisms that detect direct write-write conflicts therefore do not automatically protect every application invariant.

Software Engineering 15 Sep 2026 7 min read

Version Columns Turn Lost Updates Into Explicit Conflicts

Two clients can read the same database row, derive different changes, and then write in sequence. If each update replaces values without checking the state that produced its decision, the later write can silently erase part or all of the earlier one. The database has serialized the statements, yet the application-level read-modify-write operation has still lost a concurrent change. A version column changes the admission rule for the write. The update is accepted only if the row still carries the version observed by the client. A competing update advances that version, so a stale writer affects zero rows instead of overwriting newer state.

Software Engineering 15 Sep 2026 8 min read

Task Scopes Bind Child Lifetimes to Parent Operations

An asynchronous function can return while work it started is still running. Once that happens, the caller no longer has a lexical boundary that states when the spawned work finishes, where its failure is observed, or which operation owns its cancellation. Structured concurrency changes that lifetime relation. Child tasks belong to an enclosing scope, and the scope does not complete until its children reach a terminal state according to the runtime’s task-group semantics. The central property is not parallel execution. It is that task lifetime follows program structure.

Software Engineering 15 Sep 2026 7 min read

Request Coalescing Turns Concurrent Cache Misses Into Shared Work

A cache entry can expire while hundreds of requests for the same key are already in flight. If every request observes the miss independently, each can start the same backend operation before any result reaches the cache. The cache still limits work across time, but it does not limit duplicate work during that miss interval. Request coalescing adds a second boundary: concurrent operations for the same logical key can share one in-flight computation. One caller becomes the active producer, while matching callers wait for that producer’s result instead of starting equivalent work. The mechanism is also called single-flight suppression in systems that expose it as a concurrency primitive.