Clause
← Toda a documentação

O original em inglês mudou desde esta tradução, pelo que partes dela podem estar desatualizadas. Ler em inglês

Traduzido do original em inglês · 1e3a39c8-dirty · 2026-10-26

Portas

Os programas obtêm portas tal como o OTP as define, e a E/S externa passa por elas. A tabela de portas, as regras de saída e as tarefas de portas estão em runtime/src/scheduler/ports.cpp, os builtins em runtime/src/builtins/erlang/ports.cpp, os drivers e a thread de E/S em runtime/src/ports/. Goldens OTP: executables_port_identities, executables_port_input, executables_port_spawn, executables_file_io, executables_sockets, executables_many_ports, executables_port_fairness, executables_busy_ports, executables_slow_owner; teste do runtime runtime_port_io.

Identidade

Tabela de portas e propriedade

Builtins e mensagens de portas

Os erros seguem o OTP: badarg para uma porta fechada ou inválida e para argumentos inválidos, e a razão POSIX (enoent, eacces) como error para uma abertura que falhe.

As mensagens de uma porta vão para o seu processo conectado: {Port, {data, Data}}, {Port, eof} (opção eof), {Port, {exit_status, Status}} (opção exit_status), {Port, closed} (depois de {Pid, close}), {Port, connected} (para o antigo dono depois de um connect).

Modos de dados e opções

OpçãoEfeito
stream (por omissão), {packet, N} (N = 1, 2, 4)Bytes tal como chegam, ou mensagens enquadradas por um comprimento big-endian de N bytes, que a saída também recebe
{line, L}{eol, Line} por linha, {noeol, Part} para partes com mais de L ou para um fim sem terminador
binaryDados como binaries em vez de listas de bytes
eof{Port, eof} no fim da entrada; a porta continua aberta até ser fechada
exit_status{Port, {exit_status, S}} quando o programa sai (portas spawn)
use_stdio (por omissão), nouse_stdio, stderr_to_stdout, in, out, hideComo no OTP (hide não tem efeito)
{args, List}, {arg0, A}, {env, Env}, {cd, Dir}Portas spawn (subprocessos)
{busy_limits_port, {Low, High} | disabled}Bytes de saída em fila que tornam a porta ocupada (portas ocupadas)

Sem eof, o fim da entrada fecha a porta com a razão normal, depois da mensagem exit_status quando esta tiver sido pedida. As opções desconhecidas dão badarg.

Drivers

Um driver é um objeto C++ por trás de uma porta (runtime/src/ports/): abre o recurso, aceita saída (port_command), responde a port_control/3 quando suporta controlo, indica a entrada e os erros como eventos e fecha. Drivers:

Os drivers internos de file, dos sockets e do registo são abertos com {spawn_driver, Name} sob nomes do Clause, e as suas operações de port_control/3 são um protocolo do Clause: os programas usam os módulos da biblioteca, não os protocolos prim_inet/efile do OTP.

Thread de E/S

Uma thread de E/S por runtime (detail::Reactor, runtime/src/ports/reactor.hpp) executa um io_context do Boost.Asio: uma I/O completion port no Windows, epoll no Linux, kqueue no macOS. A primeira porta que precisa dela inicia-a; serve todos os tipos de portas, pelo que uma porta não custa uma thread própria:

runtime/src/ports/io*.cpp contêm a E/S das portas (detail::IoService): os seus métodos apenas publicam trabalho na thread de E/S, onde reside todo o estado de E/S. A thread de E/S entrega a entrada tal como foi lida: bytes em bruto, o fim da entrada, um erro de leitura, o código de saída de um programa. Os eventos de sockets (mensagens, ligações aceites) são entregues da mesma forma.

O protótipo tests/prototypes/poller/ (run.py --wsl) mostra o despertar no Windows (completion port) e no Linux em WSL (poll()): uma thread do escalonador inativa acorda 9–91 µs após a entrada, e o encerramento para a thread de E/S sem entrada.

No fim do programa, todas as portas são fechadas (os programas filhos veem o fim da entrada; não são mortos, como no OTP) e a thread de E/S para: é juntada (join) fora do mutex do executor, porque uma entrega em curso o obtém; um leitor fd do Windows bloqueado numa leitura que não pode ser cancelada é desanexado e não entrega mais nada.

Tarefas de portas

Uma porta é escalonada como um processo: o que a thread de E/S entrega espera na porta, e a porta espera na fila de portas do executor até um worker do escalonador executar a sua tarefa.

Portas ocupadas

A saída que um driver põe em fila para a thread de E/S (pipes de programas lançados) conta para os limites de ocupação da porta, o busy_limits_port do OTP (por omissão: alto 8192 bytes, baixo 4096; {busy_limits_port, {Low, High}} ou disabled como opção de open_port/2, limites de pelo menos 1, um limite baixo acima do alto é reduzido a este):

Uma porta também limita a entrada que retém:

Subprocessos

open_port({spawn, Command}, Options) e open_port({spawn_executable, File}, Options) iniciam um programa cujo stdin e stdout são pipes da porta:

E/S padrão e ficheiros

Sockets

Um socket é uma porta, como no backend inet_drv do OTP: is_port(Socket) é true e as mensagens em modo ativo são {tcp, Socket, Data}, {tcp_closed, Socket}, {tcp_error, Socket, Reason}, {udp, Socket, Address, Port, Data}. O subconjunto: gen_tcp:connect/2,3,4 (um anfitrião e uma porta, ou um map de endereço de socket), listen/2, accept/1,2, send/2, recv/2,3, close/1, controlling_process/2, shutdown/2; gen_udp:open/1,2, connect/2,3, send/2,3,4,5 (as formas de destino e os dados auxiliares do OTP), recv/2,3, close/1; inet:setopts/2, getopts/2, getstat/1,2, info/1, i/0,1,2, monitor/1, cancel_monitor/1, port/1, peername/1, peernames/1,2, sockname/1, socknames/1,2; modos {active, true | false | once}, binary/list ({mode, M}), {packet, 0 | 1 | 2 | 4 | raw}, {backlog, N}, {ip, Address}/{ifaddr, Address}, inet/inet6; endereços IPv4 e IPv6 como tuplos, nomes de anfitrião como strings ou átomos (incluindo loopback).

As opções para além do modo de dados (ports/socket_options.cpp, library/kernel/clause_socket_options.erl) são records do driver com um código e um valor, dadas quando um socket abre (definidas antes de se associar ou conectar) ou por setopts/2; todas as opções de uma chamada setopts/2 são verificadas antes de qualquer uma ser definida. As opções do sistema (reuseaddr, keepalive, dontroute, linger, broadcast, sndbuf, recbuf, nodelay, tos, tclass, ipv6_v6only, multicast_ttl, multicast_loop, multicast_if, add_membership, drop_membership, exclusiveaddruse no Windows, priority e reuseport onde o sistema as tem) vão para setsockopt e são lidas com getsockopt; as opções que o inet_drv do OTP mantém internamente (buffer, header, deliver, exit_on_close, send_timeout, limiares, packet_size, ...) são mantidas com os valores por omissão de inet_drv e não são aplicadas (diferenças). Como no inet_drv, um buffer de receção aumenta buffer até este ser definido, e getopts/2 omite as opções que um tipo de socket não tem (broadcast de um socket TCP, nodelay de um UDP, tclass de IPv4) e recusa as desconhecidas com {error, einval}. Uma ligação aceite herda as opções do seu socket de escuta. send/5 com dados auxiliares define tos, tclass ou ttl apenas para esse datagrama; no Windows dá {error, einval}, como no OTP.

Cada socket conta o seu tráfego como o inet_drv para getstat/1,2: os pacotes recebidos são leituras (TCP) ou datagramas, os pacotes enviados são chamadas send (com o seu cabeçalho de pacote) ou datagramas, com as médias acumuladas do inet_drv; send_pend é a saída em fila ainda não escrita. getstatus dá os indicadores de estado do inet_drv (open, connected, connecting, listen, accepting), que info/1 e i/0,1,2 imprimem com os nomes e a coluna de módulo do OTP (inet_tcp, inet6_udp, ...). As consultas a um socket fechado respondem {error, einval}, como no OTP.

O módulo socket (library/kernel/socket.erl) usa as mesmas portas de driver em modo binary passivo. Um socket de fluxo pode ser associado antes de escutar ou conectar (a operação 22 abre e associa o socket do sistema, que listen transforma no aceitador ou connect conecta); um socket de datagramas é associado por udp_open, implicitamente no seu primeiro envio. O fim de uma ligação responde a todos os recv em espera (também a um que peça mais bytes do que os restantes). socket mantém no seu próprio buffer os bytes que um recv não consumiu, pelo que recv/2,3,4 dá os resultados do OTP {error, {timeout, Data}} e {error, {closed, Data}}.

Implementação (runtime/src/ports/sockets.cpp):

O registo

win32reg abre {spawn_driver, "clause_registry"} no Windows (runtime/src/ports/registry_windows.cpp). O driver mantém o estado do driver de registo do OTP (o acesso pedido, a chave atual e a sua raiz, inicialmente HKEY_CLASSES_ROOT) e responde aos nove comandos do OTP como operações síncronas de port_control/3 com as respostas que o driver do OTP envia como dados da porta: o, e e o nome errno (danos no registo dão eio, uma chave eliminada einval, os outros erros do Windows conforme o mapeamento do runtime de C), k e um nome de chave, v, o tipo, nome e dados (strings sem o zero final, DWORDs big-endian), s e a chave atual. As listagens devolvem todas as respostas, cada uma precedida pelo seu tamanho de 32 bits. Os nomes e as strings usam a página de códigos ANSI, como no driver do OTP.

Não fornecido

Drivers linked-in e NIFs, erlang:open_port({spawn_driver, Name}) para nomes de drivers do OTP, portas de distribuição, port_call/3 sobre os drivers fornecidos para além do protocolo próprio da biblioteca, portas de sockets ocupadas, e as opções overlapped_io, parallelism e busy_limits_msgq.