refactor(client): extract poll_command_completion helper#45
Merged
BlindMaster24 merged 1 commit intomainfrom Apr 23, 2026
Merged
Conversation
Three command-dispatch sites duplicated the same 50+ line timeout loop
pattern: issue a command, check cmd_id.is_ok() for rejection, then spin
either an infinite poll(50) loop (timeout_ms < 0) or a deadline loop
with a local wait_slice helper, matching terminal CmdSuccess/CmdError
for cmd_id and (for list-style waits) accumulating data events along
the way.
Hoist that shared shape into Client::poll_command_completion next to
the existing poll_until primitive, so it is:
* built on top of poll_until (which already handles both the negative
and positive timeout_ms cases uniformly), and
* parameterised by an error_context string for messages and an
FnMut(Event, &Message) hook that lets callers accumulate non-terminal
events (BannedUser, UserAccount, ...) without branching on timing.
Apply to:
* Client::wait_for_command (no accumulator; passes |_, _| {}).
* Client::list_bans_and_wait (BannedUser accumulator).
* Client::list_user_accounts_and_wait (UserAccount accumulator).
Other wait_for_* helpers (join_channel_and_wait, login_and_wait,
update_server_and_wait, create/delete_user_account_and_wait,
wait_for_file_transfer*) either terminate early on a data event rather
than on CmdSuccess, or short-circuit on connection-state changes. Those
have genuinely different semantics and stay on their current manual
loop; the wait_slice helpers in channels.rs, server.rs, directory.rs,
files.rs, and users/auth.rs are preserved for now. Future PRs can fold
them in with their own dedicated helper.
Structural only, no semantic change:
* Error messages are reconstructed verbatim ("<context> rejected in
current state", "<context> failed", Error::Timeout) via
* cmd_id validation runs before any polling, matching the previous
early-return behaviour.
* The predicate fed to poll_until matches exactly the terminal set
{CmdSuccess, CmdError} tied to cmd_id, preserving the original
accumulator/terminal ordering.
Net diff: -81 / +20 lines in existing files, +55 lines in
client/core/runtime.rs (mostly doc comments).
Local verification:
* cargo fmt --all --check clean
* cargo clippy --workspace --all-targets --all-features -- -D warnings clean
* cargo test --workspace --all-features -> 287 passed / 0 failed
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This was referenced Apr 23, 2026
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.
Summary
Three
Client::*_and_waitsites duplicated the same ~50-line timeoutloop pattern: check
cmd_id.is_ok()for immediate rejection, thenbranch between a
timeout_ms < 0infinitepoll(50)loop and adeadline loop driven by a local
wait_slicehelper, matching terminalCmdSuccess/CmdErrorforcmd_idand (for list-style waits)accumulating data events (
BannedUser,UserAccount, ...) along theway. Every copy agreed today but with small stylistic divergences and
no shared test surface, so any future adjustment (e.g. honouring a new
terminal event, widening the no-op-timeout semantics, reworking how
CommandId::ZEROis reported) would silently drift between callsites.
Hoist the shape into
Client::poll_command_completionnext to theexisting
poll_untilprimitive. It is:poll_until(which already handlestimeout_ms < 0and positive deadlines uniformly);
error_contextstring used to rebuild the exactsame error messages the old copies produced; and
FnMut(Event, &Message)hook so list-stylecallers can push into a local accumulator without the helper needing
to know anything about
BannedUser,UserAccount, etc.Applied to:
Client::wait_for_command(no accumulator; passes|_, _| {}).Client::list_bans_and_wait(BannedUseraccumulator).Client::list_user_accounts_and_wait(UserAccountaccumulator).Deliberately not touched in this PR:
join_channel_and_wait/login_and_wait- short-circuit onConnectionStatechanges every iteration, not just on events.Folding them in cleanly requires a separate helper shape, or pushing
the state check into
poll_until.update_server_and_wait/create_user_account_and_wait/delete_user_account_and_wait/wait_for_file_transfer/wait_for_file_transfer_terminal- return early on a data eventrather than on
CmdSuccess. Fits a different "first-match-wins"helper. Saving that for a follow-up PR to keep this one narrowly
scoped.
The local
wait_slicehelpers inchannels.rs,server.rs,directory.rs,files.rs, andusers/auth.rsare intentionally keptas-is. They still have one or more non-converted callers each, and
removing them opportunistically would mask the fact that those sites
are next in the queue.
Structural only, no semantic change:
state" / " failed" /
Error::Timeout) viaformat!("{error_context} ...").cmd_idvalidation runs before any polling, matching the previousearly-return behaviour bit-for-bit.
poll_untilpredicate matches exactly the terminal set{CmdSuccess, CmdError}tied tocmd_id; non-terminal events flowthrough
on_eventin the same order as before.poll_command_completionispub(crate)- invisible to downstreamcrates, same surface as the previous private helpers.
Net diff: -81 / +20 lines in existing files, +55 lines in
client/core/runtime.rs(mostly doc comments on the new helper).Local verification:
cargo fmt --all --checkcleancargo clippy --workspace --all-targets --all-features -- -D warningscleancargo test --workspace --all-features-> 287 passed / 0 failedReview & Testing Checklist for Human
crates/teamtalk/src/client/core/runtime.rsand confirm thenew
poll_command_completionpreserves the original errorstrings bit-for-bit. In particular:
"command rejected in current state"/"command failed"forwait_for_command,"ban list command rejected in current state"/"ban list command failed"for
list_bans_and_wait,"user account list command rejected in current state"/"user account list command failed"forlist_user_accounts_and_wait.sites converted; the five first-match-wins sites and the two
state-aborting sites left for follow-up PRs. The alternative is
one large PR that also introduces a
poll_command_or_eventhelper and folds the state-aborting sites in; happy to do that if
preferred.
Notes
This is PR 2 of the P0 structural refactor queue (PR 1 was #44, which
deduped
can_issue_logged_in_command). Next up: either convert theremaining
wait_slice-driven sites topoll_until, or start splittingthe oversized modules (
client/bus.rs,bot/storage.rs,bot/fsm.rs,client/hooks/builders.rs,types/entities/media_common.rs, and theclient/backend*.rspair) into sub-modules. Each will ship as its ownreviewable PR.
The pre-existing
semverCI gate is expected to remain red here too;release-plzhandles the eventual version bump. Every other check isexpected to pass.
Link to Devin session: https://app.devin.ai/sessions/71fdd6196cb74723a2e277bb81993a9c
Requested by: @BlindMaster24