Repository navigation
Advance skipped steps in a loop instead of recursing - #983
Open
theluckystrike wants to merge 1 commit into
Open
theluckystrike wants to merge 1 commit into
theluckystrike wants to merge 1 commit into
Conversation
`NavigationController::update_user_location` called itself after every step advance to evaluate the same location against the next step (the "speed run" behavior from stadiamaps#715). One update that lands N steps ahead of the current step therefore nested N frames deep. On a long route that overflows the small stacks mobile platforms give navigation threads and aborts the process (stadiamaps#964). I moved the existing body unchanged into `update_user_location_once`, which evaluates the current step, advances at most once and returns `ControlFlow::Continue` when the same location needs another pass. `update_user_location` now loops on that result. The arrival, completion and pass through paths return the same states as before. Only the stack depth changes. The moved body keeps its indentation, so its diff is the return values only. The new test `test_speed_run_skipping_many_steps_uses_bounded_stack` builds a 1,000 step route with the configuration from stadiamaps#964, starts at step 0 and sends one update in the middle of step 800 on a thread with a 256 KiB stack. It asserts that step 800 becomes the current step. Commands I ran in `common/` on macOS arm64 with rustc 1.99.0. Before the fix (main 4e2d7f6 plus the new test) the test aborts the process. ``` $ cargo test -p ferrostar --lib test_speed_run_skipping_many_steps_uses_bounded_stack thread 'speed-run-stack' has overflowed its stack fatal runtime error: stack overflow, aborting (signal: 6, SIGABRT: process abort signal) $ cargo nextest run -p ferrostar --no-fail-fast SIGABRT ferrostar navigation_controller::tests::test_speed_run_skipping_many_steps_uses_bounded_stack Summary 114 tests run: 113 passed, 1 failed, 0 skipped ``` With the fix every check passes. ``` $ cargo test -p ferrostar --lib test_speed_run_skipping_many_steps_uses_bounded_stack test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 110 filtered out; finished in 1.13s $ cargo test -p ferrostar --lib test result: ok. 111 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (no .snap.new files, no snapshot changes) $ cargo build --verbose exit 0 $ cargo nextest run --no-fail-fast Summary 114 tests run: 114 passed, 0 skipped $ cargo test --doc test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out $ cargo fmt --check exit 0 $ cargo clippy -p ferrostar --all-targets 104 warnings on main, 104 with this change ``` I also ran the recipe from stadiamaps#964 through the public API in a throwaway integration test that isn't part of this commit. It builds a 1,000 step route of about 205 km with `step_advance_distance_from_step(35, 20, WhileOnRoute)` and `StaticThreshold { 20, 30.0 }`. The test calls `get_initial_state` at the route start. Then it sends one `update_user_location` on step 500 and one on step 990. Both run on a 512 KiB thread, the iOS default. On main the debug and release builds both abort. ``` thread 'ios-sized-512k' has overflowed its stack fatal runtime error: stack overflow, aborting (signal: 6, SIGABRT: process abort signal) ``` With this change both builds return the right step. ``` debug target step 500: current step index 500, instruction "step 500" target step 990: current step index 990, instruction "step 990" release target step 500: current step index 500, update took 255 ms target step 990: current step index 990, update took 416 ms ``` The same test on a 256 MiB stack dumped 27 states. Each run jumped to step 0, 1, 2, 3, 50, 200, 997, 998 or 999 and then sent two updates at the route end. The dump holds remaining steps, waypoints, deviation, progress and snapped location. The files from main and from this change are byte identical. Not run are `just check-msrv` (cargo hack isn't installed here), the wasm build and the Swift, Kotlin and web suites. This change touches only Rust code inside the ferrostar crate and no bindings. The per step copying of the remaining steps that stadiamaps#964 also describes is unchanged. The release timings above come from it. Reducing it is a separate change.
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.
Fixes #964
update_user_locationadvanced through skipped steps by calling itself once per step (the "speed run" from #715). One update on a long route nested hundreds of frames deep and aborted with a stack overflow. This change keeps the advance logic and runs it in a loop, so the stack depth no longer grows with the number of skipped steps.What changed
navigation_controller/mod.rsmoves the existing body ofupdate_user_locationunchanged intoupdate_user_location_once. It evaluates the current step, advances at most once and returnsControlFlow::Continuewhen the same location needs another pass.update_user_locationloops on that result instead of recursing. Arrival handling, waypoint trimming, deviation checks and the condition state reset are untouched.Test
test_speed_run_skipping_many_steps_uses_bounded_stackfollows the recipe from the issue. It builds a 1,000 step route, starts at step 0 and sends one location on step 800 on a thread with a 256 KiB stack. It asserts that step 800 becomes current.On
mainplus the test, the test aborts the process.Under nextest the abort fails only that test (113 passed, 1 failed).
End to end
I ran the issue's recipe through the public API in a throwaway integration test that isn't part of the PR. It uses
create_navigatorwithstep_advance_distance_from_step(35, 20, WhileOnRoute)andStaticThreshold { 20, 30.0 }on a 1,000 step route of about 205 km. It callsget_initial_stateat the route start and then oneupdate_user_locationon step 500 and on step 990.Each update runs on a 512 KiB thread, the iOS default for secondary threads. On
mainboth debug and release builds overflow the stack and abort. With this change the update returns with step 500 (and step 990) current, and a release build takes 255 ms and 416 ms for those two updates.To confirm the loop changes nothing else, the same test on a large stack dumped 27 states from 9 jump targets (steps 0, 1, 2, 3, 50, 200, 997, 998 and 999, each followed by two updates at the route end). The dumps from
mainand from this branch are byte identical.The per step copying of the remaining steps that the issue also describes is unchanged. The release timings above come from it. Skipping several steps in one pass is a separate design decision and stays out of this PR.
Checks
cargo test -p ferrostar --lib test_speed_run_skipping_many_steps_uses_bounded_stackaborts onmainand passes herecargo test -p ferrostar --lib111 passed, no snapshot changescargo build --verbose,cargo nextest run --no-fail-fast(114 passed) andcargo test --doc(8 passed) incommon/cargo fmt --checkincommon/, andcargo clippy -p ferrostar --all-targetswith 104 warnings onmainand 104 herejust check-msrv(cargo hack isn't installed here)