Repository navigation
test(mcp-server): ожидания тестов не зависят от нагрузки процессора - #428
Merged
Merged
Conversation
`await_walk_start` ждал первый файл обхода не дольше 60 секунд по часам. Теперь ожидание отсчитывает опросы: под нагрузкой каждый опрос растягивается, а бюджет по-прежнему истекает только тогда, когда обход не начался вообще. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…рки порядка вместо часов Тесты падали под нагрузкой процессора, потому что решали успех по часам: дедлайн в секундах у ожидания состояния, порог в 500 мс у статуса, которому нужен лок, пауза «для прогрева» перед гонкой. Продуктовое поведение не меняется; `McpServer::shutdown` сохраняет пятисекундное ожидание владельцев, добавлен только `shutdown_within`. Ожидания состояний: - `eventually` считает опросы (6000 по 10 мс), а не время; `eventually_within` для ожидания, которое задаётся расписанием повторов (бутстрап, 30 с + 60 с). - Рукописные циклы с дедлайном в тестовом коде переведены на `Patience` (счётчик опросов). В тестовом коде таких циклов не осталось. - `wait_until_watching`, `poll_now`, `tick_now` больше не принимают таймаут: ожидание ограничено общим `STATE_CEILING` (60 с), он защищает от зависания, а не меряет скорость. - Позитивные `recv_timeout`/`timeout` на события с пределами 5–30 с подняты до общего предела. Окна отсутствия (`!recv_timeout(100 мс)`) оставлены: они могут скрыть баг, но не вызвать ложный отказ. - `graph/test_support`: `wait_until_within` и `hold_every_handle` считают опросы. Владельцы и фикстуры: - `tests/tasks.rs`, `tests/task_deadline.rs`: сервер останавливается и ждёт выхода фоновых владельцев до удаления стенда (`Retired`, `shutdown_within`). Раньше фоновая очистка `bsl-overlay-backlog` читала уже удалённые `Толпа*`. - `tests/task_deadline.rs`: перед гейтируемой задачей индекс прогревается, чтобы срок попадал в фазу чтения, а не в фазу построения. - `tests/diagnostics_baseline.rs`: `partial` проверяется после того, как рабочее пространство сообщило, что видит оба модуля (`files_total == 2`). Гонки: - lease: `hold_file_lock_for_test` ждёт освобождения держателя, а не считает, что lock свободен. - `all_status_requests_are_cached_under_held_lease`: вместо порога в 500 мс проверяется журнал дисковых проверок (`disk_checks_on_this_thread`): статус не ходил за lease. - `embedding_fence_*`, `an_embed_pass_in_its_retry_pause_is_not_live_work`: выход пасса ждётся по счётчику `OwnerStop::live`, держатель отпускается после второй попытки fence. - `graph::*` (четыре теста из прогона под нагрузкой): `wait_settled` ждёт не только статус `Ready`, но и проход публикации, пустой слот сборки и отсутствие обхода (`walk_out`). - Три теста без `WALK_GATE` читали общий счётчик обходов: теперь держат гейт. - `a_cancelled_call_frees_the_resident_slot`, `cancelling_a_working_handle_...`: порог «не дольше 3 с» заменён сравнением с тем же ожиданием за незавершённым обходом. Проверки скорости (было 41 `elapsed`-порог в тестовом коде): - Неблокировка доказывается удержанием ресурса до ответа или журналом дисковых проверок (`snapshot`, `hybrid`, `state/mod`). - Ответ сам доказывает результат: `Pending`/`Failed` вместо «быстрее 500 мс». - Остались нижние границы (`>= floor`, `>= 200 мс`), они не нарушаются нагрузкой, и порядка величины 2× (15/30 с, 30/60 с, 300/600 с) — указано в тестах. Проверено: `cargo fmt --check`, `cargo clippy -p mcp-server --all-targets --all-features -D warnings`, `cargo test -p mcp-server --lib` и все интеграционные тесты без нагрузки проходят; с нагрузкой результаты — в описании PR. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- Patience считает шаг в наносекундах: шаг в микросекунды больше не усекается до миллисекунды. - Циклы, которые крутились на `yield_now`, теперь делают шаг по времени: бюджет опросов больше не тратится за миллисекунды (сборка графа, тикер тестов сессии, перенос подписки, `yield_until` в tasks). - Смоук-тест индексации снова ограничен своим бюджетом (150 с), а не общим пределом в 60 с. - Тест shutdown базового сервиса ограничивает возврат десятью производственными ack-таймаутами: блокировку по-прежнему доказывает шлюз, а задержку — верхняя граница. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Бюджет ограничивает только ожидание владельцев. Остановка reference worker, baseline и sweeper выполняется до него без изменений, как и в `shutdown`; документация теперь говорит это прямо. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- Watchdog изолированного фикстурного процесса снова отмеряет часами: это бюджет приёмки (150 с и 30 с), а не ожидание состояния, и сообщение называет секунды. - `walk_out` перенесён выше документа `reserve_probe`, чтобы документация не съехала. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- Проверки задержки ответа на отмену возвращены: `CANCEL_BOUND` (500 мс) как явный контракт скорости в `cancel_tests`, его проверят повторные прогоны под нагрузкой. - Потолки 120 с (resident) и 90 с (владельцы графа) возвращены к прежним значениям: сокращение до 60 с ужесточало тесты там, где ожидание самое длинное. - Контроль `a_cancelled_call_frees_the_resident_slot` меряет холостой вызов на свежем резиденте, который обход не трогал. - Условные ожидания в гейтах `PollGate` и `AnnounceBarrier` списывают бюджет только на таймаут (общий шаг `wait_step`), а пробуждения ограничены отдельным пределом. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…аний) - Ожидание занятости lifecycle в lease-тесте спит между опросами, а не крутится на yield. - Шаг опроса в metadata равен реальной паузе цикла (100 мс): бюджет 60 с, а не 600 с. - Отрицательный контроль владельца снова ждёт 2 с: окно отсутствия не сжимается. - `connect_within` использует свой бюджет (раньше параметр игнорировался). - Бюджеты ожидания из базы (180/120/90 с) возвращены в name_dictionary, outline, references, location_contract и query_validate. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…онде Проверка текста не ловит чистую задержку: статус, который ждёт зонда, но возвращает тот же текст, прошёл бы тест. Возвращена проверка «не дольше 500 мс» как явный контракт скорости (так же, как для отмены поиска). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Ожидания, чьим единственным детектором задержки была граница по времени, снова ограничены исходными значениями: явные проверки `elapsed` (200/500 мс, 1/2/3/5 с, 100 мс) и короткие ожидания «быстро» как счётчики опросов с исходным номиналом (100 и 200 опросов по 10 мс). Контроль `a_cancelled_call_frees_the_resident_slot` снова абсолютный (сосед за живым обходом ждёт больше 1 с): нагрузка только удлиняет такое ожидание, ложного падения не будет. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- `disk_checks_on_this_thread` на безымянном потоке сравнивает с той же меткой `"<unnamed>"`, что и запись: проверка «на этом потоке» не становится пустой. - Ожидания по опросам имеют стенной предел-страховку 20 минут: дорогой опрос не растягивает путь падения на часы. Успешный путь он не касается. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- Тест «владелец, которому велено уйти, отвечает Stopping при занятом движке» запускает вызовы в отдельном потоке и ждёт ответ не дольше секунды: регрессия даёт падение, а не зависание. - Матрица отмены снова проверяет задержку ответа (500 мс) у каждой ячейки; предел 60 с остаётся как страховка от зависания. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
- Бюджет, который не помещается на часах (`shutdown_within(Duration::MAX)`), ждёт срезами по часу, а не паникует при сложении `Instant`. - Комментарий в тесте shutdown называет предел 200 мс тем, чем он является: исходным контрактом задержки (100 мс ack-таймаут в тестовой сборке, предел вдвое больше). - Паника 20-минутного предохранителя в `wait_until` называет предохранитель, а не число опросов. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… теста Тело теста с #[tokio::test] выполняется на его собственном потоке, а ветка status отвечает инлайн, поэтому фенс статуса записан под именем потока теста. Фильтр по рантайм-воркерам его пропускал: регрессия не ловилась. Теперь считаются фенсы текущего потока. Мутация (фенс в ветке metadata status) валит тест с «a status call fenced the lease». Также: предел shutdown-теста 1 с (десятикратный ack-таймаут, как написано в комментарии), а сообщение probe-теста снова называет фактическую длительность. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ниченное тело опроса Ревью круга 487 (линзы перебор) дало два mid-замечания, оба подтверждены по коду. - OwnerStop::wait_empty с бюджетом, который не помещается в Instant, ждал часовыми срезами, но после первого истёкшего среза при живом владельце возвращал false. Теперь такой бюджет заканчивается только уходом владельцев; ограниченный бюджет ведёт себя как прежде. Цикл вынесен в wait_empty_in_slices(timeout, slice), чтобы тест мог взять срез в 10 мс. - Тело eventually в state/sync.rs спит 200 мс на попытку, а бюджет считал попытки по 10 мс: провал растягивался до 20 минут (HANG_GUARD). Теперь eventually_within(100, …) — около 21 с. Продуктового поведения это не меняет: shutdown по-прежнему ждёт владельцев пять секунд. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… первой устарелости Тест baseline_parity_partial_stale_error_recovery_and_cancellation под нагрузкой падал в 8 из 20 прогонов базы и в 10 из 20 после прошлых правок: ответ уже был stale, но baseline ещё `full`. Правка файла (drift) наблюдается раньше, чем нечитаемость файла (дыра). Хелпер ждал только stale и отдавал первый такой ответ, а утверждение требовало state `partial`. Теперь хелпер (workspace_until_partial_stale) ждёт обоих фактов по счётчику опросов. Дыра, однажды возникнув, держит partial до лечения, так что ожидание сходится в одну сторону. В сообщение проверки добавлено тело ответа: падение теперь показывает, что именно наблюдалось. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что сделано
Тесты
mcp-serverиtest-utilsрешали успех по часам: дедлайны в секундах, пороги в 500 мс, паузы «для прогрева». Здесь ожидания переведены на счётчики опросов и синхронизацию по событию, а проверки скорости оставлены явными контрактами. Продуктовое поведение не меняется; единственное добавление в публичный API —McpServer::shutdown_within(budget),shutdownвызывает его с прежними пятью секундами.Какие тесты зависели от нагрузки и почему
tests/diagnostics_baseline.rs::baseline_parity_partial_stale_error_recovery_and_cancellation— ждал первого ответа соstale: true. Этот ответ приходит, когда правка файла уже видна, а нечитаемость файла (дыра,state: partial) ещё нет. Ожидание переведено на оба факта.graph::build::tests::a_failed_reload_whose_retry_is_held_stays_owed,graph::state::tests::a_refused_reload_spawn_gives_its_claim_back,graph::state::tests::a_walk_that_outlived_its_episode_is_still_the_only_walk,graph::state::tests::every_admission_carries_the_healings_measured_before_it— читали состояние сразу послеReady, пока фон держал слот сборки, проход публикации или обход. Переведены наwait_settled.tests/tasks.rs,tests/task_deadline.rs— фикстура стенда удалялась, пока фоновый владелецbsl-overlay-backlogеё ещё читал. Теперь сервер останавливается и владельцы уходят до удаления стенда (Retired).state::embed::tests::embedding_fence_distinguishes_retry_from_supersession,state::embed::tests::an_embed_pass_in_its_retry_pause_is_not_live_work— держатель блокировки отпускался по часам.tests::all_status_requests_are_cached_under_held_lease— фильтр фенсов видел только рабочие потоки tokio и не видел вызовов на потоке теста; порог 500 мс у статуса заменён счётом фенсов по потоку теста.diagnostics_state::session::tests::a_cancelled_call_frees_the_resident_slot— абсолютный порог «ждать дольше 1 с» оставлен контролем, что живой обход действительно держит слот; проверка «отменённый ждёт в 3 раза меньше» сравнивает два замера под одной нагрузкой.tests/tasks.rs::cancelling_a_working_handle_stops_the_read_and_frees_the_resident— абсолютный порог «не дольше 3 с» после отмены заменён сравнением с тем же свипом, оставленным работать.Что заменено
eventually(Duration, f)→eventually(f)(6000 опросов по 10 мс; 20-минутный предел по часам остаётся страховкой от зависания). Рукописные циклы по дедлайну →Patience(счётчик опросов).recv_timeout/timeoutна события с пределами 5–30 с → общийSTATE_CEILING(60 с). Он защищает от зависания, а не меряет скорость.disk_checks_on_this_thread); исход доказывает сам результат (Pending,Failed,Stopping). Оставшиеся верхние границы — порядка величины и названы в тестах (например, 500 мс у отмены поиска).Retiredвtests/common(остановка сервера и ожидание владельцев до удаления стенда).graph/test_support::wait_settledвместоwait_readyв четырёх тестахgraph::*.WALK_GATEв трёх тестахlib.rs, которые двигали общий счётчик обходов.test-utils::walk_probe::await_walk_start: счётчик опросов вместо 60 с по часам.Изменения в продакшен-коде
state/mod.rs:shutdownвызываетshutdown_within(owners_budget)с 5 с. Телоshutdown_withinсовпадает с прежнимshutdownшаг в шаг.OwnerStop::wait_emptyвынесен вwait_empty_in_slices(timeout, slice): бюджет, которого нет вInstant, теперь ждёт уходом владельцев, а не возвращаетfalseпосле первого часового среза. Рабочие бюджеты не меняются; продакшен передаёт 5 с.lib.rs: публичныйMcpServer::shutdown_within.src/—#[cfg(test)]-хелперы (walk_out,disk_checks_on_this_thread,hold_file_lock_for_testи др.) и тесты. Вgraph/state.rsиgraph/debt.rsновые функции подcfg(test).Результаты под нагрузкой
Нагрузка: 2 × число ядер (64) процессов
yes > /dev/nullна 32 ядрах.Полные проходы всех бинарников
mcp-server(cargo test -p mcp-server,--no-fail-fast, 3 прохода):4467ba9a, load average 146–174): проход 1 — 1 падение (diagnostics_baseline…); проход 2 — без падений; проход 3 — 4 падения (графовые тесты). Итого: 2 из 3 проходов с падениями, 5 падений.cf8263f9, load average 107–131): в каждом проходе по одному падению, все в графовом семействе:graph::state::tests::a_chronically_unreadable_subtree_is_probed_but_never_rebuilt,tools::graph::tests::graph_data_requests_are_pool_only_under_held_lease,graph::state::tests::a_healed_subtree_is_found_by_the_probe_and_rebuilt. Итого: 3 из 3 проходов с падениями, 3 падения.Load average в проходах «после» был ниже, поэтому прямое сравнение неточное.
Повторы каждого из 32 затронутых тестов по 20 раз под той же нагрузкой (изолированно, один тест на запуск):
diagnostics_baseline::baseline_parity_partial_stale_error_recovery_and_cancellationtasks::cancelling_a_working_handle_stops_the_read_and_frees_the_residentУ остальных 30 тестов из списка 20/20 и до, и после: в изоляции под этой нагрузкой они не воспроизводили падений, поэтому по ним эффект не измерен.
Графовые тесты, падавшие в полных проходах (повторы по 100 раз, изолированно, под нагрузкой):
graph::state::tests::a_chronically_unreadable_subtree_is_probed_but_never_rebuiltgraph::state::tests::a_healed_subtree_is_found_by_the_probe_and_rebuilttools::graph::tests::graph_data_requests_are_pool_only_under_held_leaseЧастоты до и после в пределах шума: PR этот флейк не лечит.
Без нагрузки:
cargo test -p mcp-server(все 25 бинарников): 1815 passed, 0 failed, 23 ignored. Все бинарники завершились с кодом 0.Что не сделано и что остаётся
graph::state::tests::*,tools::graph::tests::*) под полной нагрузкой падает примерно в 1–6% прогонов каждого теста — до и после одинаково. Причина в фоновой перестройке графа, которую тест ждёт счётчиком опросов. Это отдельная работа, в этом PR не закрыта.tests/tasks.rs::cancelling_a_working_handle…: порог «отменённый в 3 раза быстрее живого» на грани под нагрузкой (в одной из серий отношение 0,38). Ослабить до 1/2 или оставить — решение автора; сейчас оставлено как есть.wait_untilвdiagnostics_state/test_support.rs: при медленной пробе провал растягивается до 20-минутного предела. Замечание ревью (low) принято, но не исправлено: любая правка либо снова вводит часовой срез (укорачивает успешные ожидания под нагрузкой), либо меняет предел.references.rs: фикстураТолпа*ещё безRetired.sleepв тестовом коде: шаги опроса внутри счётчиков и фиксированные паузы перед записью на диск (наблюдатели файлов).McpServer::shutdown_withinне ограничивает join референсного воркера, baseline и sweeper — они остаются безграничными, как и вshutdown.Ревью
Круги
pioпо коммитам: 487 (a47d6367) закрыт линзами; две находки mid исправлены в3b95770bи записаны как подтверждённые. 488 (3b95770b) закрыт; одна находка low (wait_until, см. выше) подтверждена и не исправлена. 491 (cf8263f9) закрыт линзами, находок нет. Формальное закрытиеpioнедоступно: выбранный коммит не совпадает с HEAD корня, поэтомуclosureEligibility— false. Часть линз в отдельных кругах не отработала.🤖 Generated with Claude Code