WASI TCP sockets must behave as-if they are implemented using the state machine described in this document.
Note: These refer to the states of the TCP socket, not the TCP connection
In pseudo code:
interface tcp {
variant state {
unbound,
bind-in-progress(bind-future),
bound,
listen-in-progress(listen-future),
listening(accept-stream),
connect-in-progress(connect-future),
connected,
closed,
}
type bind-future = future<result<_, error-code>>;
type listen-future = future<result<_, error-code>>;
type connect-future = future<result<tuple<input-stream, output-stream>, error-code>>;
type accept-stream = stream<result<tuple<tcp-socket, input-stream, output-stream>, error-code>>;
}
As seen above, there can be at most one asynchronous operation in progress at any time. The socket's pollable ready state can therefore be unambiguously derived as follows:
fn ready() -> bool {
match state {
unbound => true,
bound => true,
connected => true, // To poll for I/O readiness, subscribe to the input and output streams.
closed => true,
// Assuming that `f.is-resolved` returns true when the
// future has completed, either successfully or with failure.
bind-in-progress(f) => f.is-resolved,
listen-in-progress(f) => f.is-resolved,
connect-in-progress(f) => f.is-resolved,
// Assuming that `s.has-pending-items` returns true when
// there is an item ready to be read from the stream.
listening(s) => s.has-pending-items,
}
}
The following diagram describes the exhaustive set of all possible state transitions:
stateDiagram-v2
state "unbound" as Unbound
state "bind-in-progress" as BindInProgress
state "bound" as Bound
state "listen-in-progress" as ListenInProgress
state "listening" as Listening
state "connect-in-progress" as ConnectInProgress
state "connected" as Connected
state "closed" as Closed
[*] --> Unbound: create-tcp-socket()\n#ok
Unbound --> BindInProgress: start-bind()\n#ok
Unbound --> Unbound: start-bind()\n#error
Unbound --> ConnectInProgress: start-connect()\n#ok
Unbound --> Closed: start-connect()\n#error
ConnectInProgress --> ConnectInProgress: finish-connect()\n#error(would-block)
ConnectInProgress --> Closed: finish-connect()\n#error(NOT would-block)
ConnectInProgress --> Connected: finish-connect()\n#ok
Connected --> Connected: shutdown()
Connected --> Closed: «connection terminated»
BindInProgress --> BindInProgress: finish-bind()\n#error(would-block)
BindInProgress --> Unbound: finish-bind()\n#error(NOT would-block)
BindInProgress --> Bound: finish-bind()\n#ok
Bound --> ConnectInProgress: start-connect()\n#ok
Bound --> Closed: start-connect()\n#error
Bound --> ListenInProgress: start-listen()\n#ok
Bound --> Closed: start-listen()\n#error
ListenInProgress --> ListenInProgress: finish-listen()\n#error(would-block)
ListenInProgress --> Closed: finish-listen()\n#error(NOT would-block)
ListenInProgress --> Listening: finish-listen()\n#ok
Listening --> Listening: accept()
Most transitions are dependent on the result of the method. Legend:
#ok
: this transition only applies when the method returns successfully.#error
: this transition only applies when the method returns a failure.#error(would-block)
: this transition only applies when the method returns thewould-block
error specifically.#error(NOT would-block)
: this transition only applies when the method returns an error other thanwould-block
.- (no annotation): Transition in unconditional.
- All state transitions shown above are driven by the caller and occur synchronously during the method invocations. There's one exception: the
«connection terminated»
transition fromconnected
toclosed
. This can happen when: the peer closed the connection, a network failure occurred, the connection timed out, etc. - While
shutdown
immediately closes the input and/or output streams associated with the socket, it does not affect the socket's own state as it just initiates a shutdown. Only after the full shutdown sequence has been completed will the«connection terminated»
transition be activated. (See previous item) - Calling a method from the wrong state returns
error(invalid-state)
and does not affect the state of the socket. A special case are thefinish-*
methods; those returnerror(not-in-progress)
when the socket is not in the corresponding*-in-progress
state. - This diagram only includes the methods that impact the socket's state. For an overview of all methods and their required states, see tcp.wit
- Client sockets returned by
accept()
are in immediately in theconnected
state. - A socket resource can be dropped in any state.