Skip to content

vms-c55: the executive holds cross-node lock traffic for the duration of a cluster state transition - #1734

Open
baron-3dl wants to merge 4 commits into
mainfrom
work/vms-c55-transition-hold
Open

baron-3dl wants to merge 4 commits into
mainfrom
work/vms-c55-transition-hold

Conversation

@baron-3dl

Copy link
Copy Markdown
Contributor

What this is

A real OpenVMS VAX V7.3 bugchecks LOCKMGRERR in the cluster demo at about 1 in 9 fires. The previous lead — "OVMX is a member the VAX admitted that silently refuses what it doesn't recognise" — is falsified here, and a different signal, on the cause side, discriminates just as sharply.

The refusals, measured everywhere they exist

Two committed crash captures plus the twelve case-runs of openvmx-site 38811815171 / 38811829040 / 38811843112 / 38814006004 (frames + all three consoles each):

  • %DLM, refusing a lock message appears once in the whole corpus — cat 0x02 op 0x19, in the one crashing case-run.
  • %CNXMAN, an unroutable VMS$VAXcluster frame was received — the other place an inbound frame can be dropped — appears zero times in all fourteen runs.
  • Every other inbound frame is answered, or is one that is never answered (the 01/0a GO, the 01/0c release, the 01/06 burst — all consumed by the barrier/join FSMs).

So the refusal set is {02/19}, and nothing OVMX refused can have provoked it.

What does discriminate

OVMX originates a cat-0x02 lock request while a cluster state transition is running.

OVMX cat-0x02 originated IN a transition outside bugcheck
3 crash runs 3 (02/04@step1 ×3, 02/01@step2 ×2) 1 YES ×3
11 clean runs 0 26 no

And over all 122 committed pcaps, real DEC-OUI senders only: inside a transition the DLM traffic is the op-0x0d rebuild record and its echo (2797 + 1240) and essentially nothing else; every non-0x0d cat-0x02 frame a real node put inside one flew at barrier step 10, 11 or 12 of 12 — zero at steps 1–5. In the one capture holding a whole 12-step barrier the in-window breakdown is exactly 307 × 02/0d, 307 × 82/0d and the barrier frames.

That is the published behaviour, measured: a transition quiesces the lock manager — Phase 1 discards all directory information cluster-wide, Phase 2 rebuilds it (Davis, VAXcluster Principles, pp. 7-40/7-41, p. 6-33).

Why OVMX did it

There was no stall. dlm_arm_transition_begin() only counted the boundary, and dq_may_transmit() — the single gate every outbound lock frame passes — had no transition condition. The quorum hang deliberately does not cover it (vms_dlm_quorum.h limit 1: "this node does not stall the POST of such a request … deferring the post until resume is a queue this rung does not build"). This is that queue.

The change

Executive-resident, reading only state the executive already holds:

  • struct dlm_req_ops gains transition_active — a live read of the CLUB's own transition_active, the flag the barrier and coordinator FSMs already maintain and cnxman_dlm_seq_learn already gates on. An injected op, not a copy, for the same INV-6 reason dir_generation is one.
  • dq_may_transmit() holds an ENQ or a CONVERT while it reads true. The block is retained exactly as vms-97c2 retains a frame the connection manager could not take, and the hold does not spend a rung of the retransmit ladder: a transition makes lock activity wait, it does not fail it.
  • a directory-entry removal raised during one is dropped, counted — it has no sleeper behind it and nothing to re-read, and Phase 1 discards the entry cluster-wide anyway.
  • a $DEQ still goes — giving a lock back is how the cluster is left in a state the rebuild can use, the same carve-out vms_dlm_quorum.h makes for a release during a quorum hang.
  • dlm_arm_transition_end() releases the hold last, after the new directory vector has been applied, through the ordinary ladder beat — so a released request is built from a fresh refill_post read under the new vector.

No new wire shape is emitted. The change moves when two existing shapes go out.

Proof

R1 test_dlm_requester.c 410 checks, 0 failures (41 new)
test_dlm_scs_arm.c 131 checks — the two bindings and the ORDER of the release
negative controls 4 new, each reddening exactly its require_fail set; full host negctl 134/134 PASS
census selftest tools/cluster/transition_dlm_census.py --selftest, wired into ctest as cluster_host_transition_dlm_census — re-derives every figure above from the committed corpus
ctest 355/355, cluster 110/110
Linux vms.ko builds clean

Evidence: tests/lab/captures/vms-c55-transition-hold-20261016 — the THIRD frame-level LOCKMGRERR specimen (openvmx-site 38811843112, page-order-throttled4, the recurrence that carried a nonzero PID cell), the census, and PREDICTION.md pre-registering the demo-gate fire.

Not claimed

