Conversation
Signed-off-by: Effi-S <effi.szt@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2131
Document severity: HIGH · Verified priority: Low · Effort: Low · Type: Task
Old issue reference
[RUN]ttx/multisig/spend.go+boolpolicy/spend.goIntent
RequestSpendView.Callin both themultisigandboolpolicypackages fansout to N goroutines, each sending its result to a shared
answerChannel. Whenthe outer loop exits early (e.g., context cancellation or first error), the
remaining goroutines block forever on a send to
answerChannelbecause noreceiver will ever drain the channel and the channel is never closed.
This pattern creates one goroutine leak per unread party response.
Expected Outcomes
answerChannelis never left with blocked senders afterCallreturns.Todo List
answerChannelbuffered to the exact number of goroutines spawned(
counter), so that senders can complete their send even if the receiverhas exited. (The current code already sizes the channel to
len(c.parties)but
countermay be smaller when some parties are "me".)any remaining items from
answerChannelin a non-blocking loop so thegoroutines that have already sent can be GC'd.
multisig/spend.goandboolpolicy/spend.go.Relevant Context
token/services/ttx/multisig/spend.goanswerChannelcreation and receive loop.token/services/ttx/boolpolicy/spend.goStatus in current code
Already fixed — filed for double-checking only. The
answerChannelpattern the document describes no longer exists in either file.Both call sites were refactored to a shared helper. The bare
answerChannelfan-outis gone:
token/services/ttx/multisig/spend.go:130token/services/ttx/boolpolicy/spend.go:154both now construct
and collect via
collector.Collect(context.Context(), counter)(multisig/spend.go:152,boolpolicy/spend.go:163). The helper istoken/services/utils/collector.go.Why this closes both remediation items:
Todo item 1 (buffer to the spawned count).
collector.go:59allocatesmake(chan Answer[K, T], capacity)— buffered to the full capacity passed in.Send(
:66-68) therefore never blocks as long as senders do not exceed capacity, which the callersguarantee by passing
len(c.parties)while spawning at mostcounter <= len(c.parties)goroutines. The document's specific worry —
counterbeing smaller thanlen(c.parties)whensome parties are "me" — is safe in this direction: the buffer is sized to the larger number,
so every spawned sender has a slot.
Todo item 2 (no blocked senders after
Callreturns). Because the channel is buffered tocapacity, an in-flight worker completes its
Sendinto the buffer and exits even whenCollecthas already returned on timeout or cancellation. No explicit drain is needed — theun-received answers and the channel itself simply become garbage once the collector goes out
of scope. The type's doc comment at
collector.go:37-45states this is the intent: "Theunderlying channel is buffered to the collector's capacity, so workers that are still in
flight when Collect returns (on timeout or cancellation) can always complete their Send and
exit without leaking a goroutine."
Bonus — the collector cannot itself hang.
Collect(:73-91) selects on three cases:the answer channel,
ctx.Done(), andtimer.Cfrom atime.NewTimer(c.timeout)withdefer timer.Stop()at:76-77.NewAnswersCollectoralso substitutesDefaultAnswersCollectorTimeout(30 s,:20) when a non-positive timeout is passed, so acollector is never accidentally unbounded. The original code's failure mode — a bare receive
that waits forever — is structurally impossible.
Recommended action: verify the above and close as completed. No code change required.
Severity and priority
The source document rated this HIGH. Re-checked against this repository at
origin/mainwithgo 1.26.5on 2026-08-04, the priority is Low for thereasons in Status in current code above.
Effort
Low