Lab 4.1: Read the VertexImpl State Machine
Background
VertexImpl.java (tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java)
is the most complex class in Apache Tez — roughly 6,000 lines. It holds the complete state
machine for vertex execution: initialization, root-input handling, scheduling, task-completion
accounting, failure and kill cascades, commit, and AM recovery. Everything the Application
Master knows about "what a vertex is doing right now" lives in one Hadoop
StateMachineFactory and a family of transition-handler inner classes.
This is a code-reading lab, the Tez analog of reading the OpenSearch Coordinator. You will
not change code. You will end with a transition table you built yourself from the source, plus
answers that prove you understood the machine — not just the file names. The skill being trained
is reading a large state machine structurally (factory → states → events → transitions →
handlers) rather than linearly top-to-bottom.
Keep three deep-dives open beside this lab: state-machines.md
(how Hadoop's StateMachineFactory works), vertex-lifecycle.md
(the narrative walk-through), and event-routing.md (how events
reach the vertex). The Level 4 overview has the class table you should keep pinned.
A note on line numbers. This lab never cites line numbers — they drift every release. It cites class names, method names, states, and events, all of which are stable. Every step gives you a
grepto find the code on your checkout. Learn the names, not the offsets.
Why This Lab Matters for Contributors
The scariest issues in the Tez tracker are vertex-lifecycle bugs: "DAG hangs with one vertex stuck
in RUNNING", "vertex went to SUCCEEDED then back to RUNNING", "kill during initialization
leaks containers", "recovery replays a vertex into the wrong state". A maintainer triaging any of
these opens VertexImpl, finds the relevant addTransition entry, and reads the handler. If you
cannot navigate this file you cannot reproduce, review, or fix those issues — and you cannot review
anyone else's patch that touches the state machine, which is where the highest-value review happens.
This lab is also the foundation for Lab 4.2 (the VertexManager hook
that StartTransition invokes) and both build/fix labs
(4.3, 4.4).
Prerequisites
-
A Tez checkout that builds (
mvn install -DskipTestssucceeds at least once). -
VertexImpl.javaopen in an editor with "go to symbol" / outline support. -
grep/awkon the command line, run from the repo root. -
You have read state-machines.md and understand the
difference between
SingleArcTransition(one fixed destination) andMultipleArcTransition(destination computed at runtime). - A reading log to append to as you go:
mkdir -p ~/tez-notes && : > ~/tez-notes/reading-log-4.1.md
How to Read a Large State Machine Class
Do not read VertexImpl.java from top to bottom. Read it in this order:
- The
StateMachineFactorydeclaration — the single source of truth for every transition. - The two state enums — the internal
VertexState(the states the machine moves through) and the event enumVertexEventType(the inputs). - The transition matrix — every
addTransition(...)call, grouped by source state. - The handler classes — the inner classes implementing
SingleArcTransition/MultipleArcTransition, whosetransition()methods contain the actual logic. - Inter-machine events — where a handler calls
eventHandler.handle(...)to post an event to another state machine (TaskImpl,DAGImpl, target vertices).
Step-by-Step Tasks
Step 1 — Find the factory and read its opening
grep -n "stateMachineFactory\|StateMachineFactory" \
tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java | head
The declaration opens like this (quoted from module tez-dag, class VertexImpl):
StateMachineFactory<VertexImpl, VertexState, VertexEventType, VertexEvent>
stateMachineFactory
= new StateMachineFactory<VertexImpl, VertexState, VertexEventType, VertexEvent>
(VertexState.NEW)
// Transitions from NEW state
.addTransition
(VertexState.NEW,
EnumSet.of(VertexState.NEW, VertexState.INITED,
VertexState.INITIALIZING, VertexState.FAILED, VertexState.KILLED),
VertexEventType.V_INIT,
new InitTransition())
Read the four type parameters: operand (VertexImpl), state (VertexState), event type
(VertexEventType), event (VertexEvent). The single constructor argument, VertexState.NEW,
is the initial state of every vertex. The chain of .addTransition(...) calls that follows is the
entire machine.
Log it: the factory's four type parameters and the initial state.
Step 2 — Read the state enum (and notice the naming trap)
There are two VertexState enums, and confusing them is a classic beginner mistake. Confirm
which one the state machine uses:
grep -n "import.*VertexState;" \
tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java
You will find import org.apache.tez.dag.app.dag.VertexState; — the internal enum. Read it:
cat tez-dag/src/main/java/org/apache/tez/dag/app/dag/VertexState.java
It has exactly ten states:
public enum VertexState {
NEW, INITIALIZING, INITED, RUNNING, SUCCEEDED,
FAILED, KILLED, ERROR, TERMINATING, COMMITTING,
}
The other one — org.apache.tez.dag.api.event.VertexState — is a public API enum
(SUCCEEDED, RUNNING, FAILED, KILLED, PARALLELISM_UPDATED, CONFIGURED, INITIALIZING) used only to
notify VertexManager plugins and listeners via VertexStateUpdate. The state machine never
transitions through CONFIGURED or PARALLELISM_UPDATED — those are notifications, not machine
states. Record both enums in your log and note that the machine uses the internal one.
Step 3 — Count states, events, and transitions
# Total transitions
grep -c "addTransition" \
tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java
# Distinct source states
grep "addTransition(VertexState\." \
tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java \
| sed -E 's/.*addTransition\(VertexState\.([A-Z_]+).*/\1/' | sort -u
Record your numbers. On current master this is on the order of 60 transitions across the ten
states. (Some addTransition calls register a set of ignored events in one call, so "distinct
source states" is a cleaner count than raw transitions.)
Step 4 — List the handler classes
grep -n "implements SingleArcTransition\|implements MultipleArcTransition\|static class .*Transition" \
tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java
You will find the real handler set. The load-bearing ones for this lab:
| Handler class | Kind | Role |
|---|---|---|
InitTransition | MultipleArc | Handles V_INIT; sets up the vertex, may go straight to INITED |
VertexInitializedTransition | MultipleArc | V_READY_TO_INIT: INITIALIZING → INITED (or FAILED) |
StartTransition | MultipleArc | V_START: INITED → RUNNING; calls the VertexManager |
TaskCompletedTransition | MultipleArc | V_TASK_COMPLETED: the completion-accounting core |
TaskRescheduledTransition | SingleArc | A succeeded task is restarted; decrements counters |
VertexNoTasksCompletedTransition | MultipleArc | V_COMPLETED for zero-task vertices |
TerminateNewVertexTransition | SingleArc | Kill in NEW → KILLED |
TerminateInitedVertexTransition | SingleArc | Kill in INITED → KILLED |
TerminateInitingVertexTransition | SingleArc | Kill in INITIALIZING → KILLED (extends the INITED one) |
VertexKilledTransition | SingleArc | V_TERMINATE in RUNNING → TERMINATING |
VertexKilledWhileCommittingTransition | SingleArc | V_TERMINATE in COMMITTING → TERMINATING |
VertexManagerUserCodeErrorTransition | SingleArc | Plugin threw; drive to failure |
RecoverTransition | MultipleArc | V_RECOVER from NEW during AM restart |
There are also five shared constant transitions instantiated once and reused (search for
INTERNAL_ERROR_TRANSITION =, ROUTE_EVENT_TRANSITION =, TASK_ATTEMPT_COMPLETED_EVENT_TRANSITION =,
SOURCE_TASK_ATTEMPT_COMPLETED_EVENT_TRANSITION =, COMMIT_COMPLETED_TRANSITION =). These map to
InternalErrorTransition, RouteEventTransition, TaskAttemptCompletedEventTransition,
SourceTaskAttemptCompletedEventTransition, and CommitCompletedTransition respectively.
Note on RECOVERING. There is no
RECOVERINGstate in the internal enum. Recovery is not a separate state — it is theRecoverTransitionregistered onNEWfor theV_RECOVERevent, which replays persisted history and lands the vertex directly inINITED,SUCCEEDED,FAILED,KILLED, orERROR. If you expected recovery states from other schedulers, this is where Tez differs. Note it in your log.
Step 5 — Build the transition matrix for THREE real flows
For each flow, derive every row from the real addTransition entries and read the handler's
transition() method. Produce a table Source | Event | Destination(s) | Handler.
Flow A — Happy path (root vertex: init → running → succeeded)
Derived from the NEW, INITIALIZING, INITED, RUNNING, COMMITTING blocks:
| Source | Event | Destination(s) | Handler |
|---|---|---|---|
NEW | V_INIT | INITIALIZING / INITED / FAILED / KILLED | InitTransition |
INITIALIZING | V_READY_TO_INIT | INITED / FAILED | VertexInitializedTransition |
INITED | V_START | RUNNING / INITED / TERMINATING | StartTransition |
RUNNING | V_TASK_COMPLETED | RUNNING / COMMITTING / SUCCEEDED / TERMINATING / FAILED / ERROR | TaskCompletedTransition |
COMMITTING | V_COMMIT_COMPLETED | SUCCEEDED / COMMITTING / TERMINATING / FAILED | CommitCompletedTransition |
Now answer from the code:
InitTransition.transition()returnsVertexState.NEWinitially and only callshandleInitEvent(vertex)once all source vertices have inited (numInitedSourceVertices == sourceVertices.size()). A root vertex has no source vertices, so it inits immediately. Confirm: what does it do to target vertices when init succeeds? (It posts aV_INITVertexEventto each of them — the init wavefront propagates down the DAG.)StartTransition.transition()asserts the state isINITED, then returnsvertex.startVertex(). ReadstartVertex(): the crucial line isvertexManager.onVertexStarted(...)— this is the hook Lab 4.2 dissects. Is it blocking? (It is a synchronous call into user plugin code; a throw drives the vertex toTERMINATING.)TaskCompletedTransition— read Flow B below; the same handler covers success and failure.- The transition to
COMMITTINGvsSUCCEEDEDis decided incheckTasksForCompletion(vertex)(astatic VertexStatehelper). It only fires whencompletedTaskCount == tasks.size(), then returnsCOMMITTINGif the vertex has outputs to commit, else finishes asSUCCEEDED.
Flow B — A task failure cascading to vertex failure
TaskCompletedTransition is a MultipleArcTransition — its destination is computed. Quote the
core of its transition() (module tez-dag, class VertexImpl, inner class
TaskCompletedTransition):
} else if (taskEvent.getState() == TaskState.FAILED) {
taskFailed(vertex, task);
if (vertex.failedTaskCount * 100 > vertex.maxFailuresPercent * vertex.numTasks) {
LOG.info("Failing vertex: " + vertex.logIdentifier +
" because task failed: " + taskEvent.getTaskID());
vertex.tryEnactKill(VertexTerminationCause.OWN_TASK_FAILURE,
TaskTerminationCause.OTHER_TASK_FAILURE);
forceTransitionToKillWait = true;
}
}
...
VertexState state = VertexImpl.checkTasksForCompletion(vertex);
if(state == VertexState.RUNNING && forceTransitionToKillWait){
return VertexState.TERMINATING;
}
return state;
Matrix rows:
| Source | Event | Destination(s) | Handler |
|---|---|---|---|
RUNNING | V_TASK_COMPLETED (FAILED, over threshold) | TERMINATING | TaskCompletedTransition |
TERMINATING | V_TASK_COMPLETED | TERMINATING / KILLED / FAILED / ERROR | TaskCompletedTransition |
Answer:
- Which field counts failures? (
failedTaskCount.) What is the threshold? (maxFailuresPercent, compared asfailedTaskCount * 100 > maxFailuresPercent * numTasks.) - What does
tryEnactKill(...)do? (Fans task-kill/terminate events out so the rest of the running tasks are torn down; the vertex sits inTERMINATINGuntil every task is accounted for.) - When the vertex finally lands in
FAILED, what does it post upward? Find it:
Then read howgrep -n "DAG_VERTEX_COMPLETED\|new DAGEvent\|eventHandler.handle" \ tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java | headDAGImplreacts:
Does the DAG fail immediately, or continue as far as it can? (It records the vertex as failed and fails the DAG when the failure makes completion impossible — read the handler to state the exact rule.)grep -n "VERTEX_COMPLETED\|vertexFailed\|VertexCompleted" \ tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/DAGImpl.java | head
Flow C — A kill during INITIALIZING
Derived from the INITIALIZING block's V_TERMINATE entry:
| Source | Event | Destination(s) | Handler |
|---|---|---|---|
NEW | V_INIT | INITIALIZING | InitTransition |
INITIALIZING | V_TERMINATE | KILLED | TerminateInitingVertexTransition |
Read the handler. It is deliberately tiny:
private static class TerminateInitingVertexTransition extends TerminateInitedVertexTransition {
@Override
public void transition(VertexImpl vertex, VertexEvent event) {
super.transition(vertex, event);
}
}
and the parent it delegates to:
private static class TerminateInitedVertexTransition
implements SingleArcTransition<VertexImpl, VertexEvent> {
@Override
public void transition(VertexImpl vertex, VertexEvent event) {
VertexEventTermination vet = (VertexEventTermination) event;
vertex.trySetTerminationCause(vet.getTerminationCause());
vertex.addDiagnostic("Vertex received Kill in INITED state.");
vertex.finished(VertexState.KILLED);
}
}
Answer: why does killing in INITIALIZING go straight to KILLED (a SingleArc transition)
while killing in RUNNING goes to TERMINATING first (VertexKilledTransition)? (A vertex that has
not started running has no tasks to tear down, so there is nothing to wait for; a RUNNING vertex
must drain in-flight tasks, hence the intermediate TERMINATING state.)
Step 6 — Identify the multi-arc (dynamic-destination) transitions
MultipleArcTransition.transition() returns the destination state. List every handler in
VertexImpl that does so, and for one of them quote the return logic. The cleanest example is
VertexNoTasksCompletedTransition, whose whole body is:
@Override
public VertexState transition(VertexImpl vertex, VertexEvent event) {
return VertexImpl.checkTasksForCompletion(vertex);
}
The destination is entirely determined by checkTasksForCompletion, which weighs
succeededTaskCount, failedTaskCount, numTasks, terminationCause, and whether outputs need
committing. This is why the same event from the same state can land in SUCCEEDED, FAILED, or
COMMITTING — the EnumSet in the addTransition call enumerates all legal outcomes, and the
handler picks one.
Step 7 — Visualize the machine (the real tool exists)
Tez ships a GraphViz generator wired into the tez-dag POM. Confirm it:
grep -n "VisualizeStateMachine\|tez.dag.state.classes\|visualize" tez-dag/pom.xml
The visualize profile runs org.apache.hadoop.yarn.state.VisualizeStateMachine (from
hadoop-yarn-common) over the classes listed in tez.dag.state.classes —
DAGImpl, VertexImpl, TaskImpl, TaskAttemptImpl, AMNodeImpl, AMContainerImpl. Generate the graph:
mvn compile -Pvisualize -pl tez-dag
# -> writes tez-dag/target/Tez.gv
dot -Tpng tez-dag/target/Tez.gv -o /tmp/tez-states.png # if graphviz 'dot' installed
To render only VertexImpl, override the property:
mvn compile -Pvisualize -pl tez-dag \
-Dtez.dag.state.classes=org.apache.tez.dag.app.dag.impl.VertexImpl \
-Dtez.graphviz.title=VertexImpl
Compare the generated diagram to the three flows you built by hand. Every arc in your tables must appear in the graph; every arc in the graph you did not derive is a flow you have not yet read.
Step 8 — Find the under-tested transitions and the @Ignored test
grep -n "@Ignore" \
tez-dag/src/test/java/org/apache/tez/dag/app/dag/impl/TestVertexImpl.java
For each @Ignored test: read the comment, note the JIRA it references (if any), and judge whether
the underlying issue is fixed and the test could be re-enabled — a genuine, mergeable contributor
task. Then pick three handler classes from your matrix and check whether TestVertexImpl exercises
each:
grep -n "V_TASK_COMPLETED\|V_TERMINATE\|V_START" \
tez-dag/src/test/java/org/apache/tez/dag/app/dag/impl/TestVertexImpl.java | head
Document one transition that looks under-covered — a candidate Test JIRA.
Deliverables
- The factory's four type parameters + initial state, in your log.
-
Both
VertexStateenums recorded, with a note on which the machine uses and why. - Transition matrices for all three flows (A happy, B failure cascade, C kill-in-INITIALIZING), every row attributed to a real handler class.
-
The list of
MultipleArcTransitionhandlers, with one quoted return-logic example. -
A rendered
Tez.gv(orVertexImpl-only graph) checked against your matrices. -
The
@Ignored test inTestVertexImplassessed, plus one under-covered transition named.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
grep for RECOVERING finds nothing | There is no such state | Recovery is RecoverTransition on NEW; look for V_RECOVER |
You see VertexState.CONFIGURED / PARALLELISM_UPDATED in the factory | You are reading the wrong enum | The machine uses org.apache.tez.dag.app.dag.VertexState; the api enum only feeds VertexStateUpdate notifications |
mvn -Pvisualize fails with ClassNotFoundException: VisualizeStateMachine | hadoop-yarn-common not on the exec classpath | The profile sets classpathScope=test; build the module first with mvn -pl tez-dag test-compile |
dot: command not found | GraphViz not installed | brew install graphviz / apt-get install graphviz, or paste Tez.gv into an online GraphViz viewer |
| A transition's destination "changes" between runs | It is a MultipleArcTransition | The destination is computed in transition(); read the method, not just the EnumSet |
| Counts differ from the lab's "~60" | The machine evolves between releases | The lab pins shape, not exact counts; record your own numbers |
Stretch Goals
- Compare complexity. Build the same matrix (states, events, transitions) for
TaskImplandTaskAttemptImpl. Rank the three by number of states and transitions and explain whyVertexImplis the largest (it coordinates all its tasks plus reconfiguration and commit). - Trace inter-machine events. Find every place
VertexImplposts to another machine:
Classify the targets (grep -n "eventHandler.handle" \ tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/VertexImpl.java | head -30TaskImpl,DAGImpl, target/sourceVertexImpls) and the event types. Cross-reference event-routing.md. - The reconfiguration path.
StartTransition/startVertex()callmaybeSendConfiguredEvent()and interact withvertexToBeReconfiguredByManager. Trace how aVertexManagerthat changes parallelism (Lab 4.2/4.4) delays theCONFIGUREDnotification. Why is this one of the most bug-prone areas of the class?
Validation / Self-check
Answer in your own words, citing the class (never a line number) for each:
- What are the four type parameters of the
VertexImplStateMachineFactory, and what is the initial state? - There are two
VertexStateenums. Which does the state machine use, which is for notifications, and name one state that exists in one but not the other. TaskCompletedTransitionis aMultipleArcTransition. Explain how the same event (V_TASK_COMPLETED) fromRUNNINGcan end inRUNNING,SUCCEEDED, orTERMINATING.- Trace a task failure that fails the vertex: which counter and threshold decide it, which method tears down the surviving tasks, and which intermediate state the vertex passes through.
- Why does a kill in
INITIALIZINGuse aSingleArcTransitionstraight toKILLED, while a kill inRUNNINGuses a transition toTERMINATINGfirst? - There is no
RECOVERINGstate. How does Tez recover a vertex after an AM restart, and which transition/event implements it? - Which real command generates a GraphViz diagram of
VertexImpl, and where does it write the output?
When you can reproduce the three flows from memory and explain each dynamic destination as logic in
a transition() method — not a fixed arrow — you have completed Lab 4.1. Continue to
Lab 4.2: VertexManager Deep Dive.