Clause
← Toda la documentación

El original en inglés ha cambiado desde que se hizo esta traducción, por lo que partes de ella pueden estar desactualizadas. Leer en inglés

Traducido del original en inglés · 1e3a39c8-dirty · 2026-10-26

Puertos

Los programas obtienen puertos tal como los define OTP, y la E/S externa pasa por ellos. La tabla de puertos, las reglas de salida y las tareas de puerto están en runtime/src/scheduler/ports.cpp, los builtins en runtime/src/builtins/erlang/ports.cpp, y los drivers y el hilo de E/S en runtime/src/ports/. Goldens de 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; prueba del runtime runtime_port_io.

Identidad

Tabla de puertos y propiedad

Builtins y mensajes de puerto

Los errores siguen a OTP: badarg para un puerto cerrado o no válido y para argumentos incorrectos, y la razón POSIX (enoent, eacces) como error para una apertura que falla.

Los mensajes de un puerto van a su proceso conectado: {Port, {data, Data}}, {Port, eof} (opción eof), {Port, {exit_status, Status}} (opción exit_status), {Port, closed} (tras {Pid, close}), {Port, connected} (al propietario anterior tras un connect).

Modos de datos y opciones

OpciónEfecto
stream (por defecto), {packet, N} (N = 1, 2, 4)Los bytes según llegan, o mensajes delimitados por una longitud big-endian de N bytes que también recibe la salida
{line, L}{eol, Line} por línea, {noeol, Part} para las partes de más de L o un final sin terminador
binaryLos datos como binaries en lugar de listas de bytes
eof{Port, eof} al final de la entrada; el puerto sigue abierto hasta que se cierra
exit_status{Port, {exit_status, S}} cuando el programa sale (puertos spawn)
use_stdio (por defecto), nouse_stdio, stderr_to_stdout, in, out, hideComo en OTP (hide no tiene efecto)
{args, List}, {arg0, A}, {env, Env}, {cd, Dir}Puertos spawn (subprocesos)
{busy_limits_port, {Low, High} | disabled}Bytes de salida encolados que hacen que el puerto esté ocupado (puertos ocupados)

Sin eof, el final de la entrada cierra el puerto con la razón normal, después del mensaje exit_status cuando se ha pedido. Las opciones desconocidas son badarg.

Drivers

Un driver es un objeto C++ detrás de un puerto (runtime/src/ports/): abre el recurso, acepta la salida (port_command), responde a port_control/3 cuando admite control, informa de la entrada y de los errores como eventos y se cierra. Drivers:

Los drivers internos de file, de los sockets y del registro se abren con {spawn_driver, Name} bajo nombres de Clause, y sus operaciones de port_control/3 son un protocolo de Clause: los programas usan los módulos de la biblioteca, no los protocolos prim_inet/efile de OTP.

Hilo de E/S

Un hilo de E/S por runtime (detail::Reactor, runtime/src/ports/reactor.hpp) ejecuta un io_context de Boost.Asio: un puerto de finalización de E/S (I/O completion port) en Windows, epoll en Linux, kqueue en macOS. El primer puerto que lo necesita lo arranca; sirve a todos los tipos de puerto, así que un puerto no cuesta ningún hilo propio:

runtime/src/ports/io*.cpp contienen la E/S de puertos (detail::IoService): sus métodos solo envían trabajo al hilo de E/S, donde vive todo el estado de E/S. El hilo de E/S entrega la entrada tal como se leyó: bytes en bruto, el final de la entrada, un error de lectura, el estado de salida de un programa. Los eventos de sockets (mensajes, conexiones aceptadas) se entregan del mismo modo.

El prototipo tests/prototypes/poller/ (run.py --wsl) muestra el despertar en Windows (puerto de finalización) y en Linux bajo WSL (poll()): un hilo planificador inactivo despierta 9–91 µs después de la entrada, y el apagado detiene el hilo de E/S sin entrada.

Al final del programa se cierran todos los puertos (los programas hijos ven el final de la entrada; no se matan, como en OTP) y el hilo de E/S se detiene: se espera (join) fuera del mutex del ejecutor, porque una entrega en curso lo toma; un lector fd de Windows bloqueado en una lectura que no se puede cancelar se desacopla y ya no entrega nada más.

Tareas de puerto

Un puerto se planifica como un proceso: lo que entrega el hilo de E/S espera en el puerto, y el puerto espera en la cola de puertos del ejecutor hasta que un hilo de trabajo del planificador ejecuta su tarea.

Puertos ocupados

La salida que un driver encola para el hilo de E/S (las tuberías de los programas lanzados) cuenta para los límites de ocupación del puerto, el busy_limits_port de OTP (por defecto: alto 8.192 bytes, bajo 4.096; {busy_limits_port, {Low, High}} o disabled como opción de open_port/2, límites de al menos 1, y un límite bajo superior al alto se rebaja a este):

Un puerto también acota la entrada que retiene:

