Skip to main content

Online edition for BLOGE 0.9.8-RC1 · facts verified 2026-09-15 · 中文

Chapter 21 — Scheduling and Complexity in One JVM

Promise: You will use a four-node order graph to see how BLOGE discovers ready nodes, runs work concurrently, waits at a join, and protects the feedback loop with complexity limits.

Learning goals

By the end of this chapter, you can:

  1. Draw how the ready set changes during completion-driven scheduling.
  2. Separate inter-graph concurrency, intra-graph concurrency, and shared-resource capacity.
  3. Explain why DependencyScheduler updates direct descendants instead of rescanning the graph.
  4. Reason about node count, depth, fan-out, and branch nesting.
  5. Record a reproducible single-machine measurement instead of quoting a naked QPS number.

This chapter adds one variable

An order graph follows dependencies. This chapter narrows the scope to one machine, one JVM, and one GraphEngine: when several nodes are eligible, how does the engine move forward?

Leases, shards, remote workers, and database capacity stay in the next chapter. Local scheduling must be visible before distributed concerns are added.

Opening problem: which order step can start

The order flow has four nodes:

loadOrder ─┬─→ checkInventory ─┐
└─→ checkCredit ─┴─→ reserveOrder

After loadOrder, the two checks may finish in either order. reserveOrder must wait for both. Before reading on, write the ready set for each moment.

Figure 21-1: node completion advances the ready set

Observe the contract in BLOGE's own tests

Run the RC1 scheduler and complexity tests:

cd submodule/bloge
mvn -pl bloge-core \
-Dtest=SchedulerSequenceTest,GraphComplexityValidatorTest test

Observed on 2026-09-14 at commit cc38fbe5:

GraphComplexityValidatorTest Tests run: 18, Failures: 0
SchedulerSequenceTest Tests run: 12, Failures: 0
Tests run: 30, Failures: 0, Errors: 0, Skipped: 0

fanInFromMany_waitsForAll uses a latch to bring four predecessors into flight before asserting that the sink runs. multiDiamond_orderingConstraints asserts only required precedence; it does not demand a fixed order between concurrent nodes.

That is the scheduling contract: dependency order is deterministic; completion order within a ready layer need not be.

Mechanism: completion events, not periodic rescans

Each execution owns a DependencyScheduler. Initialization records unfinished dependency counts and builds an outgoingEdges index:

pendingDeps(loadOrder) = 0
pendingDeps(checkInventory) = 1
pendingDeps(checkCredit) = 1
pendingDeps(reserveOrder) = 2

Only loadOrder enters the first ready queue. Completion follows two outgoing edges and reduces both check nodes to zero. One completed check changes reserveOrder from 2 to 1; the second changes it to zero.

Figure 21-2: each execution owns scheduler state; completion touches direct descendants

The important design is not merely “virtual threads.” Progress cost follows the completed node's out-degree. Rescanning every edge after every completion would repeatedly pay for unrelated work.

Do not mix three kinds of concurrency

QuestionOwnerWhat this chapter proves
Many orders at oncehost caller plus engineexecution state is isolated
Parallel checks in one orderdependency schedulereligible nodes may overlap
Database/API loadexternal resource and capacity confignot proven here

A shared GraphEngine does not make downstream pools infinite. A thousand virtual threads waiting on a 20-connection database still have a 20-connection bottleneck.

Complexity limits protect feedback speed

RC1 checks four dimensions while constructing a graph. ComplexityLimits.DEFAULT supplies recommended and hard values:

DimensionRecommendedHardRisk intuition
operator nodes1530larger state and failure surface
DAG depth610longer critical path
fan-out per node58burst amplification
branch nesting23path combinations become opaque

Crossing a recommendation emits a warning; crossing a hard limit rejects the graph. The focused tests cover rejection of 31 nodes, depth 11, and fan-out 9.

Single-factor break: fan-out 8 becomes 9

Keep operators, inputs, and all other edges fixed. Add a ninth child to the root. GraphComplexityValidatorTest asserts a construction failure whose message names the graph, root, hard limit 8, and a decomposition hint.

Recovery options, in order:

  1. Check whether nine tasks belong in one graph or form a subgraph/batch.
  2. Check whether pure projections were incorrectly modeled as operators.
  3. If the business really needs the topology, raise limits explicitly and establish a measurement baseline for it.

Raising a limit makes a graph legal. It does not make it fast or understandable.

Record a trustworthy single-machine measurement

A QPS number needs commit, JDK, CPU quota, heap, graph topology, operator behavior, concurrency, warmup, measurement, fork count, and raw result file. Without them, it is a rumor from one laptop.

Add variables one layer at a time:

empty-operator scheduling cost
→ fixed topology under concurrency
→ realistic operators with controlled dependencies
→ metrics and tracing overhead

Find which layer changed the result before optimizing. A functional test's elapsed time is not a benchmark, and a JMH mean is not a production capacity promise.

Real-world transfer: a restaurant dispatch board

A kitchen does not scan every ticket every second. When prep finishes, it notifies only stations that depend on it. Cold and hot dishes may proceed together; plating waits for both.

  • The ready queue is the set of station cards that can start now.
  • pendingDeps is the number of missing prerequisites.
  • outgoingEdges says whom completion should notify.
  • Complexity limits ask an oversized ticket to become courses or batches.

More cooks do not create more oven capacity. In software, thread concurrency is not external-resource capacity.

Your turn: draw and measure your ready set

Choose a four-to-eight-node graph:

  1. Draw dependencies and mark the initial ready set.
  2. Pick one legal completion order and update pendingDeps frame by frame.
  3. Add one edge or child, predict the complexity result, then run it.
  4. Create a minimal JMH or repeated-measurement record with environment and topology.

Deliver a four-frame ready-set diagram and one measurement card. Stop when someone else can reproduce the run and the card states that the result applies only to this fixed single-machine environment.

What this chapter proved—and did not prove

The RC1 tests prove dependency precedence for the covered topologies, all-predecessor fan-in, and default complexity rejection. They do not prove operator thread safety, external API capacity, database headroom, or a reusable production QPS.

The next chapter adds one variable: Chapter 22 — Distributed Runtime and Capacity Validation examines ownership, routing, and bottlenecks when several JVMs share durable state.

Experiment acceptance card

  • Expected and observed: Ready sets and completion events drive single-JVM scheduling; fan-out warns about feedback cost.
  • Failure and recovery: Raise fan-out from 8 to 9; recover the structure or record acceptance.
  • Proof boundary: Proves scheduling and a complexity signal, not universal QPS.
  • Exercise contract: Four-node graph; add one fan-out edge; deliver a timeline and measurement card; stop with a local single-axis conclusion.

Exact fact entry points

Coding Agent: Open the versioned task guide.