Clause
← Уся документація

Англійський оригінал змінився після перекладу, тож частина тексту може бути застарілою. Читати англійською

Перекладено з англійського оригіналу · 1e3a39c8-dirty · 2026-10-26

Порти

Програми отримують порти в тому вигляді, як їх визначає OTP, і зовнішній ввід-вивід проходить через них. Таблиця портів, правила виходу й завдання портів містяться в runtime/src/scheduler/ports.cpp, вбудовані функції — в runtime/src/builtins/erlang/ports.cpp, драйвери й потік вводу-виводу — в runtime/src/ports/. Еталони 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; тест runtime runtime_port_io.

Ідентичність

Таблиця портів і володіння

Вбудовані функції й повідомлення портів

Помилки відповідають OTP: badarg для закритого чи недійсного порту й неправильних аргументів, причина POSIX (enoent, eacces) як error для невдалого відкриття.

Повідомлення від порту йдуть його під'єднаному процесу: {Port, {data, Data}}, {Port, eof} (опція eof), {Port, {exit_status, Status}} (опція exit_status), {Port, closed} (після {Pid, close}), {Port, connected} (старому власникові після під'єднання).

Режими даних і опції

ОпціяЕфект
stream (типово), {packet, N} (N = 1, 2, 4)Байти в міру надходження або повідомлення, обрамлені N-байтовою довжиною в порядку big-endian, яку отримує й вивід
{line, L}{eol, Line} для кожного рядка, {noeol, Part} для частин, довших за L, або для незавершеного кінця
binaryДані як binary замість списків байтів
eof{Port, eof} наприкінці вводу; порт лишається відкритим, доки його не закриють
exit_status{Port, {exit_status, S}}, коли програма завершується (порти spawn)
use_stdio (типово), nouse_stdio, stderr_to_stdout, in, out, hideЯк в OTP (hide не має ефекту)
{args, List}, {arg0, A}, {env, Env}, {cd, Dir}Порти spawn (підпроцеси)
{busy_limits_port, {Low, High} | disabled}Кількість байтів виводу в черзі, що робить порт зайнятим (зайняті порти)

Без eof кінець вводу закриває порт із причиною normal, після повідомлення exit_status, якщо його було запитано. Невідомі опції дають badarg.

Драйвери

Драйвер — це об'єкт C++ за одним портом (runtime/src/ports/): він відкриває ресурс, приймає вивід (port_command), відповідає на port_control/3, якщо підтримує керування, повідомляє про ввід і помилки як про події та закривається. Драйвери:

Внутрішні драйвери file, сокетів і реєстру відкриваються через {spawn_driver, Name} під іменами Clause, а їхні операції port_control/3 утворюють протокол Clause: програми використовують модулі бібліотеки, а не протоколи OTP prim_inet/efile.

Потік вводу-виводу

Один потік вводу-виводу на runtime (detail::Reactor, runtime/src/ports/reactor.hpp) виконує io_context Boost.Asio: порт завершення вводу-виводу (I/O completion port) на Windows, epoll на Linux, kqueue на macOS. Його запускає перший порт, якому він потрібен; він обслуговує порти всіх видів, тож порт не коштує окремого потоку:

