Skip to content

Run only one measurement at a time 2.0.5 - #89

Draft
Victor1Ja wants to merge 1 commit into
release-candidate-test-2.0.4from
fix/concurrent-measurements-single-flight
Draft

Run only one measurement at a time 2.0.5#89
Victor1Ja wants to merge 1 commit into
release-candidate-test-2.0.4from
fix/concurrent-measurements-single-flight

Conversation

@Victor1Ja

Copy link
Copy Markdown
Collaborator

Nothing stopped two ndt7 runs from overlapping. Two tests sharing the link measure each other's traffic as congestion, so both report speeds that are too low.

Reproduced by hand on the test build: two manual tests plus the post-registration one, all three running at once.

What changed

One measurement at a timeMeasurementClientService.runTest() is now single-flight.

  • Every trigger funnels through it: the test button, the post-registration first test, the scheduled slots and the startup test. A guard on the button alone would not have caught the first test, which comes from another call path.
  • A request arriving while a run is in flight is dropped, not queued: a measurement that starts late is worth less than the one already running, and queueing would only move the overlap later.
  • The lock is released in a finally, so a failed test frees it too.

The test button is disabled while a run is live.

  • startNDT() also returns before touching the UI. Everything after that point reset progress to 0 and cleared the timers of the test still running — which is what made the app look stuck and invited another tap.
  • Worth knowing: the progress control already gated clicks on progressValue === 0 || 100, which is why the bug looked intermittent. At the very start progress is 0, so the second tap went through.

A scheduled slot waits instead of being lost.

  • If its turn comes up while another measurement is running, decide() leaves the semaphore untouched and returns.
  • The existing 60s tick retries it, so the slot waits for the link to free up. It costs no retry attempt and no backoff, and is not recorded as a failure.

Tests

9 new specs:

  • second run dropped while one is in flight; first blocks the manual path; a scheduled run is held off during a manual one
  • the lock is released after a successful run and after a failed one
  • testRunning$ transitions
  • scheduler: semaphore untouched while another measurement runs, and the run happens once the link is free

Verification

tsc --noEmit -p tsconfig.spec.json clean and ng build clean.

⚠️ The specs have not been executed. ng test cannot run in this checkout: @angular-devkit/build-angular@19.2.17 is installed but its builders.json is missing — the file its own package.json declares under "builders" — so the CLI cannot resolve the karma target. Unrelated to this change; worth a look before merging so CI is not the first place it runs.

Not included

The startup test still has two known ways to schedule on top of a slow run: its 10-minute guard window is shorter than a slow ndt7, and the day is only marked after a successful run, so a failure leaves it to be rescheduled on the next tick. Both are far rarer than the overlap fixed here and are better handled together with the retry policy.

Nothing stopped two ndt7 runs from overlapping. Two tests sharing the link
measure each other's traffic as congestion, so both report speeds that are
too low. Reproduced by hand: two manual tests plus the post-registration
one, all three at once.

- MeasurementClientService.runTest() is now single-flight. A request that
  arrives while a run is in flight is dropped rather than queued: a
  measurement that starts late is worth less than the one already running,
  and queueing would only move the overlap later. Every trigger goes
  through here, so it covers the manual button, the post-registration test
  and both scheduled paths.

- The test button is disabled while a run is live. startNDT() also returns
  before touching the UI: everything after that point reset progress and
  cleared the timers of the test that was still running, which made the app
  look stuck and invited another tap.

- A scheduled slot whose turn comes up during another measurement leaves
  its semaphore untouched and returns. The 60s tick retries it, so the slot
  waits for the link instead of being lost, and it costs no retry attempt
  and no backoff.

Assisted-by: Claude Opus 5
@Victor1Ja Victor1Ja changed the title Run only one measurement at a time Run only one measurement at a time 2.0.5 Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant