1.7.8
Fixed (#413)
- WebSocket hub чата больше не блокирует доставку событий медленным клиентом. Broadcast обрабатывался одним hub-loop’ом: под
RLockитерировались подписчики и для каждого синхронно выполнялась блокирующая запись в сокет (Write с таймаутом до 5с) — один «зависший» клиент задерживал tool-события, финальный ответ иturn_endостальным подписчикам и самому себе (в UI — запоздавшие tool calls и висящий «печатает…»), а очередьregister/unregister/broadcastиSubscribeвставали за ним. КаждыйWSConnectionполучил bounded исходящую очередь (ёмкость =ws_hub_buffer_size, clamp 16–4096 с отдельным потолком per-conn 1024 — RSS-инвариантзначение × N_клиентов) и writer-горутину — единственного писателя в сокет с write-deadline 5с на кадр; hub-loop иBroadcastAllтолько неблокирующе enqueue-ят (сокет-I/O из hub-loop убран полностью), порядок кадров одного соединения строго FIFO, handshake (ready/status-resume) ставится в очередь до Register и не конкурирует с broadcast-штормом. Переполнение очереди: соседние delta/reasoning_delta-кадры одного стрима коалесцируются (merge Text, бюджет 256 KiB in-memory) — burst дельт переживается уплотнением очереди in-place даже вместе с terminal-событием; несливаемое переполнение (хронически нечитающий клиент) закрывает соединение close-кодом 4008 с reason-строкой — терминальные события молча не теряются, фронт реконнектится (existing reconnect с backoff) и восстанавливает состояние по истории/status-resume. Writer при ошибке записи сам вычищает соединение из hub (раньше мёртвый коннект держался до read-timeout 60с/ping 30с); закрытие сокета hub’ом прерывает висящий Write, shutdown сервера не порождает ложных write-error-метрик. Метрики:taigaclaw_ws_overflow_disconnects_total,taigaclaw_ws_write_errors_total,taigaclaw_ws_write_timeouts_total(timeouts ⊂ errors) + JSON-snapshot секцияws; Warn-логи дропа с conn_id/agent_id/причиной.metrics.Default()переведён наatomic.Pointerс CAS-ленивой установкой — конкурентные первые вызовы видят один канонический инстанс.
Review (#413)
- ocr review — 9 раундов; все high/medium исправлены: гонка close-кодов между Unregister и writer-дропом (единая точка закрытия
removeConnectionByID— код/лог/метрика отражают причину, победившую в гонке; метрики причин смерти пишутся только у победителя), утечка writer-горутины после ухода клиента (per-connection cancel context), утечка пре-existing shutdown-watcher-горутины на соединение (select на connCtx), блокировка Register/Unregister/Broadcast/Broadcast*-хелперов после остановки hub-loop (done-канал + select, быстрый отказ emitter’ов), окноdropConnByID→closed.Store(флаг выставляется подh.mu), потеря handshakereadyпри broadcast-шторме на старте соединения (ready/resume enqueue-ятся до Register), json.Marshal-сбой классифицировался как сетевая ошибка и дропал соединение (теперь Error-лог + пропуск кадра), burst из дельт +turn_endдропал здорового клиента до дренирования writer’ом (in-place уплотнение очереди — поймано race-тестом), ложные overflow/write-error метрики на shutdown-путях (closed-гарды в Send/handleWriteFailure/hub.Run),metrics.Default()безNew()плодил инстансы (atomic.Pointer + CAS). Отклонены с обоснованием: read-limit 32 KiB (относится к Read тестового клиента, браузерный WebSocket лимита не имеет), «Write висит 5с после закрытия сокета» (Conn.Closeпрерывает блокированный Write немедленно — подтверждено логами тестов), отдельные close-коды на причину и panic вmetrics.New()(фронт коды не различает; production-инвариант New-до-горутин задокументирован — тема осциллировала 5 раундов подряд с взаимоисключающими предложениями), evict старейшего события под terminal (молчаливая дыра в порядке хуже явного закрытия с reconnect). Тесты (ws_hub_queue_test.go): коалесценция соседних кадров (порядок, разные StreamID/типы, лимит слияния), overflow-политика (merge в хвост, уплотняемый burst с terminal, неуплотняемая очередь → дроп), интеграционный дроп переполненного соединения из hub-loop (close 4008 + метрика + идемпотентность повторного дропа), e2e «медленный клиент не задерживает здорового» (300 дельт + turn_end), e2e порядок terminal-событий под burst, конкурентный Enqueue без потерь. Проверки:go test ./... -count=1(4 контрольных прогона — 0 падений; единичные FAIL-прогоны — таймаут-чувствительные интеграционные тесты под CPU-нагрузкой параллельного ocr-процесса),go test -race ./internal/channels/...(стабильно),golangci-lint(0 новых; легаси-SA5011 в нетронутых файлах),go vet,make build-cross(6 платформ) — зелёные.