That this is the cause. What cat-0x02 op-0x19 IS remains unknown and is still not guessed — no answer frame invented, RULE C not relaxed, the removal not suppressed outside a transition (vms-e09's 8,106 real removals stand). The demo-gate fire scored against PREDICTION.md is what says whether the crash stops.

🤖 Generated with Claude Code

@baron-3dl

Copy link
Copy Markdown
Contributor Author

FIRE DISPATCHED 2026-10-16 ~01:38Z, scoring tests/lab/captures/vms-c55-transition-hold-20261016/PREDICTION.md, which was pushed with the commit and BEFORE these were fired.

Four preflights, each three cases (b-first, all-at-once, page-order-throttled4 — the three orders a LOCKMGRERR has been seen in), capture=true, ref=work/vms-c55-transition-hold:

  • openvmx-site 38836182130
  • openvmx-site 38836196104
  • openvmx-site 38836210245
  • openvmx-site 38836224210

Node B was rebuilt from THIS head first (vms run 38833987086, success) and rd vms-4f4 has since put the cases input on site main alongside Node B staging from the build under test — so for the first time a case-filtered run can be the build under test end to end (P6).

CI on this PR: 115 success / 51 skipped / 0 failures, including NetBSD/vax vms module cross-compiles — both substrates clean.

How to score, in this order (P1 carries the argument, not P5):

  • P1 transition_dlm_census.py emitter <frames.jsonl> — the in-txn column must be 0 for every case-run. Baseline 3 of 3 crash runs had one.
  • P2 the same tool's outside column must be nonzero somewhere, or P1 is vacuous: a fire in which OVMX emits no lock requests at all proves nothing.
  • P3 both OVMX nodes still reach a logged-in DCL $; no new DLM timeout failures on their consoles.
  • P4 no new (category, opcode) pair — this change emits no new shape.
  • P5 zero LOCKMGRERR across ≥12 case-runs, against a 1-in-9 baseline, with the refusing a lock message (cat 0x02 op 0x19) string as the companion signal (5 of 5 crashes, 0 of 30 clean to date).

A recurrence is not a dead end: capture=true, so the window around any bugcheck says whether an OVMX cat-0x02 was inside a transition when the VAX died — and if it was not, that falsifies this diagnosis the same way the nonzero PID cell falsified the last one.

@baron-3dl

Copy link
Copy Markdown
Contributor Author

FIRE SCORED — 24 case-runs across two arms. Committed as tests/lab/captures/vms-c55-transition-hold-20261016/FIRE.md.

hold (18 case-runs) control / main (6 case-runs)
OVMX cat-0x02 originated inside a transition (P1) 0 0
the same, outside one (P2) 232+ 28
a new emitted (cat, op) pair (P4) none none
LOCKMGRERR (P5) 0 0
refusing a lock message (cat 0x02 op 0x19) 0 0
CN=3 18 of 18 6 of 6
OVMXB reaches Username: (P3) no, 17 of 18 no, 6 of 6

What it settles

  1. The hold is live. In 38846164733 / b-first the OVMX/VAX node's console carries, once each, %DLM, holding cross-node lock requests until the VAXcluster state transition completes and %DLM, the VAXcluster state transition completed; held cross-node lock requests resume. The mechanism is exercised by real traffic, not only by its tests, and the node went on serving afterwards.
  2. No regression. OVMXB never reaching Username: is present identically on main — 6 of 6 — so it is rd vms-97c2 (P0, open; vms-97c2 (PARTIAL): a cross-node lock request whose frame could not go out is retried, never dropped on a sleeping $ENQW — measured NOT to be the demo stall #1729 was partial), made visible for the first time by rd vms-4f4 putting a real Node B in the gate. Without the control arm this fire would have read as a regression caused by the hold.
  3. No peer harmed in either arm.

What it does NOT settle, said plainly

That the LOCKMGRERR is fixed. The control did not reproduce it either (0 in 6, where the baseline is 1 in 9–12), so 0 of 18 is consistent with the fix working and with the crash simply not firing. And P1 is not a discrimination in these runs — the control also shows 0 in-transition originations, so the condition the hold guards against did not arise there. P1's categorical content is a property of the code, proven at R1 and R2 where the condition is forced.

So the change stands on what it was defended on and nothing more: real VMS nodes do not put lock traffic into a rebuild; OVMX did, in 3 of 3 crash captures and 0 of 11 clean ones; this removes it, harms nothing and emits no new shape. vms-c55 stays open, and vms-8cb5's question — what cat-0x02 op-0x19 IS — is still unanswered and still not guessed.

Also added since the first review: the R2 leg (the REAL vms_lock.c engine's $ENQ waits across a transition and is granted when it lifts), the console diagnostic and its fifth negative control. Full host negctl 135/135, cluster ctest 110/110, CI green including NetBSD/vax vms module cross-compiles.

Queuing for merge.

baron-3dl and others added 4 commits October 16, 2026 05:04
… of a cluster state transition

A real OpenVMS VAX V7.3 bugchecks LOCKMGRERR in the cluster demo about once in
nine fires. The previous lead -- "OVMX silently refuses what it does not
recognise" -- is FALSIFIED here, and a different signal, on the CAUSE side,
discriminates just as sharply.

THE REFUSALS, measured everywhere they exist (two committed crash captures plus
the twelve case-runs of openvmx-site 38811815171 / 38811829040 / 38811843112 /
38814006004, frames + all three consoles each):
  - "%DLM, refusing a lock message" appears ONCE in the whole corpus: cat 0x02
    op 0x19, in the one crashing case-run.
  - "%CNXMAN, an unroutable VMS$VAXcluster frame was received" -- the other
    place an inbound frame can be dropped -- appears ZERO times in all fourteen.
  - Every other inbound frame is answered, or is one the spec says is never
    answered (the 01/0a GO, the 01/0c release, the 01/06 burst).
So the refusal set is {02/19} and nothing OVMX refused can have provoked it.

WHAT DOES DISCRIMINATE: OVMX originates a cat-0x02 lock request WHILE A CLUSTER
STATE TRANSITION IS RUNNING. 3 of 3 crash runs (a directory removal at BARRIER
STEP 1, twice also a lookup at step 2); 0 of 11 clean runs, which carry 26
OVMX-originated requests, all of them outside a transition.

AND REAL VAXES DO NOT DO IT. Over all 122 committed pcaps, real DEC-OUI senders
only: inside a transition the DLM traffic is the op-0x0d rebuild record and its
echo (2797 + 1240) and essentially nothing else; every non-0x0d cat-0x02 frame
a real node put inside one flew at barrier step 10, 11 or 12 OF 12 -- ZERO at
steps 1-5. In the one capture holding a whole 12-step barrier the in-window
breakdown is exactly 307 x 02/0d, 307 x 82/0d and the barrier frames. That is
the published behaviour measured: a transition quiesces the lock manager --
Phase 1 discards all directory information cluster-wide, Phase 2 rebuilds it
(Davis, VAXcluster Principles, pp. 7-40/7-41, p. 6-33).

WHY OVMX DID IT: there was no stall. dlm_arm_transition_begin() only COUNTED
the boundary, and dq_may_transmit() -- the single gate every outbound lock
frame passes -- had no transition condition. The quorum hang deliberately does
not cover it (vms_dlm_quorum.h limit 1: "this node does not stall the POST of
such a request ... deferring the post until resume is a queue this rung does
not build"). This is that queue.

THE CHANGE, executive-resident and reading only state the executive holds:
 - struct dlm_req_ops gains transition_active, a LIVE read of the CLUB's own
   `transition_active` -- the flag the barrier and coordinator FSMs already
   maintain and cnxman_dlm_seq_learn already gates on. An op, not a copy, for
   the same INV-6 reason dir_generation is one.
 - dq_may_transmit() holds an ENQ or a CONVERT while it reads true. The request
   block is RETAINED exactly as rd vms-97c2 retains a frame the connection
   manager could not take, and the hold does NOT spend a rung of the retransmit
   ladder: a transition makes lock activity WAIT, it does not fail it.
 - a DIRECTORY-ENTRY REMOVAL raised during one is DROPPED, counted. It has no
   sleeper behind it and nothing to re-read, and Phase 1 discards the entry
   cluster-wide anyway -- so holding it would mean asserting, after the
   rebuild, about a directory entry this executive no longer has a fact about.
 - a $DEQ still goes: giving a lock back is how the cluster is left in a state
   the rebuild can use, the same carve-out vms_dlm_quorum.h makes for a release
   during a quorum hang.
 - dlm_arm_transition_end() releases the hold LAST, after the new directory
   vector has been applied, through the ordinary ladder beat -- so a released
   request is built from a fresh refill_post read under the NEW vector, never
   from a remembered frame.

NO NEW WIRE SHAPE IS EMITTED. The change moves WHEN two existing shapes go out.

PROOF
 - R1 tests/cluster/host/test_dlm_requester.c: 410 checks, 0 failures (41 new)
   -- a held request waits out three ladders' worth of beats without being
   failed, then goes out carrying the handle the LKB holds NOW; a removal in a
   transition is dropped and NOT re-sent afterwards; a $DEQ is not held; an
   unbound op holds nothing.
 - test_dlm_scs_arm.c: the two bindings and the ORDER of the release.
 - FOUR new negative controls, each reddening exactly its require_fail set:
   dlm-request-not-held-in-transition, dlm-dir-remove-sent-in-transition,
   dlm-transition-hold-spends-the-ladder, arm-transition-never-releases-the-hold.
   Full host negctl suite: 134/134 PASS.
 - tools/cluster/transition_dlm_census.py --selftest, wired into ctest
   (cluster_host_transition_dlm_census), re-derives every figure above from the
   committed corpus so none of them can drift.
 - ctest 355/355; cluster 110/110; vms.ko builds clean.

EVIDENCE: tests/lab/captures/vms-c55-transition-hold-20261016 -- the THIRD
frame-level LOCKMGRERR specimen (openvmx-site 38811843112, page-order-
throttled4), the census, and PREDICTION.md pre-registering the fire.

NOT CLAIMED: that this is the cause. What cat-0x02 op-0x19 IS remains unknown
and is still not guessed -- no answer frame invented, RULE C not relaxed, the
removal not suppressed outside a transition. The demo-gate fire scored against
PREDICTION.md is what says whether the crash stops.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The R1 leg proves the FSM holds and releases against a fake engine. What one
node cannot prove there is the thing this change could plausibly break: that
holding a frame does not strand the $ENQW the shipping vms_lock.c put to sleep
on its proxy LKB.

scenarios/dlm_requester.c gains an arm with the REAL engine, a REAL CLUB and
the shipping FSM: the barrier's own `club.transition_active` is set, a $ENQ for
a cross-node resource puts NOTHING on the LAN, fifteen ladder beats go by
without the request being failed, $GETLKI still shows it pending (not errored),
and when the flag clears dlm_req_fsm_transition_ended() puts the frame out and
the real engine grants the lock at EX.

89 checks, 0 failures. Cluster ctest 110/110.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…isode

The twelve-case fire of the hold met P1 (0 of 12 case-runs put an OVMX
cat-0x02 request inside a transition, against 3 of 3 crash runs), P2, P4 and
P5 (0 LOCKMGRERR, CN=3 in 12 of 12, and no op-0x19 refusal on any console) --
but OVMXB, which is the build under test for the first time since rd vms-4f4,
never offered Username: and its console stops between the STARTUP.COM hand-off
and the site-startup line. NOTHING on that console says whether a lock request
was WAITING for a rebuild or had been lost, so the stall cannot be attributed
from the evidence the run produced. That is the #1714 lesson again.

So the hold says so: one line at the first held transmission of an episode and
one at its release, bounded by `xition_hold_said` so a storm of held requests
inside one rebuild is still one line. A console now answers the question by
itself.

Tested: the R1 suite asserts exactly one line across sixteen held
transmissions and exactly one more at the release (404 checks, 0 failures),
and a fifth negative control -- dlm-transition-hold-is-silent -- reddens
exactly those two assertions. Cluster ctest 110/110.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
24 case-runs across two arms, same instrument, one variable.

 HOLD (ref=work/vms-c55-transition-hold, 18 case-runs, Node B from the ref):
   0 OVMX cat-0x02 originated inside a transition, 0 LOCKMGRERR, 0 op-0x19
   refusals, CN=3 in 18 of 18, no new emitted shape.
 CONTROL (ref=main, this branch's exact base, 6 case-runs, Node B from main):
   the same -- 0 and 0 and CN=3 in 6 of 6.

WHAT IT SETTLES.
 (1) The hold is LIVE. In 38846164733/b-first the OVMX/VAX node's console
     carries the two new lines once each -- it held cross-node lock requests
     for a transition and released them when it completed -- so the mechanism
     is exercised by real traffic, not only by its tests, and the node went on
     serving.
 (2) The change causes NO regression. OVMXB never reaching Username: is
     present IDENTICALLY on main (6 of 6 against 17 of 18): it is rd vms-97c2,
     still open, and rd vms-4f4 is why it became visible at all -- every
     earlier case-filtered run booted the deployed V0.7-7 image. Without the
     control this fire would have read as a regression caused by the hold.
 (3) No peer was harmed in either arm.

WHAT IT DOES NOT SETTLE, said plainly: that the LOCKMGRERR is fixed. The
CONTROL did not reproduce it either (0 in 6 where the baseline is 1 in 9-12),
so 0 of 18 is consistent with the fix working AND with the crash not firing;
and P1 is not a discrimination in these runs, because the control also shows 0
in-transition originations. P1's categorical content is a property of the CODE,
proven at R1/R2 where the condition is forced. The change stands on what it was
defended on: real nodes do not put lock traffic into a rebuild, OVMX did in 3
of 3 crash captures and 0 of 11 clean ones, and this removes it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@baron-3dl
baron-3dl force-pushed the work/vms-c55-transition-hold branch from 41965b6 to 3ddb673 Compare October 16, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant