Перейти к содержимому
Star

PR в effector ​

deffector работает с effector в его нынешнем виде (22.6+) — на обходных путях. Шесть небольших изменений в самом effector сделали бы эти обходы ненужными, а данные — точнее. Изменения реализованы и проверены как ветки от master 93dd907 (effector 23.4.4).

Шесть изменений ​

#ИзменениеСутьIssuesОбход в deffector сейчас
1Инспектор не ломает ядроtry/catch вокруг вызова инспектора в ядре—агент оборачивает свой колбэк в try/catch
2Номера сообщений и транзакцийseq, parentSeq, launchId, filtered, derived в сообщениях#977собственный счётчик, WeakMap<Stack, step> через setInspector
3Все scope и их именаinspect({ scope: 'all' }), fork({ name }), объявления scope#659, #911, #1144stack.scope из setInspector, имена через registerScope
4Причинность через границыMessage.cause для эффектов и вложенных вызовов#1324, #541, #771stack.meta.fxID и эвристика «внешняя транзакция продолжилась»
5Рёбра и удаление юнитовinspectGraph({ links: true }): link (с reads) и dispose#91, #1200, PR #1334, #1353обход node.next, family, шагов mov из store
6Бюджет шагов (по желанию)лимит шагов на корневой launch, ошибка с путём цикла#331, #1153статический поиск циклов и аварийная остановка

1. Инспектор не ломает ядро ​

Исправление бага, отправить первым. Ядро вызывает инспектор вне tryRun; исключение из инспектора раскручивает launch мимо восстановления состояния, isRoot навсегда остаётся false, и все последующие отложенные запуски выходят раньше времени — await fx() не завершается, fx.pending застревает в true. Патч оборачивает вызов в try { … } catch (err) { console.error(err) }. Два теста, оба падают без исправления.

2. Номера сообщений и транзакций ​

seq (сквозной номер сообщения), parentSeq (seq сообщения stack.parent), launchId (номер корневого launch), filtered (узел выполнен, но ветка остановлена) и уже существующий derived в типах. parentSeq стоит O(1), в отличие от trace: true, который добавляет 85% ко времени события. В реализации шаг, запустивший другие (watcher вызвал событие), получает номер раньше них, потому что ядро выполняет вложенный launch сразу; сортировка по seq даёт причинный порядок.

3. Все scope и их имена ​

ts
inspect({ scope: 'all', fn }); // Message.scope: { id: string; name?: string } | null
fork({ name: 'client' });
inspectGraph({ fn }); // { type: 'scope', id, name } при fork, { type: 'scope-dispose', id }

Сейчас фильтр scope — точное совпадение, перечислить scope нельзя. scope: null в сообщении означает вызов вне scope — ровно то, что нужно проверке «вызов вне scope» (#1144).

4. Причинность через границы ​

Message.cause — { kind: 'effect', fxID, callSeq } или { kind: 'nested', seq }. Раннер эффекта и onSettled запускаются без стека, поэтому трейс там обрывается; событие из watcher-а начинает новый корень. С cause воспроизведение не дублирует вложенные вызовы, а «unit call from pure function» может указать на источник (#1324). В реализации часть про эффекты целиком считается в inspect.ts; ядро отдаёт только stack.caller.

5. Рёбра и удаление юнитов ​

inspectGraph({ links: true }) сообщает { type: 'link', id, op, from, to, reads, region, loc } и { type: 'dispose', id }. За основу взят формат link из PR #1334 и чтение связей после подключения из PR #1353 — это нужно согласовать с их авторами. Новое от deffector: reads (sample({ source }) не создаёт ребро next от source) и dispose (clearNode сейчас ничего не сообщает). Связи отправляются на микрозадаче, потому что sample прикрепляет source уже после создания узла.

6. Бюджет шагов ​

У цикла ядра нет ограничения, поэтому синхронный цикл через события вешает вкладку. Реализация — limitSteps({ steps, fn }) из effector/inspect: лимит шагов на корневой launch, а при превышении — остановка и ошибка с самыми частыми узлами. Необязательное и спорное изменение: мейнтейнеры раньше называли обнаружение циклов нерешаемой задачей. Собственная аварийная остановка deffector работает и без него.

Проверка ​

ВеткаБазаТесты
deffector/1-safe-inspectormaster+2; без фикса оба падают
deffector/2-message-seqmaster+3
deffector/3-all-scopesmaster+4
deffector/4-causedeffector/2-message-seq+3
deffector/5-links-disposemaster+3
deffector/6-step-budgetmaster+3
deffector/allвсе вместе777 из 777 тестов проекта effector; 13 файлов, +790/−24

Сборка effector (node tools/build.js) проходит, и агент deffector прогнан на ней: 27 из 27 тестов vitest.upstream.config.ts, включая тест, который проверяет точные skip, caller и fork({ name }).

Чтобы прогнать upstream-тесты агента на такой сборке:

sh
EFFECTOR_DIST=…/npm/effector npx vitest run -c vitest.upstream.config.ts

План ​

  1. Discussion в effector/effector со ссылкой на прототип deffector.
  2. PR 1 сразу.
  3. PR 2–4 по одному, каждый с тестами и замером на бенчмарке из 200 тысяч событий; без инспектора разницы быть не должно.
  4. PR 5 совместно с авторами #1334 и #1353.
  5. Цель — минорная версия 23.x, затем перенос в release/v24.
  6. deffector определяет возможности по наличию полей в сообщениях, а не по номеру версии effector, поэтому работает и до, и после.