Subprocesos

open_port({spawn, Command}, Options) y open_port({spawn_executable, File}, Options) arrancan un programa cuyos stdin y stdout son tuberías del puerto:

E/S estándar y archivos

Sockets

Un socket es un puerto, como con el backend inet_drv de OTP: is_port(Socket) es true y los mensajes en modo activo son {tcp, Socket, Data}, {tcp_closed, Socket}, {tcp_error, Socket, Reason}, {udp, Socket, Address, Port, Data}. El subconjunto: gen_tcp:connect/2,3,4 (un host y un puerto, o un map de dirección 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 (formas de destino y datos auxiliares de 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; los modos {active, true | false | once}, binary/list ({mode, M}), {packet, 0 | 1 | 2 | 4 | raw}, {backlog, N}, {ip, Address}/{ifaddr, Address}, inet/inet6; direcciones IPv4 e IPv6 como tuplas, nombres de host como cadenas o átomos (incluido loopback).

Las opciones aparte del modo de datos (ports/socket_options.cpp, library/kernel/clause_socket_options.erl) son records del driver con un código y un valor, indicados al abrir un socket (se fijan antes de vincularlo o conectarlo) o mediante setopts/2; todas las opciones de una llamada a setopts/2 se comprueban antes de fijar ninguna. Las opciones del sistema (reuseaddr, keepalive, dontroute, linger, broadcast, sndbuf, recbuf, nodelay, tos, tclass, ipv6_v6only, multicast_ttl, multicast_loop, multicast_if, add_membership, drop_membership, exclusiveaddruse en Windows, priority y reuseport donde el sistema las ofrece) pasan a setsockopt y se consultan con getsockopt; las opciones que el propio inet_drv de OTP conserva (buffer, header, deliver, exit_on_close, send_timeout, marcas de agua, packet_size, ...) se conservan con los valores predeterminados de inet_drv y no se aplican (diferencias). Como en inet_drv, un búfer de recepción aumenta buffer hasta que este se fija, y getopts/2 omite las opciones de las que carece un tipo de socket (broadcast de un socket TCP, nodelay de uno UDP, tclass de IPv4) y rechaza las desconocidas con {error, einval}. Una conexión aceptada hereda las opciones de su socket de escucha. send/5 con datos auxiliares fija tos, tclass o ttl solo para ese datagrama; en Windows es {error, einval}, como en OTP.

Cada socket cuenta su tráfico como inet_drv para getstat/1,2: los paquetes recibidos son lecturas (TCP) o datagramas, los enviados son llamadas a send (con su cabecera de paquete) o datagramas, con las medias acumuladas de inet_drv; send_pend es la salida encolada y aún no escrita. getstatus proporciona los indicadores de estado de inet_drv (open, connected, connecting, listen, accepting), que info/1 e i/0,1,2 imprimen con los nombres y la columna de módulo de OTP (inet_tcp, inet6_udp, ...). Las consultas a un socket cerrado responden {error, einval}, como OTP.

El módulo socket (library/kernel/socket.erl) usa los mismos puertos de driver en modo binario pasivo. Un socket de flujo se puede vincular antes de escuchar o conectarse (la operación 22 abre y vincula el socket del sistema, que listen convierte en el receptor de conexiones o connect conecta); un socket de datagramas se vincula mediante udp_open, implícitamente en su primer envío. El final de una conexión responde a todos los recv en espera (también a los que piden más bytes de los que quedan). socket conserva los bytes que un recv no tomó en su propio búfer, por lo que recv/2,3,4 proporciona {error, {timeout, Data}} y {error, {closed, Data}} de OTP.

Implementación (runtime/src/ports/sockets.cpp):

El registro

win32reg abre {spawn_driver, "clause_registry"} en Windows (runtime/src/ports/registry_windows.cpp). El driver conserva el estado del driver del registro de OTP (el acceso solicitado, la clave actual y su raíz, inicialmente HKEY_CLASSES_ROOT) y responde a los nueve comandos de OTP como operaciones síncronas de port_control/3 con las respuestas que el driver de OTP envía como datos de puerto: o, e y el nombre errno (los daños del registro son eio, una clave eliminada einval, los demás errores de Windows como los asigna el runtime de C), k y un nombre de clave, v, el tipo, el nombre y los datos (cadenas sin su cero final, DWORDs big-endian), s y la clave actual. Los listados devuelven todas las respuestas, cada una precedida por su tamaño de 32 bits. Los nombres y las cadenas usan la página de códigos ANSI, como el driver de OTP.

No proporcionado

Los drivers enlazados (linked-in drivers) y los NIF, erlang:open_port({spawn_driver, Name}) para nombres de drivers de OTP, los puertos de distribución, port_call/3 sobre los drivers proporcionados fuera del protocolo propio de la biblioteca, los puertos de socket ocupados, y las opciones overlapped_io, parallelism y busy_limits_msgq.