Skip to main content

Wait for readiness, not for time

The run must wait when it starts an address before any test uses it. The choice is between a fixed sleep that sometimes loses and a probe that checks the address and records what it waited.

Level 3, lesson 2About 7 minutes
By the end
  • Register a readiness probe after the piece that publishes the address.
  • Read the wait in the run layer of a trace.
  • Say what a sleep would leave in the trace instead.
Before you start
  • Move the test clock (lesson 1).
  • Nothing installed. The archives are on this site.

The scenario​

The browser journey needs a real listener: the page is served over HTTP, so the application cannot run in-process for it. The run starts a loopback instance and publishes its address, and something has to wait until that address answers.

A sleep guesses. On a loaded machine the guess is too short, and on an idle one it wastes the difference. The sample registers a readiness probe instead, and the wait it performs is part of the run's record.

The registration​

The probe sits directly after the listener it probes:

Setup.cs3 notes
1if (run.RunsLocalApplications)
2{
3// The browser needs a real listener; the page journey follows this instance's address.
4builder.AddLoopbackApplication(NorthstarTargets.Web, NorthstarProgram.CreateApp);
5builder.AddHttpReadiness(NorthstarTargets.Web, "/health");
6}
  1. Only when the run hosts the application

    A run pointed at a deployed address skips the listener and the probe entirely.

  2. Start the listener

    The loopback instance binds a free port and publishes the address it got.

  3. Wait for it to answer

    The probe resolves the application's published address and polls /health until it answers. Registering it after the listener is what gives it an address to resolve.

From samples/Northstar.ProtoTest/Setup.cs.

The order is the contract. A probe is awaited at the position it is registered, so a probe registered before the piece that publishes the address has nothing to check. The same shape appears wherever a run starts a real process, including after an AppHost.

The wait in the run layer​

The run layer holds one readiness entity for the loopback instance. From the archive:

AttributeValue
readiness.urlhttp://127.0.0.1:54120/health, the port this run bound
readiness.attempts1
readiness.waitedMs85

The address is the run's own, so it changes between runs. The two numbers are the answer a sleep cannot give: the address answered on the first probe, and the wait cost 85 milliseconds. A run that needs several probes adds attempts, and a slow address shows up as a larger number instead of a mystery failure in the first test that used it.

The entity is released with the run, at the same position the listener is released. That release is a resource.release entry in the run layer of the same archive.

What the tests do instead​

No test in the sample waits for the application. A test starts, takes its client and calls. The waiting happened once, before the first test, and every test after it reads an address the run already checked. Replace a Task.Delay before a request with a readiness probe.

What would a sleep for the same wait leave in the trace instead of the readiness entity?

Verify
Read the test execution spans in l3-clock-window.prototrace. No test span waits for the address.

Checkpoint​

The API journeys run in-process, so they need no address. Why does the run layer still carry a readiness entity?

Verify
Download l3-clock-window.prototrace, open it in the viewer, and read the run screen beside the test screen.

What you learned​

  • A run that starts an application registers a readiness probe for its address.
  • The probe is registered after the piece that publishes the address, because it resolves that address when it runs.
  • Readiness records the attempts and the time waited; a sleep records nothing.

Keep exploring​