Skip to content

bft: enqueue a decision when its view is aborted - #742

Merged
HagarMeir merged 1 commit into
hyperledger:mainfrom
pfi79:735-4a-decide-enqueue
Nov 1, 2026
Merged

HagarMeir merged 1 commit into
hyperledger:mainfrom
pfi79:735-4a-decide-enqueue

Conversation

@pfi79

@pfi79 pfi79 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Decide blocked when the decision channel was full and the view was being aborted: the controller loop could be inside abortView waiting for the view to stop, and the view could be waiting to enqueue its decision. The abort and the decision waited for each other.

Hand the decision to the controller in the background when the view is aborting. The decision is not dropped: MutuallyExclusiveDeliver filters a duplicate that the view changer has already delivered.

#736

@pfi79
pfi79 requested a review from HagarMeir October 29, 2026 15:09
Comment on lines +906 to +910
case <-view.AbortChan():
// The view is stopping while the controller is busy with a long sync or
// delivery. Do not block the view, or the abort would wait for it, but do
// not drop the decision either: hand it to the controller in the background.
go c.enqueueDecision(d)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small thing: the deadlock in the description is the controller loop sitting in abortView waiting for this view, not a long sync or delivery. Could the comment say that? Otherwise someone might think this case only matters for slow syncs.

(Written by Claude Code.)

Comment thread internal/bft/controller.go Outdated
// enqueueDecision hands a decision to the controller when the view that decided it is
// already stopping. The decision was accepted by a quorum, so it must not be lost:
// the controller delivers it once it is no longer busy. If the view changer has
// already delivered the same proposal, MutuallyExclusiveDeliver drops the duplicate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: MutuallyExclusiveDeliver doesn't just drop the duplicate. It runs a Sync() and resets the checkpoint. Maybe "skips delivering it again" instead of "drops"?

(Written by Claude Code.)

Comment thread internal/bft/controller_decide_test.go Outdated
Comment on lines +60 to +73
type abortRecordingProposer struct {
aborted bool
abortChan chan struct{}
}

func (p *abortRecordingProposer) Propose(types.Proposal) {}
func (p *abortRecordingProposer) Start() {}

func (p *abortRecordingProposer) Abort() {
p.aborted = true
if p.abortChan != nil {
close(p.abortChan)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completion_signal_test.go already uses &View{abortChan: make(chan struct{})} with view.stop(). Could this test use that instead of a new fake? aborted is never read either.

(Written by Claude Code.)

@pfi79
pfi79 force-pushed the 735-4a-decide-enqueue branch from bdd7ae1 to 4bed5c2 Compare November 1, 2026 10:15
@pfi79

pfi79 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

fixed

// TestDecideDoesNotDropDecisionOnAbortedView covers the hand-off between the view
// and the controller: a view that is being aborted must not stay blocked on a full
// decision channel, but the committed decision must still reach the controller.
func TestDecideDoesNotDropDecisionOnAbortedView(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

completion_signal_test.go is also in package bft and only has TestControllerDecideViewBookkeeping, which tests the same view-to-controller hand-off. Could both tests go in one file? Your name fits both better, so maybe rename completion_signal_test.go to controller_decide_test.go and add this test there.

(Written by Claude Code.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Decide blocked when the decision channel was full and the view was being
aborted: the controller loop could be inside abortView waiting for the view
to stop, and the view could be waiting to enqueue its decision. The abort
and the decision waited for each other.

Hand the decision to the controller in the background when the view is
aborting. The decision is not dropped: MutuallyExclusiveDeliver filters a
duplicate that the view changer has already delivered.

Signed-off-by: Fedor Partanskiy <[email protected]>
@pfi79
pfi79 force-pushed the 735-4a-decide-enqueue branch from 4bed5c2 to 107b7f8 Compare November 1, 2026 10:44
@HagarMeir
HagarMeir merged commit 32a628d into hyperledger:main Nov 1, 2026
6 checks passed
@pfi79
pfi79 deleted the 735-4a-decide-enqueue branch November 1, 2026 11:04
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.

2 participants