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

Перекладено з англійського оригіналу · 06042fa · 2026-10-09 · Читати англійською

Порти

Рішення плану 11, крок 57A (2026-10-08). Воно замінює рішення кроку 53 (без портів, процеси): програми отримують порти в тому вигляді, як їх визначає OTP, і зовнішній ввід-вивід проходить через них. Кроки 57B–57F це реалізують; кожен розділ називає свій крок. Цей контракт визначає представлення, модель драйверів і потік вводу-виводу до того, як будь-яке джерело зможе відкрити порт.

Реалізовано: ідентичності, таблицю портів, вбудовані функції й повідомлення портів, зв'язки (links), монітори, імена й сигнали виходу портів, а також порти fd лише для виводу (крок 57B, runtime/src/scheduler/ports.cpp, runtime/src/builtins/ports.cpp, runtime/src/ports/; еталон OTP executables_port_identities); потік вводу-виводу і ввід портів fd з потоковим, пакетним і рядковим фреймуванням (крок 57C, runtime/src/ports/io*.cpp; еталон OTP executables_port_input); порти підпроцесів, os:type/0, os:getenv/1 і os:cmd/1 (крок 57D, runtime/src/ports/spawn*.cpp, library/stdlib/os.erl; еталон OTP executables_port_spawn); файловий драйвер, підмножину file бібліотеки і стандартний ввід через io:get_line/io:get_chars (крок 57E, runtime/src/ports/file.cpp, library/stdlib/{file,io}.erl; еталон OTP executables_file_io); сокети (крок 57F, еталон OTP executables_sockets); один подієвий потік вводу-виводу для портів усіх видів (крок 57G1, runtime/src/ports/reactor.cpp; еталон OTP executables_many_ports, тест runtime runtime_port_io); завдання портів на робочих потоках планувальника (крок 57G2, runtime/src/scheduler/ports.cpp; еталон OTP executables_port_fairness); зайняті порти й обмежений ввід (крок 57G3; еталони OTP executables_busy_ports, executables_slow_owner).

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

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

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

Вбудована функція або повідомленняКрок
is_port/1 істинна для портів, port_to_list/1, list_to_port/1, ports/057B
port_info/1,2 (name, links, id, connected, input, output, os_pid, monitors, monitored_by, registered_name)57B
port_close/1, port_connect/2, port_command/2,357B
Port ! {Pid, {command, Data}}, {Pid, close}, {Pid, {connect, New}}57B
link/1, unlink/1, monitor(port, P), exit/2 для портів; register/2 порту57B
port_control/3, port_call/357B (badarg для драйверів без керування); використовуються драйверами 57E/57F
open_port({fd, In, Out}, Opts)57B вивід; ввід з 57C
open_port({spawn, Command} | {spawn_executable, File}, Opts)57D
Внутрішні драйвери бібліотеки проєкту (file, сокети)57E, 57F

Вбудовані функції замінюють діагностику [ports] notimpl кроку 53; функція (feature) ports стає реалізованою в 57B. Помилки відповідають 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 (57D)
{busy_limits_port, {Low, High} | disabled}Кількість байтів виводу в черзі, що робить порт зайнятим (зайняті порти, 57G3)

Без 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, крок 57G1) виконує io_context Boost.Asio: порт завершення вводу-виводу (I/O completion port) на Windows, epoll на Linux, kqueue на macOS. Його запускає перший порт, якому він потрібен; він обслуговує порти всіх видів, тож порт не коштує окремого потоку:

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

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

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

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

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

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

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

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

Підпроцеси

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

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

Крок 57E.

Сокети (57F)

Сокет — це порт, як у бекенді inet_drv в OTP: is_port(Socket) істинне, а повідомлення активного режиму — це {tcp, Socket, Data}, {tcp_closed, Socket}, {tcp_error, Socket, Reason}, {udp, Socket, Address, Port, Data}. Підмножина: gen_tcp:connect/3,4, listen/2, accept/1,2, send/2, recv/2,3, close/1, controlling_process/2, shutdown/2; gen_udp:open/1,2, send/4, recv/2,3, close/1; inet:setopts/2, inet:port/1, inet:peername/1, inet:sockname/1; режими {active, true | false | once}, binary/list, {packet, 0 | 1 | 2 | 4 | raw}, {reuseaddr, Bool}, {backlog, N}, {ip, Address}/{ifaddr, Address}, inet/inet6; адреси IPv4 та IPv6 як кортежі, імена хостів як рядки чи атоми (включно з loopback). Опції налаштування nodelay, keepalive, send_timeout, send_timeout_close, delay_send і exit_on_close приймаються, але не застосовуються; будь-яка інша опція дає exit(badarg), як для недійсної опції в OTP.

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

Не надається

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