Datagram design

Linux::Event::Datagram is the packet-preserving network object for UDP and Unix datagram sockets. It does not adapt Stream: a byte queue cannot represent packet boundaries, per-packet destinations, truncation, or source addresses correctly.

Public type model

A concrete subclass defines on_datagram once:

package EchoDatagram;
use parent 'Linux::Event::Datagram';

sub on_datagram ($socket, $payload, $peer) {
    $socket->is_connected
        ? $socket->send("echo:$payload")
        : $socket->send("echo:$payload", to => $peer);
}

sub on_error ($socket, $error) {
    warn "$error\n";
}

Callbacks are cached per subclass. Each object owns its socket, packet queue, backpressure state, addresses, and application data.

Socket forms

Unconnected UDP binds a local address and receives the source Address with each packet:

my $server = $loop->add(EchoDatagram->new(
    host => '0.0.0.0', # required
    port => 9999,      # required; 0 chooses an ephemeral port
));

Connected UDP resolves a hostname asynchronously when necessary, installs a default peer, and filters incoming traffic to that peer:

my $client = $loop->add(EchoDatagram->connect(
    host       => 'collector.example.com', # required
    port       => 9000,                    # required
    local_host => '192.0.2.20',            # optional numeric source address
    local_port => 0,                       # optional source port
));

Unix datagrams use filesystem paths:

my $server = EchoDatagram->new(
    unix            => '/run/example.sock', # required
    unlink          => 0,                   # default
    unlink_on_close => 1,                   # default
    permissions     => 0660,                # optional
);

my $client = EchoDatagram->connect(
    unix            => '/run/example.sock',        # required peer path
    local_unix      => '/run/example-client.sock', # optional reply path
    unlink          => 0,                          # default
    unlink_on_close => 1,                          # default
    permissions     => 0600,                       # optional
);

Closing a connected Unix client may remove only local_unix; it never unlinks the peer's path. detach suppresses all path removal.

An adopted fh must be a datagram socket in the IPv4, IPv6, or Unix family. Datagram sets nonblocking and close-on-exec flags, detects a connected peer, and defaults to caller ownership. owns_socket => 1 transfers close ownership.

Lifecycle

Bound server sockets are created during new and start unattached. Connected sockets are created on Loop attachment so outbound hostname work and failure do not occur in the constructor. States are:

unattached -> resolving -> active -> closed
     |            |          |         ^
     |            +-- error -+-> failed|
     +--------------------------> detached

Literal Internet and Unix connection setup happens during attachment; hostname completion resumes on the Loop thread. on_ready is deferred to a later Loop turn after the socket is active. close is idempotent. detach returns the still-open handle, does not call on_close, and is terminal.

The Loop remains available during terminal on_error and on_close notifications and is released afterward.

Packet input

XS drains recvmsg into one native buffer per readiness dispatch. Each delivered callback receives exactly one payload and one lazy Linux::Event::Address. Zero-length packets are valid.

max_datagram_size defaults to 65,535 bytes. Native input uses MSG_TRUNC to learn the original packet length. An oversized packet is discarded whole and reported as a datagram_size Error; no partial payload is delivered.

max_datagrams_per_tick defaults to 256 for level-triggered fairness. Zero drains until EAGAIN and is required by edge_triggered => 1.

Packet output

One send call represents one packet:

$connected->send('metrics');
$unconnected->send('reply', to => $peer);

The connected form rejects to; the unconnected form requires an Address. Native output uses send or sendto with MSG_DONTWAIT | MSG_NOSIGNAL. Datagram writes are atomic. A packet that would block remains one queued segment and is retried whole.

Soft byte watermarks provide cooperative backpressure. A false return means the packet was accepted and the producer should wait for on_drain. Hard max_pending_bytes and max_pending_datagrams limits reject only the new packet and report output_limit; existing queued packets remain ordered.

Policy and options

datagram_options caches packet limits, fairness, watermarks, and common socket policy once per subclass. Constructor values override the class for one object. Internet-only and Unix-only options are validated against the selected source, including explicit false values. See SOCKET-CONFIGURATION.md for the full matrix and live setters.

Resolver boundary

Connected hostname UDP reuses the Loop's private native resolver service with SOCK_DGRAM hints. Workers copy candidates into C-owned completion values and signal the service eventfd; they never enter Perl. Cancellation removes the recipient, so late getaddrinfo completion is discarded safely.

UDP does not use a TCP-style Happy Eyeballs race because connect does not perform a handshake. Candidates are tried in resolver order until one socket can be configured, locally bound, and connected. A socket-configuration error is terminal; ordinary address or connect failure may advance to another candidate.

Error policy

Queue overflow, packet truncation, and ordinary datagram I/O are reported without automatically closing an active packet socket. Resolver and connection failure make the object terminal. last_error always retains the most recent Error. Without on_error, Datagram warns rather than hiding the failure.