runtime/src/ports/io*.cpp містять ввід-вивід портів (detail::IoService): його методи лише передають роботу потоку вводу-виводу, де живе весь стан вводу-виводу. Потік вводу-виводу передає ввід у тому вигляді, як його прочитано: сирі байти, кінець вводу, помилку читання, код завершення програми. Події сокетів (повідомлення, прийняті з'єднання) передаються так само.

Прототип tests/prototypes/poller/ (run.py --wsl) показує пробудження на Windows (порт завершення) і WSL Linux (poll()): вільний потік планувальника прокидається через 9–91 мкс після вводу, а завершення роботи зупиняє потік вводу-виводу без вводу.

Наприкінці програми кожен порт закривається (дочірні програми бачать кінець вводу; їх не вбивають, як і в OTP), а потік вводу-виводу зупиняється: його приєднують (join) поза м'ютексом виконавця, бо доставка, що триває, бере цей м'ютекс; читач fd на Windows, заблокований у читанні, яке не можна скасувати, від'єднується й більше нічого не доставляє.

Завдання портів

Порт планується як процес: те, що передає потік вводу-виводу, чекає в порту, а порт чекає в черзі портів виконавця, доки робочий потік планувальника не виконає його завдання.

Зайняті порти

Вивід, який драйвер ставить у чергу для потоку вводу-виводу (канали запущених програм), враховується в межах зайнятості порту, busy_limits_port в OTP (типово: верхня 8 192 байти, нижня 4 096; {busy_limits_port, {Low, High}} або disabled як опція open_port/2, межі щонайменше 1, нижня межа понад верхню знижується до неї):

Порт також обмежує ввід, який утримує:

Підпроцеси

open_port({spawn, Command}, Options) і open_port({spawn_executable, File}, Options) запускають програму, чиї stdin і stdout є каналами порту:

Стандартний ввід-вивід і файли

Сокети

Сокет — це порт, як у бекенді inet_drv в OTP: is_port(Socket) істинне, а повідомлення активного режиму — це {tcp, Socket, Data}, {tcp_closed, Socket}, {tcp_error, Socket, Reason}, {udp, Socket, Address, Port, Data}. Підмножина: gen_tcp:connect/2,3,4 (хост і порт або map адреси сокета), 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 (форми призначення й допоміжні дані 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; режими {active, true | false | once}, binary/list ({mode, M}), {packet, 0 | 1 | 2 | 4 | raw}, {backlog, N}, {ip, Address}/{ifaddr, Address}, inet/inet6; адреси IPv4 та IPv6 як кортежі, імена хостів як рядки чи атоми (включно з loopback).

Опції, крім режиму даних (ports/socket_options.cpp, library/kernel/clause_socket_options.erl), — це записи драйвера з кодом і значенням, які задаються при відкритті сокета (встановлюються до прив'язування чи з'єднання) або через setopts/2; кожна опція виклику setopts/2 перевіряється до встановлення будь-якої з них. Системні опції (reuseaddr, keepalive, dontroute, linger, broadcast, sndbuf, recbuf, nodelay, tos, tclass, ipv6_v6only, multicast_ttl, multicast_loop, multicast_if, add_membership, drop_membership, exclusiveaddruse у Windows, priority і reuseport, де система їх має) передаються в setsockopt і зчитуються через getsockopt; опції, які inet_drv з OTP зберігає сам (buffer, header, deliver, exit_on_close, send_timeout, порогові рівні, packet_size, ...), зберігаються з типовими значеннями inet_drv і не застосовуються (відмінності). Як в inet_drv, буфер приймання збільшує buffer, доки його не задано, а getopts/2 пропускає опції, яких тип сокета не має (broadcast для сокета TCP, nodelay для UDP, tclass для IPv4), і відхиляє невідомі через {error, einval}. Прийняте з'єднання успадковує опції слухального сокета. send/5 з допоміжними даними задає tos, tclass чи ttl лише для цієї датаграми; у Windows це {error, einval}, як в OTP.

Кожен сокет рахує свій трафік як inet_drv для getstat/1,2: отримані пакети — це читання (TCP) чи датаграми, надіслані — виклики send (з їхнім заголовком пакета) чи датаграми, з поточними середніми inet_drv; send_pend — це вивід у черзі, який ще не записано. getstatus повертає прапорці стану inet_drv (open, connected, connecting, listen, accepting), які info/1 і i/0,1,2 друкують з іменами OTP і стовпцем модуля (inet_tcp, inet6_udp, ...). Запити до закритого сокета отримують {error, einval}, як в OTP.

Модуль socket (library/kernel/socket.erl) використовує ті самі порти драйверів у пасивному режимі binary. Потоковий сокет можна прив'язати до прослуховування чи з'єднання (операція 22 відкриває й прив'язує системний сокет, який listen перетворює на сокет приймання з'єднань, а connect з'єднує); сокет датаграм прив'язується через udp_open, неявно при першому надсиланні. Завершення з'єднання відповідає кожному recv, що чекає (зокрема тому, що запитує більше байтів, ніж лишилося). socket зберігає байти, яких recv не забрав, у власному буфері, тож recv/2,3,4 повертає {error, {timeout, Data}} і {error, {closed, Data}} як в OTP.

Реалізація (runtime/src/ports/sockets.cpp):

Реєстр

win32reg відкриває {spawn_driver, "clause_registry"} у Windows (runtime/src/ports/registry_windows.cpp). Драйвер зберігає стан драйвера реєстру OTP (запитаний доступ, поточний ключ і його корінь, спочатку HKEY_CLASSES_ROOT) і відповідає на дев'ять команд OTP як на синхронні операції port_control/3 відповідями, які драйвер OTP надсилає як дані порту: o, e та ім'я errno (пошкодження реєстру — eio, видалений ключ — einval, інші помилки Windows — за відображенням C runtime), k та ім'я ключа, v, тип, ім'я й дані (рядки без кінцевого нуля, DWORD у порядку big-endian), s і поточний ключ. Переліки повертають кожну відповідь, перед кожною — її 32-бітний розмір. Імена й рядки використовують кодову сторінку ANSI, як драйвер OTP.

Не надається

Вбудовувані (linked-in) драйвери та NIF, erlang:open_port({spawn_driver, Name}) для імен драйверів OTP, порти розподіленості (distribution), port_call/3 на наданих драйверах, окрім власного протоколу бібліотеки, зайняті порти сокетів, а також опції overlapped_io, parallelism і busy_limits_msgq.