Anony Mouse created RATIS-2656:
----------------------------------

             Summary: BaseStateMachine.pause() does not pause the lifecycle, so 
a streamed snapshot install closes the division
                 Key: RATIS-2656
                 URL: https://issues.apache.org/jira/browse/RATIS-2656
             Project: Ratis
          Issue Type: Bug
          Components: server
    Affects Versions: 3.2.2
            Reporter: Anony Mouse


h3. Summary

A state machine that extends {{BaseStateMachine}} without overriding 
{{pause()}} has its division closed by every streamed snapshot install it 
receives. The default {{pause()}} is an empty method, so the lifecycle never 
reaches {{PAUSED}}; the install path then calls 
{{StateMachineUpdater.reload()}}, whose precondition requires {{PAUSED}}; the 
resulting {{IllegalStateException}} reaches the updater's catch-all, which 
closes the division.

Observed at 3.2.2 and again against the 3.3.0 RC2 artifacts.

h3. Chain

# {{BaseStateMachine.pause()}} - empty body; nothing transitions the lifecycle.
# Follower receives a streamed install; the server pauses the state machine and 
calls {{StateMachineUpdater.reload()}}.
# {{reload()}} asserts the state machine is {{PAUSED}} (frame observed: 
{{StateMachineUpdater.reload(StateMachineUpdater.java:230)}} at the 3.2.2 tag) 
-> {{IllegalStateException}}.
# The updater's catch-all logs {{StateMachineUpdater caught a Throwable}} and 
closes the division.

h3. Observed timeline (one follower, one install)

{noformat}
06:49:26.353  SnapshotManager - Installed snapshot, renaming temporary dir ... 
to .../sm
06:49:26.356  StateMachineUpdater caught a Throwable.
              java.lang.IllegalStateException
                at StateMachineUpdater.reload(StateMachineUpdater.java:230)
06:49:26.357  RaftServer$Division: shutdown
{noformat}

Four milliseconds from a successful install to a dead division. The install 
itself is durable on disk; only the division dies. A later crash-restart of 
that node recovers normally from the installed snapshot, which is what makes 
this easy to miss: the cluster keeps working, and the node only dies again the 
next time it is sent an install.

h3. Impact

Any integrator whose state machine inherits the default {{pause()}} - which is 
the natural thing to do, since it is a concrete no-op rather than an abstract 
method - loses a follower's division on each streamed install, and gains it 
back only on process restart. Nothing in the signature or javadoc of 
{{pause()}} indicates that an override is required for correctness.

A secondary effect is filed separately: while the division is closed, the 
leader continues to re-initiate the install with no backoff.

h3. Reproduction

An in-JVM probe with a deliberately naive {{BaseStateMachine}} subclass (no 
{{pause()}} override) reproduces it directly, and a fixed subclass that honors 
the lifecycle survives the same path:

https://github.com/hooji/ratis-jepsen - see 
{{harness/probe/ratis_jepsen/probe/lifecycle.clj}}, and 
{{jobs/08-membership-churn/}} plus {{reviews/08-membership-churn/}} for the 
original discovery, exact frames, and preserved run stores.

The probe was run against 3.2.2 (reproduces) and the 3.3.0 RC2 artifacts 
(reproduces identically); it is not sensitive to the surrounding harness.

h3. Possible directions

Any one of these would close it; the choice is yours:

* Give {{BaseStateMachine.pause()}} a default implementation that actually 
transitions the lifecycle to {{PAUSED}} (and {{reinitialize()}} back), so the 
shipped base class is correct by default.
* Have {{StateMachineUpdater.reload()}} tolerate a non-{{PAUSED}} state machine 
rather than treating it as a precondition violation.
* If the override is intended to be the integrator's responsibility, make that 
explicit - javadoc on {{pause()}}/{{reinitialize()}} stating that a snapshot 
install will close the division otherwise, and ideally a louder failure than an 
assertion swallowed by a catch-all.

Happy to test a patch - the probe above gives a fast yes/no.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to