Skip to content

[Bug]: Infinite back() loop — history.block re-intercepts its own retryNativeFx go() (single router, no app navigation) #110

Description

@warbr1ng3r

Package

@effector/router (core)

What happened?

Один нативный back() при активном history.block (дефолтный historyAdapter) уходит в бесконечный цикл и вешает приложение. Никакой навигации со стороны приложения при этом не происходит.

Механизм: retryNativeFx (transition.retry(), dist ~index.js:724) переприменяет удержанный POP сырым history.go, не обёрнутым в runWithoutBlocking. Активный blocker (subscribeHistoryFx, ~index.js:969) перехватывает этот же retry-go как новую нативную навигацию → откатывает его → снова retry → бесконечно.

Ожидается: retry() должен обходить blocker (как runWithoutBlocking для push/replace), либо blocker должен приостанавливаться на время retry. Один back() должен дать один POP.

Фактически: бесконечный go(-1)/go(1) пинг-понг между двумя локациями; history.length стабилен; push/replace = 0 (навигация приложения не участвует).

Связано с #109 (там потеря POP при конкурирующем replace) — вероятно, это более фундаментальная причина: конкурирующая навигация не нужна.

Logs / screenshots

goNeg = 34, goPos = 35, POPSTATE = 69, push = 0, replace = 0   // anyPushOrReplace = false; растёт ~47 popstate/сек

Reproduction

Один self-contained файл (зависимости с CDN), запуск в реальном браузере:

<!doctype html><html><head><meta charset=utf-8><title>@effector/router 1.2.0 back() re-entrancy</title></head><body>
<pre id=out>running…</pre>
<script type=module>
import { createRouter, createRoute, historyAdapter } from 'https://esm.sh/@effector/router@1.2.0';
import { createBrowserHistory } from 'https://esm.sh/history@5.3.0';
const out=document.getElementById('out');
const c={push:0,replace:0,goNeg:0,goPos:0,pop:0}; const log=[];
const H=window.history, _p=H.pushState,_r=H.replaceState,_g=H.go,_b=H.back;
H.pushState=function(...a){c.push++;if(log.length<400)log.push('PUSH '+a[2]);return _p.apply(this,a);};
H.replaceState=function(...a){c.replace++;if(log.length<400)log.push('REPLACE '+a[2]);return _r.apply(this,a);};
H.go=function(d){d<0?c.goNeg++:d>0&&c.goPos++;if(log.length<400)log.push('GO('+d+') @'+location.pathname);return _g.call(this,d);};
H.back=function(){if(log.length<400)log.push('history.back() @'+location.pathname);return _b.call(this);};
window.addEventListener('popstate',()=>{c.pop++;if(log.length<400)log.push('POPSTATE @'+location.pathname);});
const history=createBrowserHistory();
const routeA=createRoute({path:'/a'}), routeB=createRoute({path:'/b'});
const router=createRouter({base:'',routes:[routeA,routeB]});
router.setHistory(historyAdapter(history));
const s=ms=>new Promise(r=>setTimeout(r,ms));
(async()=>{ await s(150); routeA.open(); await s(150); routeB.open(); await s(250);
  c.push=c.replace=c.goNeg=c.goPos=c.pop=0; log.length=0;
  window.history.back();            // единственный триггер, дальше никакой навигации
  await s(1500);
  out.textContent=JSON.stringify({afterPath:location.pathname,counts:{...c},anyPushOrReplace:c.push>0||c.replace>0,logSample:log.slice(0,26)},null,2);
})();
</script></body></html>

Запуск: python3 -m http.server 8099 --bind 127.0.0.1 → открыть http://127.0.0.1:8099/.

Шаги: один createRouter + createBrowserHistory + дефолтный historyAdapter, роуты /a,/b; собрать /a → /b через route.open; затем один window.history.back() — больше ничего.

Environment

  • @effector/router: 1.2.0
  • history: 5.3.0
  • Браузер: Chromium (реальный)
  • createMemoryHistory этот дефект НЕ ловит: там back синхронный, нет popstate-revert.

Logs / screenshots

Лог одного цикла:

history.back() @/b
POPSTATE @/a     // back закоммитился в /a
GO(1) @/a        // blocker: revert -> /b
POPSTATE @/b
GO(-1) @/b       // retryNativeFx: transition.retry() -> /a
POPSTATE @/a
GO(1) @/a ... GO(-1) @/b ...   // бесконечно

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions