# dista.socket

> A module of Dista's API.

IPv4 TCP and process control over libc.

Just enough of `socket(2)` and friends for dista's processes: listeners on
loopback or on every interface, streams to a host name or to loopback, the
non-blocking reads and writes the head's event loop uses, `poll`, and
`fork`/`waitpid`/`_exit` to start and reap local processes. macOS and Linux
differ in `sockaddr_in`'s first byte, in `addrinfo`'s field order, in the
`SOL_SOCKET`, `MSG_DONTWAIT` and `EAGAIN` constants, and in how a write to a
closed peer is kept from raising SIGPIPE; `platform_map` picks the right value
for each.

## Structs

### `Stream`

```mojo
struct Stream
```

A connected TCP socket. It's non-blocking, but `write_all` and `read_into` wait for it, so to its users it blocks; the head's loop uses `send_some` and `recv_some`, which don't. Closes itself when dropped.

Implements: `Movable`

Fields:

- `fd: c_int`

#### `__init__`

```mojo
def __init__(out self, fd: Int32)
```

#### `__deinit__`

```mojo
def __deinit__(deinit self)
```

#### `connect`

```mojo
def connect(port: Int, timeout_ms: Int = Int(-1)) -> Self
```

Connects to 127.0.0.1:`port`, waiting at most `timeout_ms` (-1: as long as the kernel tries).

```mojo
def connect(host: String, port: Int, timeout_ms: Int = Int(-1)) -> Self
```

Connects to `host`:`port`; `host` is a name or a dotted quad. Waits at most `timeout_ms` (-1: as long as the kernel tries).

```mojo
def connect(ip: Array[UInt8, Int(4)], port: Int, timeout_ms: Int = Int(-1)) -> Self
```

Connects to `ip`:`port`, waiting at most `timeout_ms` (-1: as long as the kernel tries).

#### `start_connect`

```mojo
def start_connect(ip: Array[UInt8, Int(4)], port: Int) -> Self
```

Starts connecting to `ip`:`port` and returns at once. The socket is writable once the connect completes or fails; `finish_connect` then says which. An event loop connects this way, so a peer that never answers doesn't stall it.

#### `finish_connect`

```mojo
def finish_connect(mut self)
```

Raises if the connect `start_connect` began failed. Call it once the socket polls writable (or failed).

#### `local_ip`

```mojo
def local_ip(self) -> Array[UInt8, Int(4)]
```

The local IPv4 address this connection goes out from.

#### `write_all`

```mojo
def write_all(self, data: Span[UInt8])
```

Writes every byte of `data`.

#### `read_into`

```mojo
def read_into(self, buf: Pointer[UInt8], size: Int) -> Int
```

Reads exactly `size` bytes into `buf`.

Returns `size`, or 0 when the peer closed before sending anything.
Raises if the peer closes partway through.

#### `close`

```mojo
def close(mut self)
```

### `Listener`

```mojo
struct Listener
```

A TCP listener. Closes itself when dropped.

Implements: `Movable`

Fields:

- `fd: c_int`
- `port: Int`

#### `__init__`

```mojo
def __init__(out self, port: Int = Int(0), any_interface: Bool = False, backlog: Int = Int(128))
```

Binds and starts listening.

Args:

- `port`: The port, or 0 for one the OS picks.
- `any_interface`: Listen on every interface instead of loopback.
- `backlog`: How many connections may wait to be accepted.

#### `__deinit__`

```mojo
def __deinit__(deinit self)
```

#### `accept`

```mojo
def accept(self) -> Stream
```

Waits for the next connection.

#### `close`

```mojo
def close(mut self)
```

## Functions

### `resolve_ipv4`

```mojo
def resolve_ipv4(host: String) -> Array[UInt8, Int(4)]
```

The first IPv4 address of `host`, a name or a dotted quad.

### `send_some`

```mojo
def send_some(fd: Int32, data: Pointer[UInt8], size: Int) -> Int
```

Sends what the socket takes without blocking. Returns the bytes sent.

### `recv_some`

```mojo
def recv_some(fd: Int32, buf: Pointer[UInt8], size: Int) -> Int
```

Reads what has arrived, without blocking.

Returns the bytes read, 0 at end of stream, or -1 if nothing is waiting.

