2026: буферизация stdio OpenClaw, остановки JSON-RPC MCP и обратное давление канала на арендованном ProxyMac Mac mini
На ProxyMac Mac mini в Гонконге, Японии, Корее, Сингапуре или США OpenClaw часто регистрирует MCP-серверы поверх stdio: шлюз порождает дочерний процесс, говорит JSON-RPC через stdin/stdout и ждёт, что каждое сообщение придёт сразу. Сбой выглядит зловеще тихо: первый вызов инструмента работает, затем ребёнок перестаёт что-либо выводить, хотя top показывает простой CPU. В девяти случаях из десяти виноваты не «плохие модели», а буферизация stdio плюс обратное давление канала: процесс переходит в полностью блочно буферизованный режим, потому что больше не видит TTY; частичные ответы сидят в libc, пока буфер не заполнится. Родитель может блокироваться на read(), ожидая разделитель, который не придёт, или ребёнок блокируется на write(), потому что родитель не опустошил канал. Руководство описывает архитектуру, сопоставляет построчную и блочную буферизацию с матрицей симптомов, сравнивает PTY-обёртки и «голые» каналы под launchd, перечисляет меры для Python (PYTHONUNBUFFERED=1, python -u), фильтров POSIX (stdbuf -oL) и потоков Node (держите stdout в потоковом режиме), затем даёт пятиступенчатую диагностику, связанную с развёртыванием MCP, перезапуском шлюза, устранением проблем развёртывания и ulimits, когда на самом деле истощение дескрипторов маскируется под «зависания».
Архитектура MCP stdio в macOS (что должно оставаться истинным)
Представьте три сотрудничающих процесса: (A) шлюз OpenClaw, (B) бинарник или скрипт MCP-сервера, (C) опциональные фильтры (jq, рантаймы). Любой участник, который блокируется на записи, пока другой ждёт чтения в той же циклической зависимости, создаёт взаимоблокировку. Транспорты stdio наследуют и дисциплину stderr: болтливые библиотеки, сыплющие прогресс-бары в stderr, могут заполнить буферы канала ядра, если их никто не читает.
- Ожидать одно JSON-сообщение на каждый сброс нереалистично — libc не знает границ JSON.
- Большие ответы требуют потоковых читателей; если шлюз буферизует весь полезный груз в RAM, вы почувствуете «остановки», не связанные с семантикой MCP.
- launchd не подключает ваши интерактивные shell-rc — паритет окружения ручной; см. шаблоны upgrade/rollback для упорядоченных перезапусков после смены окружения.
Построчная и блочная буферизация (почему Terminal «работает»)
Многие CLI используют построчную буферизацию, когда isatty(stdout) истинно, и блочную (часто кратно 4–8 KiB), когда stdout — канал. Под безголовым LaunchAgent ваш MCP-сервер внезапно становится писателем в канал — логи, которые в Terminal казались «живыми», пакуются, пока буфер не заполнится. Задержка выглядит как «модель остановилась», хотя LLM закончил секунды назад.
stdbuf -oL -eL — измерьте задержку, прежде чем вшивать обёртки в продуктовые plist.
Матрица симптомов зависшего MCP
| Сигнал | Вероятнее багов MCP | Подтвердить / опровергнуть | Следующая ссылка |
|---|---|---|---|
| Первый RPC ок, второй висит навсегда | Блочно буферизованный stdout | Тот же бинарник под script -q /dev/null (дымовой PTY-тест) | Эта статья |
| CPU забит, RAM ровная | Тугой спин на пустом fd | Сэмпл sample pid 5 -file /tmp/st.txt | Диагностика |
Too many open files | Ulimits, fan-out MCP | launchctl limit maxfiles против мягкого лимита процесса | Ulimits |
| Интермитентно после ротации логов | Обработка SIGHUP / переоткрытые fd | Сопоставить метки времени с newsyslog | Логи |
PTY-обёртки и «голые» каналы под LaunchAgents
Некоторые команды оборачивают MCP-серверы в script, unbuffer или кастомные PTY-родители, чтобы дети думали, что они интерактивны. Компромисс: PTY добавляют накладные расходы CPU и копирования, но убирают класс сюрпризов буферизации. Голые каналы дешевле, но требуют дисциплинированного сброса в дочернем процессе или прокладок stdbuf. Выбирайте осознанно — смешение стилей между серверами путает дежурных.
Смягчения в цепочке инструментов (скопировать в EnvironmentVariables LaunchAgent)
Python: экспортируйте PYTHONUNBUFFERED=1 или вызывайте python3 -u; для упакованных CLI предпочитайте точку входа с sys.stdout.reconfigure(line_buffering=True) на поддерживаемых версиях. Node: потребляйте stdout в потоковом режиме — приостановленные потоки между дочерними процессами — частая ловушка. Go / Rust: явно сбрасывайте буфер после каждого кадра JSON-RPC, если контролируете исходники. Shell-фильтры: в автоматизации через каналы не забывайте grep --line-buffered.
<key>EnvironmentVariables</key>
<dict>
<key>PYTHONUNBUFFERED</key>
<string>1</string>
<key>NODE_OPTIONS</key>
<string>--max-old-space-size=4096</string>
</dict>
Пятиступенчатая stdio-диагностика до переписывания агента
- Воспроизведение под
ssh: те жеProgramArgumentsиз plist вставить в интерактивный shell — если внезапно заработало, есть расхождение окружения/TTY. - Аналог strace в macOS: кратко
sudo fs_usage -w -f filesys | grep mcpдля зависших записей (в проде осторожно). - Разделить stderr в ротируемый файл, чтобы отладочный шум не конкурировал с JSON-RPC на stdout.
- Нагрузочный прогон с синтетическими крупными полезными нагрузками после включения смягчений.
- Выбрать транспорт: если stdio остаётся хрупким, планируйте HTTP MCP по руководству по развёртыванию.
FAQ
Помогает ли увеличение sysctl буфера канала? Только крайний случай — чаще причина в несогласованном ритме читателя и писателя, а не только в размере буфера.
Стоит ли логировать MCP-серверам в stdout? stdout только для JSON-RPC; человеческие логи — в stderr или структурированные файлы.
Меняет ли Apple Silicon буферизацию? Нет — M4 лишь ускоряет столкновение с багом, но не делает иммунитетом к умолчаниям libc.
Почему ProxyMac Mac mini — правильное место, чтобы укрепить MCP stdio
Мини Apple Silicon M4 в регионах HK / JP / KR / SG / US дают постоянно включённое железо, где можно ночью повторять одни и те же графики stdio, подвесить региональную ёмкость рядом с вызываемыми SaaS API и держать ссылки справки возле репозитория LaunchAgent. Когда людям нужно подтверждать запросы TCC, откатитесь на VNC; когда сеть добавляет колебания, читайте IPv6 Happy Eyeballs SSH из той же линии релизов.
Запускайте OpenClaw на железе, которое можно спокойно strace’ить
HK / JP / KR / SG / US · автоматизация, дружественная к stdio