5.9.6 Node Restart Cost

extra

A restart re-invokes one internal Java SkyFunction.compute method. It does not restart the Bazel server. 5.9.2 Evaluation Model explains the full lookup and restart protocol. The detail needed here is that returning null ends the current invocation and releases its evaluator thread. A later invocation starts the method again, so serial dependency discovery can replay progressively more work.1,2

N restarts can produce quadratic work

Suppose an internal function processes N dependency keys, but stops at the first missing value on every invocation:

for (SkyKey key : keys) {
  SkyValue value = env.getValue(key);
  if (env.valuesMissing()) {
    return null;
  }
  consume(value);
}

If none of the values is initially ready, the first invocation reaches key 1. The next replays key 1 and reaches key 2. Later invocations replay progressively longer prefixes. This trace assumes dependencies complete successfully. Code that needs to recover from or enrich a dependency error uses the appropriate getValueOrThrow form rather than assigning every null lookup the meaning “not ready.” The node may restart N times, while the number of loop visits grows like 1 + 2 + ... + N: O(N²) repeated work. It is the time cost, not the number of restarts, that is quadratic.2,3

Previously requested values are generally available on the later invocation, so this pattern still makes progress. That does not make the replay cheap: key construction, validation, transformation, event preparation, and other work before the next missing lookup may all run again.1,4

Request independent dependencies as one group

When all keys are known and one result cannot change which other keys are needed, request them as a dependency group. The public Skyframe documentation calls this operation env.getValues(). In current Bazel Java source, the corresponding SkyFunction.LookupEnvironment entry point is getValuesAndExceptions(Iterable<SkyKey>), which returns a SkyframeLookupResult. The exact Java interface is internal and may evolve. The durable design rule is to group independent lookups.1,3

SkyframeLookupResult results = env.getValuesAndExceptions(keys);

List<SkyValue> values = new ArrayList<>();
for (SkyKey key : keys) {
  values.add(results.get(key));
}
if (env.valuesMissing()) {
  return null;
}
for (SkyValue value : values) {
  consume(value);
}

This compact example again assumes successful dependencies. An implementation that handles dependency failures queries each result with the matching getOrThrow overload and declared exception type.3

This first invocation declares the whole group, allowing its missing members to be evaluated in parallel. Once all values requested in that invocation are ready, the function can restart and consume them. “At most one restart” is a useful model for this simple case, not a universal guarantee: errors, partial re-evaluation, external futures, and later data-dependent lookups can require more evaluation passes.1,3

Bulk lookup is not correct when an earlier result determines whether a later key is needed. Requesting every possible key would record dependencies the computation did not actually require. Current source documentation gives this decision rule: group lookups that are independent of one another. Keep truly data-dependent discovery in separate groups.3

When discovery is genuinely sequential

Some computations cannot know the next key in advance, such as following a chain of symbolic links. In internal Bazel Java code, the alternatives are to reduce replay by splitting the computation into smaller Skyframe nodes, or to checkpoint temporary computation with SkyKeyComputeState and structured Skyframe state machines. Cached state is a performance optimization rather than a correctness requirement: Skyframe may discard it, so the function must still be able to recompute correctly.1,4,3

That yields a practical internal review sequence:

  1. Group every set of keys already known to be independently required.
  2. Separate discovery into smaller nodes when that gives each node a bounded restart path.
  3. Use compute state or state machines when expensive sequential work must span restarts, while preserving correct behavior if the state is dropped.

This is internal Java guidance, not a Starlark rule pattern

Starlark rule implementations do not receive SkyFunction.Environment, cannot call getValue, getValues, or getValuesAndExceptions, and do not manually checkpoint a SkyFunction. Their dependency targets and providers arrive through declared attributes before the implementation registers the actions described in 4.2.2 Actions. The public rules API still presents loading, analysis, and execution concepts rather than exposing Skyframe's dependency discovery protocol.2

Consequently, the restart mechanism does not directly imply that a Starlark rule author should “collect SkyKeys” or avoid conditionals over provider data. For Starlark, use the supported declarative interfaces: declare attributes and action inputs, consume providers, and preserve transitive collections as depsets where appropriate. The separate O(N²) problem caused by repeatedly flattening or copying transitive collections is covered by 4.1.5 depset vs list. It should not be confused with SkyFunction restart replay.2

key takeaway

A serially discovering internal SkyFunction may restart O(N) times yet do O(N²) total replay work. Returning null ends the current invocation and frees its evaluator thread. The function is invoked again later rather than remaining suspended on that thread.

Internal Bazel code should group independent dependency requests, split nodes, or checkpoint expensive sequential computation. These are Java Skyframe implementation techniques, not APIs available to Starlark rule authors.

Check your understanding · 3 questions

1.An internal SkyFunction discovers N unavailable dependencies one at a time and replays the loop prefix after each restart. What grows quadratically?

Select one answer

2.Which statements correctly describe a Skyframe restart?

Choose True or False for each sentence

The incomplete invocation keeps its evaluator thread while dependencies run.
Returning null ends the invocation, and Skyframe invokes the function again later.
The later invocation may run on a different evaluator thread.
A restart means Bazel discarded the whole in-memory graph.

3.Which responses fit expensive internal dependency discovery?

Select all that apply

0 of 3 answered

Footnotes

  1. Skyframe — missing-value restart protocol, multi-key requests, and genuinely multi-pass dependency discovery 1 2 3 4 5

  2. Challenges of Writing Rules — fixed-size pool, O(N) restarts with O(N²) time, bulk declaration, and the boundary of the public rules API 1 2 3 4

  3. Bazel upstream source — current SkyFunction.LookupEnvironment dependency-group API and SkyKeyComputeState contract (verified 18 July 2026) 1 2 3 4 5 6

  4. Empyrean Evaluation — restart replay, compute-state caching, state machines, and memory/recomputation trade-offs 1 2