5.9.2 Evaluation Model
recommendedCalling 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
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
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.
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
Footnotes
-
Skyframe — evaluation, dependency requests, restarts, side effects, hermeticity, incrementality, and parallelism ↩1 ↩2 ↩3 ↩4 ↩5 ↩6
-
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 -
Empyrean Evaluation — why production Skyframe uses restartable stackless evaluation instead of blocking dependency calls ↩