Skip to content

Archive

I/O

37 articles
Tech 16 Sep 2026 5 min read

PCIe Relaxed Ordering Lets Transactions Pass Within Ordering Rules

PCIe Relaxed Ordering Lets Transactions Pass Within Ordering Rules PCI Express carries requests and completions through switches, bridges, and endpoint logic that can have several transactions in flight at once. Strict ordering between every packet would make many independent transfers wait behind traffic that has no dependency on them. Relaxed Ordering provides a protocol signal that permits more reordering where the requester can tolerate it. The feature does not remove all ordering constraints. It marks a transaction as eligible for additional movement relative to other traffic, while PCIe ordering rules still define which combinations may pass. Software and device logic must only use the attribute when reordering cannot expose stale state or break a producer-consumer dependency.

Linux 16 Sep 2026 5 min read

io_uring Registered Files Bypass Repeated Descriptor Lookup

An io_uring request that uses a normal file descriptor still has to resolve that descriptor through the submitting task’s file table. A registered file takes a different path: the ring holds a reference to the open file, and an SQE names a slot in that ring-local table. That distinction removes repeated descriptor lookup from the request path. It also changes resource lifetime, update semantics, and the meaning of the SQE fd field.

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.

Go 07 Sep 2026 12 min read

Read Large Line-Oriented Input Safely in Go with bufio.Scanner

Line-oriented input looks simple until one record is much larger than expected. A program may process thousands of ordinary log lines correctly, then stop on a generated stack trace, a large JSON record, or a malformed input that contains no newline for megabytes. Go’s bufio.Scanner is convenient for this job because it handles tokenization and defaults to scanning lines. But that convenience comes with an important boundary: a scanner has a maximum token size. If a token cannot fit within that limit, scanning stops with an error.

Go 07 Sep 2026 11 min read

Read File Regions Safely in Go with io.ReaderAt

Many file formats are not consumed strictly from beginning to end. An index may point to records at known offsets. A binary container may keep metadata in a header and payloads elsewhere. A server may need to serve several independent byte ranges from the same open file. The obvious approach is to call Seek, then Read. That works when one goroutine owns the file position. It becomes fragile when several operations share the same file because the current offset is mutable state.

Go 07 Sep 2026 14 min read

Observe Streaming Reads in Go with io.TeeReader

Streaming code often needs to do two things with the same bytes. A service may need to parse a response while computing its checksum. A file importer may want to decode records while recording exactly what the decoder consumed. A diagnostic tool may want to inspect a stream without first loading the entire input into memory. A common but wasteful approach is to read everything into a byte slice and then run each operation over that copy. That is simple for small inputs, but it removes the main advantage of streaming: work can begin before the whole input has arrived, and memory usage does not have to grow with the full input size.

Go 07 Sep 2026 8 min read

Handle Buffered Write Errors Correctly in Go

Buffering output can reduce write overhead, but it also changes when an I/O failure becomes visible. With an unbuffered writer, a call to Write normally reaches the underlying destination immediately. With bufio.Writer, a successful call may only mean that the bytes were accepted into memory. The actual write to a file, socket, pipe, or other destination can happen later, often during Flush. That distinction creates a common production bug: code checks every apparent write, defers Flush, and still returns success after the final flush fails.

Go 07 Sep 2026 12 min read

Compose Sequential Streams in Go with io.MultiReader

Programs often need to present several pieces of data as one input stream. A request body may need a generated header followed by a file. A test may need a prefix, a fixture, and a suffix. A protocol adapter may need to expose several existing readers through an API that accepts only one io.Reader. The obvious solution is to read every piece into memory, concatenate the byte slices, and create a new reader over the result. That is reasonable for small, already-buffered data. It is a poor fit when one source is large, slow, or naturally streaming because the consumer cannot start until the combined buffer has been built.

Go 06 Sep 2026 9 min read

Handle Long Lines Safely in Go with bufio.Scanner

bufio.Scanner is one of the simplest ways to process line-oriented input in Go. It is a good fit for log files, command output, newline-delimited JSON, and other formats where one logical record fits in memory. The convenience has an important boundary: a Scanner will stop if the next token grows beyond the amount of buffering it is allowed to use. That default protects a program from growing memory without bound, but it can surprise code that works on small test files and later encounters one unusually long line in production.

Linux 05 Sep 2026 10 min read

Move Data Between Linux File Descriptors with splice()

A conventional file-copy loop reads bytes into a user-space buffer and then writes those bytes somewhere else. That pattern is portable and easy to understand, but sometimes the program does not need to inspect or transform the data at all. It only needs to move bytes from one file descriptor to another. On Linux, splice() can handle that case differently. It transfers data between file descriptors while keeping the transferred data out of a user-space buffer. At least one endpoint must be a pipe, so a pipe can act as the kernel-side bridge between a source and a destination.

Go 05 Sep 2026 8 min read

Bound Untrusted Stream Reads in Go with io.LimitReader

Reading an entire io.Reader is convenient, but convenience can become a resource problem when the reader is not fully under your control. A request body, uploaded file, decompressed stream, subprocess output, or protocol payload may be much larger than expected. Calling io.ReadAll directly asks Go to keep reading until EOF, growing memory as needed. The important mental model is that a size limit should sit in front of the consumer. Instead of trusting every caller to stop at the right point, wrap the source in a reader that exposes only a bounded prefix. Go’s io.LimitReader does exactly that.

Python 04 Sep 2026 9 min read

Avoid Subprocess Pipe Deadlocks in Python

Launching a command from Python is easy. Capturing its output is also easy. The trouble starts when a parent process waits for a child while the child is waiting for the parent to read from a pipe. That circular wait is a deadlock: neither process can make progress even though neither has crashed. This problem is especially confusing because the same code may work during testing and hang only when a command produces more output. Small output fits in an operating-system pipe buffer. Larger output can fill that buffer and expose the incorrect coordination.

Go 02 Sep 2026 7 min read

Short Reads and Exact-Length I/O in Go

Reading bytes in Go looks simple: allocate a buffer, call Read, and inspect the error. The subtlety is that io.Reader does not promise to fill the buffer in one call. A valid reader may return fewer bytes than requested even when more data will arrive later. That behavior matters for network protocols, binary file formats, framed messages, and any code that expects an exact number of bytes. Correct stream handling starts by matching the API to the requirement: use ordinary Read when partial progress is acceptable, and use helpers such as io.ReadFull when a fixed-size field must be complete.