What the DataContext Init Time-Box Bounds

Every hub that carries data runs a 120 s time-box around its DataContext initialization (DataContext.OpenInitializationGate). When it expires the hub enters a FAILED state and answers every later request with a terminal DeliveryFailure. The bound is a liveness guarantee and it stays — see Initialization Gates. This page is about what it is actually waiting on, because the timeout used to guess and the guess was wrong.

The wait is nested three deep

DataContext.tasks                                   ← the 120 s time-box waits on this
  └─ IDataSource.Initialized                         = Task.WhenAll(each stream's sub-hub.Started)
       └─ sync/{clientId} sub-hub BuildupAction      = SynchronizationStream.Initialize
            └─ GetInitialValueAsync                  = SelectMany over EVERY type source
                 └─ ITypeSource.Initialize(…).Take(1)     + Aggregate + FirstAsync

Two consequences follow directly from that shape:

For a per-node hub it is one storage read

A default per-node hub has exactly one data source (AddMeshDataSource) with one type source, MeshNodeTypeSource. Its Initialize concatenates a durable read ahead of the routing-supplied node:

DurableSeed()                  IStorageAdapter.Read(hubPath) — NO wall-clock bound, on purpose
  .Concat(_ownNodeStream)       subscribed only after the read settles

The missing bound is deliberate and documented on DurableSeed: a timeout there would let a per-node hub seed from stale routing state, which is the acked-write-loss family. So for a per-node hub the 120 s box is, in practice, a box around one storage read, which queues on the process-wide pg-read gate (cap 16) — see Controlled I/O Pooling for that gate's measured wait profile.

"A stuck NodeType compile" cannot reach this time-box

The timeout message used to end "— likely a stuck NodeType compile, or a data source that never initialised". The first candidate is unreachable on this path:

So the sentence sent every reader towards a mechanism the platform does not implement. Because a LogIncident fingerprint is category + message template + exception type, every cause folded into one issue behind it: 217 occurrences over five weeks, covering at least three populations the text could not tell apart — a pod-wide wave of four hubs timing out inside one millisecond; a mixed _Access / _Activity / _Issue wave; and one user partition whose per-node hubs all went dark for 34 minutes.

The timeout names what it was waiting on

At the instant the box expires every layer of the wait can be inspected, so the message now reports it instead of guessing:

Hub 'sglauser/_Answers/…/Quiz' DataContext initialization did not complete within 120s.
Still waiting on 1 of 1 data source(s): 'MeshNodes (MeshDataSource)' — 1 of 1 stream(s) never
produced a first frame (sync/… stream=… owner=… partition=(none)), type-source legs still
outstanding: …/MeshNode.

The ledger is diagnostic only: nothing waits on it and nothing branches on it. The log template is unchanged, so the existing incident keeps collecting its history while every sample line now carries the cause.

A failed init errors every stream it holds, and creates none

The failure used to be propagated with ds.GetStreamForPartition(null).OnError(failure). That accessor is get-or-create, and the call was wrong twice:

  1. It errored one stream of however many the source held. Every other stream — each partition stream of a partitioned source — was left un-errored, so its subscribers were never told and each waited out an unrelated deadline instead. That is how one stall produced four log sites reporting four different causes: path-resolution timeouts, a broken quiz, and two 120 s bounds, none naming the stall.
  2. On a source with no null-partition stream it minted one. PartitionedHubDataSource.Initialize opens only its declared partitions, so the null key misses, and a SynchronizationStream constructor always builds its sub-hub. The failure path therefore built a hub and a second container for a hub it had just declared FAILED — and the null-partition stream of that source opens one remote stream per declared partition, each starting its own 120 s initialization against the dependency that had just failed to answer.

The failure path now walks IDataSource.OpenStreams — presence only — and errors each one.

What this does not change

Tests

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.