Compare reduced-copy and buffered transfers.
A conventional file server reads a region into an application buffer, then sends that buffer to a socket. The loop is straightforward, but it asks the application to coordinate two operations and moves the payload across the kernel boundary twice. For large or frequent transfers, those copies, syscalls, and buffer passes consume memory bandwidth and calling-task CPU without changing the bytes.
Send_Chunk keeps the same synchronous Ada control flow while selecting a lane-appropriate data path. The operation is positional and does not disturb the file descriptor's current position.
- Read then send
- The kernel supplies file data to an application buffer; the application submits that buffer to the socket. This is the portable baseline and remains the fallback.
- Native task
- The host
sendfileoperation passes the file region directly to the socket path. The application manages progress and deadlines, but does not receive the payload bytes. - Lightweight task
- File input remains completion-driven so the event-loop pthread is not blocked. On Linux,
SEND_ZCcan reduce copying on the socket side; Flyology waits for the kernel's release notification before allowing the caller to reuse the scratch storage. Unsupported kernels use the ordinary socket-send fallback.
This is most useful for unchanged regular-file regions sent over plain stream sockets: static responses, downloads, cached artifacts, backup streams, replication, and media segments. The benefit usually becomes clearer as transfer size or frequency grows.
Use a normal buffered pipeline when the application must inspect or transform the bytes. TLS framing, compression, checksums over transformed data, protocol encoding, and dynamic content all require work above this primitive. Small transfers may also be faster through the simpler fallback, so measure on the target host.
Use an explicit positional region.
Send_Chunk starts at an explicit file offset and does not change the descriptor position. It returns after sending the available chunk, reaching end of file, or raising an exception. Advance the offset by Sent and call again until the requested region is complete.
File- An open
File_Descriptorfor a regular file that permits reads. Socket- An open, connected plain stream
Socket_Type. The operation does not encode a protocol or pass data through a TLS provider. OffsetandCount- A
File_Offsetidentifies the first byte, and a transferByte_Countsets the maximum progress. A zero count returns zero immediately; zero progress with a positive count means end of file. Scratch- One acquired
Unique_Buffer. It supplies completion-driven file storage in the lightweight lane and the portable fallback path. Timeout- One monotonic budget spanning file access, socket progress, and terminal buffer release within this call.
Use the same loop in either lane.
The reusable buffer determines the largest completion-driven chunk. Create storage with Buffers.Pool, then call Buffers.Acquire before the first transfer. The example gives each chunk one five-second budget. For one budget across the complete region, compute a monotonic deadline before the loop and pass its remaining duration to each call.
with Flyology.Buffers;
with Flyology.IO.Files;
with Flyology.IO.Files.Transfers;
with Flyology.IO.Sockets;
procedure Send_File_Region
(File : Flyology.IO.Files.File_Descriptor;
Socket : Flyology.IO.Sockets.Socket_Type;
First : Flyology.IO.Files.File_Offset;
Count : Flyology.IO.Files.Transfers.Byte_Count)
is
package Buffers renames Flyology.Buffers;
package Files renames Flyology.IO.Files;
package Transfers renames Flyology.IO.Files.Transfers;
use type Files.File_Offset;
use type Transfers.Byte_Count;
Storage : aliased Buffers.Pool
(Block_Size => 16 * 1_024 * 1_024, Capacity => 1);
Scratch : Buffers.Unique_Buffer (Storage'Access);
Offset : Files.File_Offset := First;
Remaining : Transfers.Byte_Count := Count;
Sent : Transfers.Byte_Count;
begin
Buffers.Acquire (Scratch);
while Remaining > 0 loop
Transfers.Send_Chunk
(File, Socket, Offset, Remaining, Scratch, Sent,
Timeout => 5.0);
exit when Sent = 0; -- end of file
Offset := Offset + Files.File_Offset (Sent);
Remaining := Remaining - Sent;
end loop;
end Send_File_Region;
Both the file and socket must remain open for the call. The caller serializes their lifetime and must not modify the transferred file region concurrently.
Identify the kernel path for each lane.
- Native task
- Darwin and Linux use the host
sendfileoperation. The call may block only that task's pthread. Retry, deadline, cancellation, and partial-progress policy remain in Ada. - Linux lightweight task
- The file read remains on the execution group's completion engine. When the io_uring probe admits
SEND_ZC, the socket send uses it and retains the buffer until the notification CQE releases kernel ownership. - Linux fallback
- If io_uring or
SEND_ZCis unavailable, Flyology uses the completion-driven file read followed by the ordinary socket-send path. - Darwin lightweight task
- POSIX AIO and
EVFILT_AIOprovide completion-driven file input, followed by the ordinary socket-send path. Flyology does not invoke potentially faultingsendfilework on an event-loop pthread.
Keep the scratch buffer uniquely owned.
The caller retains the Unique_Buffer handle throughout the operation. On a lightweight completion path, submitted bytes are kernel-owned until terminal completion. Send_Chunk does not return or raise while the kernel may still read that storage, so the same scratch buffer can be reused immediately afterward. The native host path does not submit Scratch.
Pre-cancellation raises Operation_Cancelled before submission. In a lightweight task, cancellation after submission first resolves terminal buffer ownership and then raises. In a native task, cancellation is checked before host sendfile attempts and can interrupt socket-readiness waits, but it cannot preempt a host syscall that is already executing.
Each normal return reports the bytes accepted by one socket send. Positive completion wins over cancellation observed in the same completion because replay would duplicate bytes. A timeout or cancellation can still race with irreversible socket progress that an exceptional return cannot expose through Sent. Do not retry the same region when duplicate bytes are unsafe.
Stop : aliased Flyology.Cancellation.Token;
Transfers.Send_Chunk
(File, Socket, Offset, Remaining, Scratch, Sent,
Timeout => 2.0,
Token => Stop'Access);
Code coordinating shutdown can share a Flyology.Cancellation.Token and call Stop.Request from another task. The token must outlive every operation that borrows it.
Measure on the target host.
The maintained showcase compares Send_Chunk with an optimized Read_At plus Send_All loop. Both paths reuse one 16 MiB buffer and transfer cached file data over TCP loopback at 1, 16, and 64 MiB. Warmups validate the payload; timed samples alternate method order and report median throughput, median calling-thread CPU efficiency, and median paired speedups. On Linux it also reports the file backend and whether SEND_ZC usage reporting observed a kernel copy fallback.
./showcases/run_file_transfer_benchmark.sh
The runner labels a reduced-copy crossover only when the median paired speedup reaches 1.05×. Re-run it on the target kernel before using the result for a deployment decision.
Know what Send_Chunk does not provide.
- The source must be a regular file and the destination a connected stream socket.
- The operation is positional and does not update the file descriptor's current position.
Send_Chunkdoes not perform TLS framing, HTTP range handling, content-length generation, or application-level retries.- Concurrent close, descriptor reuse, or mutation of the file region remains a caller synchronization error.
- For a native task, the deadline governs retries and readiness waits but cannot preempt a host
sendfilesyscall that is already executing. OpenandCloseremain direct metadata syscalls in both lanes.- Use the exact exception and parameter contracts in the generated API reference.