### `fcntl`

```mojo
def fcntl(fd: Int32, cmd: Int32, arg: Int32) -> c_int
```

`fcntl(2)` with one int argument: the only declaration of it in dista, since two with different signatures don't link.

### `set_nonblocking`

```mojo
def set_nonblocking(fd: Int32) -> Bool
```

Makes `fd` non-blocking; whether that worked.

### `keepalive_seconds`

```mojo
def keepalive_seconds() -> Int
```

`DISTA_KEEPALIVE_SECONDS`: how long a connection may be silent before the kernel probes it; 0 turns keepalive off. Default 10.

### `set_keepalive`

```mojo
def set_keepalive(fd: Int32)
```

Has the kernel notice a peer that went silent without closing: a host that froze, lost its network or was hard-stopped, which no TCP close reports. After `keepalive_seconds()` of silence it probes every half of that, and after `KEEPALIVE_PROBES` unanswered probes the socket fails, about 25 s by default; `poll` then reports it as an error, and dista drops it as it does a closed one.

Probes go only while nothing sent is unacknowledged; with data in flight
the kernel's retransmission limit applies instead (about 15 minutes on
Linux). `TCP_USER_TIMEOUT` would shorten that, but it also fails a
connection whose peer is alive and only not reading, as a driver busy
between `get`s, or one the head holds back for admission, is.
`DISTA_TASK_TIMEOUT` bounds a task however its worker stalls.

### `get_int_option`

```mojo
def get_int_option(fd: Int32, level: Int32, name: Int32) -> Int
```

A socket option's int value, or -1 if it can't be read.

### `socket_error`

```mojo
def socket_error(fd: Int32) -> c_int
```

The error pending on `fd` (`SO_ERROR`): how a non-blocking connect ended, 0 if it succeeded.

### `wall_ns`

```mojo
def wall_ns() -> UInt64
```

Nanoseconds since the epoch, from CLOCK_REALTIME.

The timeline stamps tasks on the head and on workers with this, so the
stamps compare across processes and, NTP permitting, machines; a
monotonic clock only compares within one host.

### `stream_pair`

```mojo
def stream_pair() -> List[Stream]
```

Two connected local stream sockets, for tests and fuzzing.

### `poll`

```mojo
def poll(fds: List[Int32], events: List[Int16], timeout_ms: Int = Int(-1)) -> List[Int16]
```

Waits for any of `events[i]` on `fds[i]`, or until `timeout_ms` passes.

Returns each descriptor's `revents`. A timeout of -1 waits indefinitely.

### `poll_readable`

```mojo
def poll_readable(fds: List[Int32], timeout_ms: Int = Int(-1)) -> List[Bool]
```

Waits until at least one of `fds` is readable, or `timeout_ms` passes.

A closed or failed socket counts as readable, so the read that follows
reports it. A timeout of -1 waits indefinitely.

### `fork`

```mojo
def fork() -> Int
```

Forks the process. Returns 0 in the child, the child's pid in the parent.

### `wait_child`

```mojo
def wait_child(pid: Int) -> Int
```

Waits for child `pid` to exit and returns its raw wait status.

### `exit_now`

```mojo
def exit_now(code: Int)
```

Ends the process immediately, without running atexit handlers.

A forked worker leaves through here, so it never returns into the driver's
`main` or flushes the driver's buffered output a second time.

### `addr_key`

```mojo
def addr_key(ip: Array[UInt8, Int(4)], port: Int) -> String
```

## Aliases

### `AF_INET`

```mojo
comptime AF_INET = Int32(2)
```

### `SOCK_STREAM`

```mojo
comptime SOCK_STREAM = Int32(1)
```

### `SOL_SOCKET`

```mojo
comptime SOL_SOCKET = SIMD(platform_map[Optional(String("SOL_SOCKET")), linux=Optional(Int(1)), macos=Optional(Int(65535))]())
```

### `SO_REUSEADDR`

```mojo
comptime SO_REUSEADDR = SIMD(platform_map[Optional(String("SO_REUSEADDR")), linux=Optional(Int(2)), macos=Optional(Int(4))]())
```

### `SO_NOSIGPIPE`

