Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(router): lazy loaded modules without RouterModule.forChild() won't cause infinite loop #36605

Closed
wants to merge 1 commit into from

Conversation

martinsik
Copy link
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #29164

When loading a module that doesn't provide RouterModule.forChild() preloader will get stuck in a infinite loop and throw ERROR Error: Maximum call stack size exceeded.

https://stackblitz.com/edit/angular-issue-repro2-atwzck?file=src%2Fapp%2Ffeature1%2Ffeature1.module.ts

The issue is that child module's Injector will look to its parent Injector when it doesn't find any ROUTES so it will return routes for it's parent module instead. This will load the child again that returns its parent's routes and so on.

What is the new behavior?

Lazy loaded modules without RouterModule.forChild() won't break the app.

I was thinking that this might throw an exception telling me that I'm lazy loading a module that doesn't define any routes but maybe it is a valid use-case (for example I'm loading a module that only does some logic in its constructor).

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

I only changed one line and added one test that would fail with Error: 1 timer(s) still in the queue. without this change:

https://github.com/angular/angular/compare/master...martinsik:issue-29164?expand=1#diff-2a29f955e9e56e86ee957a9768a19929R44

All other changes were made by running yarn gulp format

Closes #29164

@atscott
Copy link
Contributor

atscott commented Apr 13, 2020

Hi @martinsik, thanks for another router fix! Can you update the commit message to match what you have in the "current behavior" section (without the stackblitz link) and also add a similar comment to the LoadedRouterConfig line that you changed to describe why InjectFlags.Self is required (again, pretty much exactly what you have in the current behavior section)?

@atscott
Copy link
Contributor

atscott commented Apr 13, 2020

presubmit

@atscott atscott added target: patch This PR is targeted for the next patch release action: presubmit The PR is in need of a google3 presubmit action: review The PR is still awaiting reviews from at least one requested reviewer type: bug/fix labels Apr 13, 2020
@martinsik martinsik force-pushed the issue-29164 branch 3 times, most recently from 09504bd to 9b8c49c Compare April 18, 2020 09:38
@martinsik
Copy link
Contributor Author

Commit message updated.

@atscott
Copy link
Contributor

atscott commented Apr 27, 2020

Presubmit is showing internal test failures when running with Ivy. Needs more investigation.

@martinsik
Copy link
Contributor Author

@atscott Hey, pls do you have any update on this :)?

return new LoadedRouterConfig(
flatten(module.injector.get(ROUTES)).map(standardizeConfig), module);
flatten(module.injector.get(ROUTES, undefined, InjectFlags.Self)).map(standardizeConfig),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
flatten(module.injector.get(ROUTES, undefined, InjectFlags.Self)).map(standardizeConfig),
flatten(module.injector.get(ROUTES, undefined, InjectFlags.Self|InjectFlags.Optional)).map(standardizeConfig),

Copy link
Contributor

@atscott atscott Jul 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The failure that was found happened because your test is only verifying behavior on the preloader, which has a catchError line

return fn().pipe(catchError(() => of(null)));

Your change introduces a newly thrown error that would have otherwise not happened. The following test will fail if added to the integration.spec.ts (please add this test):

      @NgModule({})
      class EmptyModule{}

      fit('should allow navigation to modules with no routes', fakeAsync(() => {
        (TestBed.inject(NgModuleFactoryLoader) as SpyNgModuleFactoryLoader).stubbedModules = {
          empty: EmptyModule,
        };
        const router = TestBed.inject(Router);
        const fixture = createRoot(router, RootCmp);

        router.resetConfig([{path: 'lazy', loadChildren: 'empty'}]);

        router.navigateByUrl('/lazy');
        advance(fixture);
      }));

I'm not exactly sure what the implications are for this. My understanding of the change you're making is that previously, navigation to this route would have inherited the parent routes. Even though it's infinitely circular, the problem wouldn't really surface unless there was preloading involved. I don't know exactly how this type of route is used so I don't know if the InjectFlags.Self change negatively affects how that works. (Edit: potentially, this is something that enables a hybrid app to handle the route in AngularJS and also do some work on the Angular side, but that's just a shot in the dark.)

Please add the InjectFlags.Optional and I'll run more tests to see if anything comes up. Thank you!

packages/router/test/router_preloader.spec.ts Show resolved Hide resolved
@atscott atscott added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews and removed action: presubmit The PR is in need of a google3 presubmit action: review The PR is still awaiting reviews from at least one requested reviewer state: blocked labels Aug 6, 2020
@martinsik
Copy link
Contributor Author

@atscott Hi, I addressed what you mentioned above and also added the test you made into integration.spec.ts.

packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
packages/router/test/router_preloader.spec.ts Outdated Show resolved Hide resolved
@atscott atscott added action: presubmit The PR is in need of a google3 presubmit and removed action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews labels Jan 13, 2021
@ngbot ngbot bot modified the milestones: needsTriage, Backlog Jan 13, 2021
@atscott atscott added the action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews label Jan 13, 2021
@atscott
Copy link
Contributor

atscott commented Jan 13, 2021

global presbumit

…t cause an infinite loop

When loading a module that doesn't provide `RouterModule.forChild()` preloader will get stuck
in an infinite loop and throw `ERROR Error: Maximum call stack size exceeded.`
The issue is that child module's `Injector` will look to its parent `Injector` when it doesn't
find any `ROUTES` so it will return routes for it's parent module instead. This will load the
child again that returns its parent's routes and so on.

Closes angular#29164
@atscott atscott added target: rc This PR is targeted for the next release-candidate action: merge The PR is ready for merge by the caretaker merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note and removed action: presubmit The PR is in need of a google3 presubmit action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews target: patch This PR is targeted for the next patch release labels Jan 14, 2021
@atscott
Copy link
Contributor

atscott commented Jan 15, 2021

caretaker note: just to be safe, let's merge and sync this one on its own. I don't anticipate issues as a result of this fix, but you never know.

AndrewKushnir pushed a commit that referenced this pull request Jan 15, 2021
…t cause an infinite loop (#36605)

When loading a module that doesn't provide `RouterModule.forChild()` preloader will get stuck
in an infinite loop and throw `ERROR Error: Maximum call stack size exceeded.`
The issue is that child module's `Injector` will look to its parent `Injector` when it doesn't
find any `ROUTES` so it will return routes for it's parent module instead. This will load the
child again that returns its parent's routes and so on.

Closes #29164

PR Close #36605
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Feb 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: router cla: yes merge: caretaker note Alert the caretaker performing the merge to check the PR for an out of normal action needed or note target: rc This PR is targeted for the next release-candidate type: bug/fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Infinite loop during preload if router configuration missed in preloaded module
3 participants