Async observations
io_uring and Linux AIO do their work after the submitting call has returned.
Antinomie records these requests as async observations attached to the
submitting call, so you can see what was asked, what the kernel accepted and what
completed.
Stages
Section titled “Stages”| Stage | Meaning |
|---|---|
| Submission | The request descriptor was observed. This is not proof it was accepted. |
| Accepted | The kernel accepted it for processing. |
| Rejected | Submission failed; the result explains why. |
| Completion | The operation finished. Multishot and partial completions are not final. |
| Canceled | The request ended with -ECANCELED. |
| Observation unavailable | The observer retired the request without seeing a completion. The error describes the observation, not the operation. |
| Reaped | For an AIO read, the data was read when the program collected the result |
What is captured
Section titled “What is captured”- Request descriptors and typed operands for many opcodes, such as a socket
address for
BIND, descriptors forFILES_UPDATE, a futex word or asiginfoforWAITID. - Read and write payloads, when they can be read safely. A request run by an
io_uringworker thread or theSQPOLLthread in the same process is read. Buffers registered with the ring are read within their lifetime. - For
O_DIRECTAIO reads that complete in an interrupt, the data is read when the program reaps the result (stage Reaped), including multi-segment vectored reads.
A submission by the SQPOLL thread, or by a thread outside the ring creator’s
group, is attributed to the ring creator and flagged as having an unknown
producer, because the shared ring does not say which thread wrote the entry.
Previewing and browsing
Section titled “Previewing and browsing”A call row previews its first 256 observations. The Async section of the
decoded call says how many were saved and how many are not shown, for example
Async (1-256 of 4,000 saved). Browse all saved observations pages through
the rest from the archive without reading the whole call, with Previous page,
Next page and Back to preview. Triage reports the calls it only
previewed.
This keeps heavy io_uring runs usable: thousands of completions on one syscall
no longer lose their entry, exit or task-end facts.
File requests in Triage
Section titled “File requests in Triage”Triage reads each io_uring or AIO request that works on a file as the system call it stands for, from the submission entry, the paths and data the guest recorded and the request’s result. The same Files tabs, file lifecycles and detection events then cover programs that do their file work this way:
| Requests | Read as |
|---|---|
io_uring OPENAT, OPENAT2 |
openat, openat2 |
io_uring READ*, WRITE* |
reads and writes, including the vectored and fixed-buffer forms |
io_uring FTRUNCATE, FALLOCATE, CLOSE, STATX |
the matching calls |
io_uring RENAMEAT, LINKAT, SYMLINKAT, UNLINKAT, MKDIRAT |
the matching calls |
io_uring FSYNC, SYNC_FILE_RANGE, SPLICE, FSETXATTR, SETXATTR, FGETXATTR, GETXATTR |
the matching calls (since 0.7.1) |
AIO pread, pwrite, preadv, pwritev |
the matching calls |
The Operation column says where a row came from, such as openat (io_uring), and the file.via filter field is syscall, io_uring or AIO.
A vectored read shows all the data it read, segment after segment.
A request on a registered descriptor carries no descriptor number. On a base
built for Antinomie 0.7.0 or later the guest records the identity of the file each
request used, and Triage ties the request to its file by that identity. An open
straight into the ring’s registered table shows its descriptor as
registered. With an older base, such requests are known by path only.
Triage reads up to 100,000 file requests per scan, including those past the
call’s preview, and its coverage line says what was not read. Since 0.7.1 the
file requests an SQPOLL ring’s kernel thread submitted are read too,
attributed to the ring’s creator, which has no call of its own for them. Not
mapped: requests that touch no file (sockets, timeouts, polls) and TEE. A request that never completed has no
result and is shown as a call with no exit. A file written through an io_uring
worker is not hashed.
How a run that uses io_uring ends
Section titled “How a run that uses io_uring ends”io_uring worker threads belong to the program. On a base built for Antinomie 0.7.0 or later they count as part of the run, and whichever thread leaves last records how the program ended, so the run gets its own exit status, with or without Core Dump on Exit.
A run is Finished (or Stopped) when its process tree has ended with no
request outstanding. An io_uring ring that a forked child or another process
also maps, or an SQPOLL ring, does not prove which task wrote an entry, so such
a run also reports incomplete observation. Since 0.7.1, a ring that only one
process maps is attributed to that process and the run is not flagged (with a
base built for 0.7.1 or later). That is a
note beside the run state, not a different state; see
Recording incomplete. With an older base
an io_uring run can end with its Root exit shown as Unknown, never as
exit 0.
Limits and loss
Section titled “Limits and loss”- At most 256 requests are tracked at once, and 1,024 records can wait to be published. Beyond that, new requests or records are refused, counted as async observation loss for the run, and the run is marked as having incomplete observation.
- A pending AIO read is dropped, and counted as loss, if the reap table (64 entries per boot) is full or the process exits first.
- Async records are published by one worker that waits for room in the CPU’s buffer, up to 10 ms per record, then publishes anyway.
- Completion is not guaranteed to be observed. Accepted is not completion, and Observation unavailable is not failure.
Async loss appears in Summary under Runs with async observation loss (this boot). See Recording incomplete. The decoded call’s fields and stages are described in Syscall Trace.