```mojo
comptime SO_NOSIGPIPE = SIMD(platform_map[Optional(String("SO_NOSIGPIPE")), linux=Optional(Int(0)), macos=Optional(Int(4130))]())
```

### `MSG_NOSIGNAL`

```mojo
comptime MSG_NOSIGNAL = SIMD(platform_map[Optional(String("MSG_NOSIGNAL")), linux=Optional(Int(16384)), macos=Optional(Int(0))]())
```

### `EINTR`

```mojo
comptime EINTR = Int32(4)
```

### `IPPROTO_TCP`

```mojo
comptime IPPROTO_TCP = Int32(6)
```

### `TCP_NODELAY`

```mojo
comptime TCP_NODELAY = Int32(1)
```

### `SO_KEEPALIVE`

```mojo
comptime SO_KEEPALIVE = SIMD(platform_map[Optional(String("SO_KEEPALIVE")), linux=Optional(Int(9)), macos=Optional(Int(8))]())
```

### `TCP_KEEPIDLE`

```mojo
comptime TCP_KEEPIDLE = SIMD(platform_map[Optional(String("TCP_KEEPIDLE")), linux=Optional(Int(4)), macos=Optional(Int(16))]())
```

### `TCP_KEEPINTVL`

```mojo
comptime TCP_KEEPINTVL = SIMD(platform_map[Optional(String("TCP_KEEPINTVL")), linux=Optional(Int(5)), macos=Optional(Int(257))]())
```

### `TCP_KEEPCNT`

```mojo
comptime TCP_KEEPCNT = SIMD(platform_map[Optional(String("TCP_KEEPCNT")), linux=Optional(Int(6)), macos=Optional(Int(258))]())
```

### `KEEPALIVE_PROBES`

```mojo
comptime KEEPALIVE_PROBES = 3
```

### `SOCKADDR_IN_SIZE`

```mojo
comptime SOCKADDR_IN_SIZE = 16
```

### `EAGAIN`

```mojo
comptime EAGAIN = SIMD(platform_map[Optional(String("EAGAIN")), linux=Optional(Int(11)), macos=Optional(Int(35))]())
```

### `EINPROGRESS`

```mojo
comptime EINPROGRESS = SIMD(platform_map[Optional(String("EINPROGRESS")), linux=Optional(Int(115)), macos=Optional(Int(36))]())
```

### `SO_ERROR`

```mojo
comptime SO_ERROR = SIMD(platform_map[Optional(String("SO_ERROR")), linux=Optional(Int(4)), macos=Optional(Int(4103))]())
```

### `F_GETFL`

```mojo
comptime F_GETFL = Int32(3)
```

### `F_SETFL`

```mojo
comptime F_SETFL = Int32(4)
```

### `O_NONBLOCK`

```mojo
comptime O_NONBLOCK = SIMD(platform_map[Optional(String("O_NONBLOCK")), linux=Optional(Int(2048)), macos=Optional(Int(4))]())
```

### `MSG_DONTWAIT`

```mojo
comptime MSG_DONTWAIT = SIMD(platform_map[Optional(String("MSG_DONTWAIT")), linux=Optional(Int(64)), macos=Optional(Int(128))]())
```

### `ADDRINFO_SIZE`

```mojo
comptime ADDRINFO_SIZE = 48
```

### `ADDRINFO_FAMILY_OFF`

```mojo
comptime ADDRINFO_FAMILY_OFF = 4
```

### `ADDRINFO_SOCKTYPE_OFF`

```mojo
comptime ADDRINFO_SOCKTYPE_OFF = 8
```

### `ADDRINFO_ADDR_OFF`

```mojo
comptime ADDRINFO_ADDR_OFF = platform_map[Optional(String("AI_ADDR_OFF")), linux=Optional(Int(24)), macos=Optional(Int(32))]()
```

### `POLLIN`

```mojo
comptime POLLIN = Int16(1)
```

### `POLLOUT`

```mojo
comptime POLLOUT = Int16(4)
```

### `POLLERR`

```mojo
comptime POLLERR = Int16(8)
```

### `POLLHUP`

```mojo
comptime POLLHUP = Int16(16)
```

### `POLLFD_SIZE`

```mojo
comptime POLLFD_SIZE = 8
```
