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:
- Draw how the ready set changes during completion-driven scheduling.
- Separate inter-graph concurrency, intra-graph concurrency, and shared-resource capacity.
- Explain why
DependencySchedulerupdates direct descendants instead of rescanning the graph. - Reason about node count, depth, fan-out, and branch nesting.
- 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.
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.
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
| Question | Owner | What this chapter proves |
|---|---|---|
| Many orders at once | host caller plus engine | execution state is isolated |
| Parallel checks in one order | dependency scheduler | eligible nodes may overlap |
| Database/API load | external resource and capacity config | not 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:
| Dimension | Recommended | Hard | Risk intuition |
|---|---|---|---|
| operator nodes | 15 | 30 | larger state and failure surface |
| DAG depth | 6 | 10 | longer critical path |
| fan-out per node | 5 | 8 | burst amplification |
| branch nesting | 2 | 3 | path 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:
- Check whether nine tasks belong in one graph or form a subgraph/batch.
- Check whether pure projections were incorrectly modeled as operators.
- 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.
pendingDepsis the number of missing prerequisites.outgoingEdgessays 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:
- Draw dependencies and mark the initial ready set.
- Pick one legal completion order and update
pendingDepsframe by frame. - Add one edge or child, predict the complexity result, then run it.
- 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
CompletionDrivenScheduler.javaDependencyScheduler.javaSchedulerSequenceTest.javaGraphComplexityValidator.javaGraphComplexityValidatorTest.java- Durable and benchmark reference
Coding Agent: Open the versioned task guide.