trait ServerLifecycleEventReceiver[TCP: TCPBackend ref = RuntimeBackend]
"""
Application-level callbacks for server-side TCP connections.
One receiver per connection, no chaining.
"""
fun ref _connection(): TCPConnection[TCP]
fun ref _on_started() =>
"""
Called when a server connection is ready for application data.
"""
None
fun ref _on_closed() =>
"""
Called when the connection is closed.
"""
None
fun ref _on_received(data: Array[U8] iso): ReadAction =>
"""
Called each time data is received on this connection.
Return `KeepReading` to let the read loop take the next message, or
`YieldReading` to stop after this one and give other actors a turn.
"""
KeepReading
fun ref _on_throttled() =>
"""
Called when we start experiencing backpressure.
The sends that completed in the write that hit backpressure are reported
first, so their `_on_sent` arrives before this. A `hard_close()` from one
of those callbacks means this one never fires at all: `_on_closed` has
arrived and the connection is no longer throttled. A graceful `close()`
leaves the queued writes to drain, so this still fires.
"""
None
fun ref _on_unthrottled() =>
"""
Called when backpressure is released.
"""
None
fun ref _on_send_accepted(token: SendToken, data: (ByteSeq | ByteSeqIter)) =>
"""
Called once for each `send()` the connection accepts, from inside that
`send()` and before the bytes are written. `token` identifies the send and
comes back exactly once more, to `_on_sent` or `_on_send_failed`. `data`
is what was passed to `send()`.
A refused `send()` does not fire this. It returns a `SendError` instead.
Closing the connection from here does not un-accept the send: `send()`
still returns `SendAccepted`, and a hard close gives the token to
`_on_send_failed`.
Sending from here queues the second send behind the first before either is
written. Nothing is written between one level of that nesting and the next,
so unlike the nesting `_on_sent` allows, it has no point at which the
socket can refuse and stop it. The flush at the innermost level can also
deliver `_on_sent` for the token this call is still delivering.
"""
None
fun ref _on_sent(token: SendToken) =>
"""
Called when the bytes from an accepted `send()` have been handed to the
OS: written to the kernel send buffer, not necessarily received by the
peer. The token matches the one `_on_send_accepted` delivered for that
send, and this callback fires exactly once for it. Callbacks arrive in
send order.
Fires during the write that hands the bytes over, which can be the write
inside the `send()` call that queued them. So anything the calling code
updates after that `send()` -- a counter, a map, a flag -- is not updated
yet when this fires.
Calling `send()` from here nests on the stack: an application that sends
its next message on each completion recurses for as long as the sends keep
draining, and a peer that keeps up sets no bound on the depth. Send from a
behavior instead to break the nesting.
A `hard_close()` from here suppresses the `_on_throttled` that backpressure
was about to deliver.
"""
None
fun ref _on_send_failed(token: SendToken) =>
"""
Called when the bytes from an accepted `send()` could not be handed to
the OS because the connection was lost or hard-closed first. The token
matches the one `_on_send_accepted` delivered for that send, and this
callback fires exactly once for it. A graceful `close()` sends what's still
queued, so only a hard close or a lost connection fires this. On a hard
close, the sends that reached the OS fire `_on_sent` and the rest fire
this, so the split shows how far your data got.
Always fires in a subsequent behavior turn, never synchronously during
`hard_close()`. Always arrives after `_on_closed`, which fires
synchronously during `hard_close()`.
"""
None
fun ref _on_start_failure(reason: StartFailureReason)
"""
Called when a server connection fails to start. This covers failures
that occur before _on_started would have fired, such as an SSL
handshake failure. The application was never notified of the connection
via _on_started.
The `reason` parameter identifies the cause of the failure.
`StartFailedSSL` indicates SSL session creation or handshake failure.
"""
fun ref _on_tls_ready() =>
"""
Called when a TLS handshake initiated by `start_tls()` completes
successfully. The connection is now encrypted and ready for
application data over TLS.
"""
None
fun ref _on_tls_failure(reason: TLSFailureReason) =>
"""
Called when a TLS handshake initiated by `start_tls()` fails. Fires
synchronously during `hard_close()`, immediately before `_on_closed()`.
The connection was already established (the application received
`_on_started` earlier), so `_on_closed` always follows to signal
connection teardown.
The `reason` parameter distinguishes authentication failures
(`TLSAuthFailed`) from other protocol errors (`TLSGeneralError`).
"""
None
fun ref _on_idle_timeout() =>
"""
Called when no successful send or receive has occurred for the duration
configured by `idle_timeout()`. This measures application-level inactivity,
not wire-level: pending OS write buffer drains and failed sends
(`SendErrorNotWriteable`) do not count as activity.
The timer re-arms after each firing while the connection is open. Call
`idle_timeout(None)` to disable it; `hard_close()` cancels it. A graceful
`close()` does not, so this can still arrive on a closing connection that is
moving bytes. The application decides what action to take — close the
connection, send a keepalive, log a warning, etc.
If the idle timer's ASIO event subscription fails,
`_on_idle_timer_failure()` is delivered instead of this callback.
"""
None
fun ref _on_idle_timer_failure() =>
"""
Called when the idle timer's ASIO event subscription fails. This is
typically caused by the kernel returning an error (e.g. `ENOMEM`) from
`kevent` or `epoll_ctl` when the runtime tries to register the timer.
Before this callback fires, the idle timer has already been cancelled:
the ASIO event is unsubscribed and the configured timeout duration is
cleared. Idle timeout detection is no longer active on this connection.
The connection itself is unaffected and continues running. The
application decides how to recover — for example, call
`idle_timeout(duration)` from within this callback to re-arm, or
`close()` the connection. A re-armed timer can itself fail
asynchronously under sustained pressure; if the new subscription also
errors, this callback fires again.
"""
None
fun ref _on_timer(token: TimerToken) =>
"""
Called when a one-shot timer created by `set_timer()` fires. The token
matches the one returned by `set_timer()`.
Fires once per `set_timer()` call. The timer is consumed before the
callback, so it is safe to call `set_timer()` from within `_on_timer()`
to re-arm. No automatic re-arming occurs.
If the user timer's ASIO event subscription fails, `_on_timer_failure()`
is delivered instead of this callback.
"""
None
fun ref _on_timer_failure() =>
"""
Called when the user timer's ASIO event subscription fails. This is
typically caused by the kernel returning an error (e.g. `ENOMEM`) from
`kevent` or `epoll_ctl` when the runtime tries to register the timer.
User timers have two error paths:
- Synchronous: `set_timer()` returns a `SetTimerError`
(`SetTimerNotOpen` or `SetTimerAlreadyActive`) when preconditions
prevent the timer from being created at all.
- Asynchronous: this callback fires when `set_timer()` succeeded but
the ASIO event subscription later failed.
Before this callback fires, the user timer has already been cancelled:
the ASIO event is unsubscribed and the timer token is cleared. The
token that the application was waiting on is no longer valid.
The connection itself is unaffected and continues running. The
application decides how to recover — for example, call
`set_timer(duration)` from within this callback to create a new timer,
or `close()` the connection. A new timer can itself fail
asynchronously under sustained pressure; if the new subscription also
errors, this callback fires again.
"""
None