Releases and changelog
Tagged releases of go-ublk, what changed, and the versioning policy.
On this page
go-ublk is pre-1.0. Releases are git tags on GitHub; go get github.com/ehrlich-b/[email protected] pins one, and @main follows development.
Versioning#
Until 1.0 the public API can change between minor versions. Code written against the previous minor version keeps compiling where that costs little (fields that no longer do anything are kept as documented no-ops for a release); behavior that was wrong is fixed, not preserved. Each release notes what changed. Bug fixes that affect data integrity or teardown are called out explicitly, because they are the reason to upgrade.
How releases are made#
Every tag passes the same gates, and the evidence ships with it:
make release-check— formatting,go vet, unit tests, the race detector, and a longer fuzzing pass over the UAPI decoders and the queue engine.- The kernel matrix —
ublk-suitebooted under every kernel intest/matrix: mainline 6.0–7.3-rc and the current kernels of the major distributions. A release requires every test to pass on every kernel that has ublk, except failures traced to a documented kernel bug. The results for the tagged commit are committed assite/data/matrix.jsonand published on the compatibility matrix. - Recovery under systemd — the shipped units, a mounted filesystem, a verifying writer, a crash and an upgrade handoff, zero I/O errors.
- The tag is cut. Pushing it runs the CI checks again on the tagged commit and publishes the GitHub release, whose body is this page’s section for that version (
scripts/release-notes.sh vX.Y.Z); a tag without a changelog section fails.
Bug fixes that affect data integrity or teardown are listed first in a release’s notes.
v0.2.0 (2026-10-04)#
A rebuild of everything under the public API, aimed at production use: a new I/O engine, user recovery, the whole kernel control surface, and a conformance suite that runs under dozens of kernels. Code written for v0.1.0 compiles unchanged; the behavior changes below are the ones to read.
Behavior changes
- Backends run concurrently within a queue. Each request runs on its own goroutine, so up to
QueueDepthcalls per queue are in flight (v0.1.0 called the backend one request at a time per queue). Backends already had to be safe for concurrent use across queues; now latency-bound backends scale.DeviceParams.Inlinerestores the old inline behavior for RAM-speed backends. - Cancelling the serving context stops the device gracefully (like
Stop). In v0.1.0 it killed the queues under in-flight I/O, which could wedge the kernel’s control plane (Critical Bug #22, deterministic on Ubuntu 7.0.0-38). StartafterStopreturnsErrStopped. The kernel cannot reliably restart a stopped device; it oopsed Arch’s 7.2.8 and wedged 6.10–6.12 in testing (#23).- The library is silent by default. With no
Options.Loggernothing is written; v0.1.0 logged at INFO to stderr. - Errnos pass through. A backend error that is a
syscall.Errnoreaches the application as that errno (on kernels that translate them) instead of alwaysEIO. A panicking backend fails the request withEIOinstead of crashing the server. Stop/Closekeep serving ifSTOP_DEVfails and return the error (#18); the wait isOptions.StopTimeout(default one minute) instead of a fixed 10 s.- Missing kernel features fail creation with an error listing them, instead of being silently dropped.
EnableUserCopyandEnableZeroCopywork (v0.1.0 accepted both but produced devices that could not serve I/O);EnableFUAworks with aFUABackendorHandler;EnableIoctlEncodeandDeviceNameare documented no-ops.
New
- User recovery:
DeviceParams.Recovery(RecoveryReissue,RecoveryQueue,RecoveryFailIO),Device.Detach(drains withQUIESCE_DEVon 6.16+, except on batch devices),ublk.Recover, andDeviceParams.TagwithFindDevices.Recovertakes the device’s geometry and modes from the kernel — zero copy, batch I/O, zoned parameters and the integrity format — so the new process only supplies a backend. The conformance suite SIGKILLs a server mid-write and recovers it from another process, and hands a device off live between processes, in the default, batch and integrity configurations; the writer sees no error and every block verifies. It also checks thatRecoveryQueueholds I/O until recovery and thatRecoveryFailIOfails it at once. Under systemd with ext4 mounted and a verifying fio running, crash and upgrade handoffs complete in about 1–2.5 s with zero I/O errors.ublk-loop -recoveryand its shipped systemd units (Restart=always, SIGUSR2 upgrade, and a mount unit ordered so that the filesystem unmounts after every login session and before the server stops) demonstrate it. - Zero copy (
EnableZeroCopywith aZeroCopyBackend, kernel 6.15+): file-backed devices are served entirely in the kernel with io_uring fixed-buffer reads and writes,fdatasync, andfallocate, using automatic buffer registration on 6.16+. - Shared-memory zero copy (
SharedMemoryZeroCopywithRegisterSharedMemory, kernel 7.1+) and larger descriptors (IODescSize, kernel 7.3+). With these, every feature in the 7.3-rc5 UAPI is implemented. - Batch I/O (
BatchIO, kernel 7.0+): requests fetched up to 128 per completion through a multishot command and a provided-buffer ring, and committed many per command; works in copy, user-copy and zero-copy modes. - Integrity metadata (
DeviceParams.Integrity, kernel 7.0+): per-block metadata with optional T10-DIF, IP or NVMe CRC64 protection that the kernel generates and verifies; served throughRequest.Integrityor anIntegrityBackend. The suite checks that a corrupted tuple fails exactly that block’s read. - Zoned devices (
EnableZoned, kernel 6.6+): host-managed zoned devices served by aHandler, withRequest.ReportZonesandCompleteZoneAppend. HandlerandRequest: a raw request interface exposing every operation and flag, completable from any goroutine;HandlerDiscard/HandlerWriteZeroes.Device.Done,Err,Waitand afailedstate: a queue that hits something unexpected now fails the device visibly instead of dying silently (#20).- Unprivileged devices (
EnableUnprivileged): the control path prefixes every command with the device’s char path and waits for udev to hand the node to its owner;examples/ublk-chownand its udev rule are the udev side. The suite serves verified I/O asnobody. Device.Resize(UPDATE_SIZE, 6.16+),SafeStop(TRY_STOP_DEV, 7.0+),NoPartitionScan(7.0+),ThreadsPerQueue(PER_IO_DAEMON, 6.16+),NeedGetData, geometry hints (PhysicalBlockSize,IOMinSize,IOOptSize,DMAAlignment).- Queue threads are pinned by default to the CPUs the kernel routes to each queue (
GET_QUEUE_AFFINITY);ListDevicesreads sysfs, so device IDs above 63 are found;DeleteDeviceAsync(DEL_DEV_ASYNC, 6.11+). ublk.Probe,GetDeviceInfo,Device.KernelInfo,Device.Features,ublk.Errno,FUABackend.- Testing and releases:
ublk-suite(make suite), a real-kernel conformance suite of about 60 tests;test/matrix, which boots it under mainline and distribution kernels;make release-check; a release workflow; and this documentation site. - Internally: a general allocation-free io_uring core, an I/O engine with a fake-kernel test suite (including a deterministic lost-wakeup test proven to fail without its fix), every control command of the 7.3-rc5 UAPI with C-fixture layout parity, and off-heap control buffers.
Fixed
- Write-zeroes of 4 GiB or more silently zeroed only part of the range on kernels before 6.11 (#24): v0.1.0 advertised a write-zeroes limit the pre-6.11 block layer truncates to 32 bits, so
blkdiscard -z -l 5Gsucceeded and left 4 GiB of old data (seen on 6.4–6.10, Ubuntu’s 6.8 and openSUSE Leap 15.6). The discard and write-zeroes limits are now capped so every kernel splits the range. - The ring leak (#17): every io_uring the library created stayed mapped until exit — four per device lifecycle, one per
ListDevices. - Use-after-unmap at teardown (#19): memory is freed only after every queue has exited and no backend call holds a request; otherwise it is deliberately leaked.
- Unpinned control buffers (#21): some control replies were written into goroutine stacks that could move.
- Discards and write-zeroes of 2 GiB or more failed with
EIO(#16): range operations now complete with 0. - The examples handle SIGHUP and ignore SIGPIPE (the likely trigger of the shutdown wedge in #15).
Verification
- Kernel matrix: on the release candidate (
4d9712f; later commits change only documentation and CI), the unit tests, the large-I/O test, the integrity sweep and the full conformance suite passed on all 47 kernels in the compatibility matrix that shipublk_drv: mainline 6.4 through 7.3-rc3; Ubuntu 22.04 HWE, 24.04 (GA, HWE 6.11, 6.14, 6.17 and 7.0, plus the AWS, Azure and GCP 7.0 kernels), 25.04, 25.10 and 26.04; Debian 12 backports and 13; Fedora 42, 43 and 44; Arch and Arch LTS; openSUSE Tumbleweed and Leap 15.6; Oracle UEK8; and CentOS Stream, AlmaLinux and Rocky 10 with io_uring enabled. There were no product failures. The only kernel-log findings are known kernel bugs. make release-check: formatting,go vet, unit and race tests, and 60 seconds of each fuzz target.- Recovery under systemd: the shipped units with ext4 mounted and a verifying fio running; crash and upgrade handoffs recovered in 1–2 s with zero I/O errors.
- Reboots under load: eight reboots with the shipped units, ext4 mounted and about 300 MB of dirty data: no I/O errors, clean
e2fsckeach time. (The same test found the mount unit’s missingBefore=user.slice, which lost writeback in 3 of 5 reboots.) - Soak: four hours through the shipped units on Ubuntu 7.0.0-38: 23 crash and upgrade handoffs, every block verified, no memory or file-descriptor growth, clean
e2fsck.
v0.1.0 (2026-09-30)#
The first tag. Highlights, roughly in the order they landed:
Correctness
- Multi-queue devices map each queue’s descriptor array at the kernel’s fixed stride. Before this, every queue after the first read queue 0’s descriptors, causing silent data corruption and unkillable hangs on multi-queue devices.
- Sectors are counted in 512-byte units everywhere, so devices with 4096-byte logical blocks report the right capacity and do I/O at the right offsets. Block sizes from 512 bytes to the page size are accepted and validated against the kernel’s rules.
- A discard larger than the request buffer no longer panics the server; only reads and writes use request buffers.
- Device attributes (
ReadOnly,Rotational,VolatileCache) and discard limits are actually sent to the kernel; before, flush and discard were unreachable. VolatileCachedefaults to true, the fail-safe choice for durability.- Backend transfer results are honored: short reads and writes fail the request.
- Per-tag request buffers are sized from the negotiated
MaxIOSize(default 1 MiB), so full-size requests work.
Lifecycle
- Graceful stop of a busy device:
STOP_DEVruns while the queues still serve, and the control-plane wait is bounded and robust to signal interruptions. 50+ teardown-under-load cycles without a hang or leak. DEL_DEVno longer hangs: queue goroutines are joined and every char-device reference released before deletion.- Failed startup tears down cleanly instead of wedging the process; the queue count the kernel returns is honored.
ListDevicesandDeleteDevicein the public API for reaping leaked devices.WriteZeroesBackendwired toUBLK_IO_OP_WRITE_ZEROES; the unusedSyncBackend,StatBackendandResizeBackendinterfaces removed.Options.Loggerreceives the library’s internal diagnostics;Options.Debugadded.
Examples and testing
ublk-loop, a losetup-style file exporter, and a compressed mode forublk-mem, both on the public API only.- Integrity sweep, teardown churn, crash and power-fail oracles, the shutdown-storm test, and verified runs on arm64 and x86_64 with kernels 6.17 and 7.0. See Testing and compatibility.