5.9.2 Evaluation Model

recommended

Calling a dependency from a SkyFunction does not behave like an ordinary blocking Java call. The function asks its SkyFunction.Environment for another SkyKey. If the dependency is ready, it receives the value. If required data is still missing, the function yields by returning null. Skyframe evaluates the requested dependencies and later invokes compute(key, env) again.1,2

A dependency lookup may continue now or restart later
Environment records the edge in both cases. Missing data yields the computation without blocking its Java thread.
REQUEST
The function asks through Environment
the dependency edge is recorded
env.getValue(depKey)
LOOKUP RESULT
VALUE READY
Continue compute()
Use this value. The function may request more dependencies before it can finish.
keep computing
OR
VALUES MISSING
Yield this computation
Check the aggregate Environment signal, then return null
valuesMissing()
RESTART AFTER DEPENDENCIES FINISH
Skyframe evaluates missing nodes, then invokes compute() from the start
This is a computation restart, not a Bazel server restart
compute(key, env)
Restart resumes the computation, not its Java stack. Work before the missing lookup may run again.

This protocol is how an implementation declares the edges introduced in 5.9.1 Skyframe Data Model while the graph is being discovered. It also explains three otherwise surprising properties of Skyframe: a function can run more than once for one key, independent dependencies can make progress concurrently, and an unregistered read can make an incremental result wrong.

Read Environment as a dependency-tracking interface

The essential shape of a computation is:

SkyValue compute(SkyKey key, Environment env) throws ... {
  SkyValue dep = env.getValue(dependencyKey(key));
  if (env.valuesMissing()) {
    return null;
  }
  return buildValue(dep);
}

This is illustrative Java, not a public extension API. getValue() both asks for a value and registers a direct dependency of the current key. It returns a SkyValue when available. A null result requires care: current source says it can mean that the dependency is not yet evaluated or that it evaluated with an error that this lookup form does not expose. Code should therefore follow the environment's aggregate signal, valuesMissing(), rather than inventing a meaning for one null in isolation.2

getValueOrThrow() variants let a function handle declared checked exception types from a dependency. For several independent keys, the current bulk API is getValuesAndExceptions(), which returns a per-key lookup result. Older high-level documentation calls this operation getValues(). Treat that name as the conceptual bulk request, not the exact current method signature.1,2

The distinction is between dependency discovery and dependency availability. Calling a lookup declares what the current computation needs. The call does not promise that the dependency can be supplied during this invocation.

A missing dependency suspends the node, not the thread

When any lookup records missing data, valuesMissing() becomes true. The function must return null, unless it is throwing a SkyFunctionException for an error it actually encountered. Skyframe then computes the requested dependencies and re-enqueues the current node after they finish. The later call starts compute() again. Skyframe does not resume the Java stack at the lookup site.2,3

That is a Skyframe restart. It is normal control flow, not a Bazel server restart and not the rare SkyFunction.Reset mechanism used to rewind evaluator state. A completed computation instead returns a non-null SkyValue.2

The practical rule is stronger than “the final result is deterministic.” Code before a missing lookup may execute again. Intermediate work must either be safe to repeat or be treated as an optimization and stored through env.getState(). Even that state is not guaranteed to survive every reinvocation, so correctness cannot depend on it. Events buffered for replay may also be discarded when a function returns null and need to be emitted again on restart.2

think

Trace: Why does Skyframe invoke compute() again instead of blocking the evaluator thread until a dependency finishes?

Reveal

Yielding frees evaluator capacity for the dependency and other ready nodes. The restarted function reconstructs its local control flow after the inputs arrive.

Requests may arrive singly or as a group

An implementation can discover dependencies in successive passes or request a group through getValuesAndExceptions(). Grouping independently known values can expose work the evaluator may schedule concurrently, while later keys may legitimately depend on earlier results.1,2 5.9.6 Node Restart Cost develops the performance consequences, decision rule, and techniques for avoiding repeated replay.

The guarantees depend on implementation discipline

Dependency tracking gives the evaluator the information needed for three useful properties:

  • Incrementality: recorded edges tell Skyframe which computations may be affected when an input changes.
  • Parallelism: computations without dependency ordering can be scheduled concurrently, and bulk dependency groups expose more ready work.
  • Hermetic evaluation: for a hermetic function, the result is a deterministic function of its tracked dependencies.1,2

None of those statements means that the Java interface prevents all unsafe behavior. A function that reads a file or environment variable directly creates a hidden input, so Skyframe may reuse its old value after that input changes. The defect is not merely philosophical “non-hermeticity”. It is missing graph information and therefore potentially incorrect incrementality.1

Nor are all real SkyFunctions universally pure. Bazel distinguishes hermetic, semi-hermetic, and non-hermetic function families. The official model permits carefully coordinated outward effects such as writing output files, while warning that unregistered inward reads are unsafe. Effects can also require synchronization, so “independent nodes may evaluate concurrently” does not imply that implementations need no locks or cannot interfere. The safe contract is narrower: track every result-affecting input, use explicit non-hermetic machinery where necessary, and coordinate outward effects so concurrent computations do not collide.1,2

This distinction is useful in an investigation. If an internal value appears stale, ask which Environment lookup should have represented the changed input. If evaluation is unexpectedly serial, ask whether independent keys were split into ordered dependency groups. If a computation repeats expensive work, first look for a legitimate missing-dependency restart before treating the repetition as duplicate node evaluation.

key takeaway

SkyFunction.Environment is a dependency-tracking protocol. Lookups declare direct dependencies. Available dependencies return values, while missing data sets valuesMissing() and normally makes compute() return null. Skyframe then evaluates the requested nodes and invokes the function again from the start, so correctness must tolerate repeated execution.

Requests may be individual or grouped. 5.9.6 Node Restart Cost develops the performance choice. The recorded edges enable correct invalidation and concurrent scheduling only when result-affecting inputs are tracked. Controlled writes and explicitly non-hermetic functions exist, so the accurate claim is not “all SkyFunctions are pure and lock-free,” but “hidden reads and uncoordinated effects break the evaluator's reasoning.”

Check your understanding · 3 questions

1.What should a SkyFunction normally do after a lookup makes valuesMissing() true?

Select one answer

2.What can a plain getValue() result of null mean in the current Environment contract?

Select one answer

3.True or false: what follows from Skyframe's evaluation protocol?

Choose True or False for each sentence

Code before a missing lookup may execute again after a restart.
Every real SkyFunction is necessarily pure and free of outward effects.
A direct untracked file read can produce incorrect incremental reuse.
Concurrent scheduling guarantees implementations never need synchronization.
0 of 3 answered

Footnotes

  1. Skyframe — evaluation, dependency requests, restarts, side effects, hermeticity, incrementality, and parallelism 1 2 3 4 5 6

  2. Bazel upstream source — current SkyFunction, LookupEnvironment, Environment, dependency-group, valuesMissing(), restart, event-replay, and hermeticity contracts (verified 18 July 2026) 1 2 3 4 5 6 7 8 9

  3. Empyrean Evaluation — why production Skyframe uses restartable stackless evaluation instead of blocking dependency calls