Skip to content

feat: add tmc::mux_many and tmc::mux_tuple#265

Merged
tzcnt merged 9 commits into
mainfrom
mux
Jul 3, 2026
Merged

feat: add tmc::mux_many and tmc::mux_tuple#265
tzcnt merged 9 commits into
mainfrom
mux

Conversation

@tzcnt

@tzcnt tzcnt commented Jul 3, 2026

Copy link
Copy Markdown
Owner

These replace and enhance the old result_each() overloads (this is a breaking change):

  • tmc::spawn_many().result_each() -> tmc::mux_many
  • tmc::spawn_tuple().result_each() -> tmc::mux_tuple

What's the same:

  • mux_* is an lvalue awaitable that allows you to co_await it multiple times. Each time it returns the index of a single ready awaitable, which you can then access with operator[] / get().
  • The group size is limited to 63 (or 31, on 32-bit) due to use of an atomic synchronization bitset.
  • You must ensure that all active awaitables are finished (co_await mux == mux.end()) before the mux is destroyed.

The crucial new feature:

  • You can .fork() a new awaitable to replace one that has completed, while other awaitables in the group are still running.
  • Unlike tmc::select(), active (not finished) awaitables don't get canceled when a different awaitable completes or is replaced.
  • Replacing awaitables is completely optional - you can await a mux that is not fully active.
  • You can use this to:
    • maintain a constant level of concurrency
    • join streams from multiple queues
    • maintain a wait on a background signal while doing work
    • implement a timeout-based batch processor (see example batch_processor.cpp)

It also offers:

  • You can customize individual task execution with fork(awaitable, executor, priority).
  • You can fork_clang() to re-initiate awaitables with HALO, and it works in a loop as long as each is within a switch statement associated with the specific index that it's replacing.
  • is_active() / active_bitset() methods let you track which awaitable slots are live.

What's lost from the old result_each():

  • You cannot customize an entire mux group initial dispatch with mux_many().run_on(ex).with_priority(2). If you want to customize the tasks you must construct it empty and then fork(task, ex, 2) each awaitable.

Documentation:

I've also added an AGENTS.md / CLAUDE.md to this repo, for library users. It's fairly lightweight and is just designed to give your agent a headstart when using the library.

tzcnt added 3 commits July 2, 2026 19:34
This prevents the compiler from generating a speculative load of incomplete awaitable paths, which triggers a TSan false positive. It's truly a false positive because the unused result is discarded.
@tzcnt tzcnt merged commit 72e2c18 into main Jul 3, 2026
56 checks passed
@tzcnt tzcnt deleted the mux branch July 3, 2026 17:20
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