diff --git a/.agents/docs/2026-10-28-add-concurrentqueue-mwrap-plan.md b/.agents/docs/2026-10-28-add-concurrentqueue-mwrap-plan.md new file mode 100644 index 00000000..019a7319 --- /dev/null +++ b/.agents/docs/2026-10-28-add-concurrentqueue-mwrap-plan.md @@ -0,0 +1,76 @@ +# Add mcpplibs.mwrap.concurrentqueue (concurrentqueue-m 1.0.5) + +## Form + +- Third-party upstream [cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue), tag `v1.0.5`. It + is available under the Simplified BSD license or the Boost Software License; its semaphore is under zlib. The + tarball (`4d6368a2…6ef3`) is the one compat.concurrentqueue 1.0.5 already pins. +- Form A external mwrap module repository: [mcpplibs/concurrentqueue-m](https://github.com/mcpplibs/concurrentqueue-m), + package `mcpplibs.mwrap.concurrentqueue`. The version is the upstream one (first release of the identity, no + revision suffix); tag series `mwrap-${version}`. +- Implementation ownership: the headers, and the C API sources with the `c-api` feature, are compat.concurrentqueue's. + The wrapper vendors no upstream code. It retains upstream's README and license under `upstream/concurrentqueue/`, + with checksums in `upstream/documents.json`. + +## Modules + +- `mcpplibs.mwrap.concurrentqueue` exports the upstream entities of `concurrentqueue.h`, `blockingconcurrentqueue.h` + and `lightweightsemaphore.h` by `using` in `export namespace moodycamel`: `ConcurrentQueue`, + `BlockingConcurrentQueue`, `ConcurrentQueueDefaultTraits`, `ProducerToken`, `ConsumerToken`, `LightweightSemaphore`, + every `swap` overload, and `ConcurrentQueueTests`. + - `moodycamel::details` is the implementation and is not exported. + - Its TU-local helpers that are not templates over arbitrary types are adapted in + `mcpplibs::mwrap::concurrentqueue::details`, each calling upstream's body. +- `mcpplibs.mwrap.concurrentqueue.macros` holds the value macros and the configuration inputs as built, all as + `constexpr bool`. The syntax macros stay in ``. +- `mcpplibs.mwrap.concurrentqueue.c_api` exports upstream's C API (`moodycamel_cq_*`, `moodycamel_bcq_*`), with the + `c-api` feature, which forwards to `compat.concurrentqueue/c-api`. +- The `no-thread-local` feature sets upstream's `MOODYCAMEL_NO_THREAD_LOCAL`. +- GCC 16.1 module handling: + - `MOODYCAMEL_NOEXCEPT_CTOR` / `_ASSIGN` are spelled with the equivalent type traits (upstream's configuration + point). Upstream's expression names a `static` function inside a template argument, which GCC rejects as an + exposure. + - The module instantiates the queues' members once, so that the non-dependent specializations of `static` helpers + have bodies in its interface. + - A unit that includes `` cannot instantiate the thread-exit path; `no-thread-local` is the documented + remedy. + +`tools/check_exports.py` audits every declaration of `moodycamel`, every name of `moodycamel::details`, the C API, and +every macro and configuration input against the pinned headers. It was negative-checked by removing an export and a +mapping. + +## Verification + +Wrapper CI runs on four rows, all required: Linux GCC 16.1, Linux LLVM 23.1.3, macOS LLVM and Windows LLVM. It +covers: + +- the build, with and without `c-api`; +- the tests: multi-producer multi-consumer traffic with tokens and bulk operations, timed waits, user types and + traits, the semaphore, the adapted helpers, the constants and header mixing; +- the `c-api` / `no-thread-local` test project; +- the export audit; +- upstream's `tests/unittests`, all 45 registered tests, built against the module instead of the headers. The + conformance tool documents its mechanical edits and applies them to the pinned tarball's files. + +The templates job scaffolds the default `pipeline` template as an installed package, builds it with `--strict` and runs +it. + +Local checks, with the CI-pinned mcpp 2026.10.26.1 and `MCPP_INDEX_MIRROR=GLOBAL`: + +- `bash tests/run_members.sh concurrentqueue-mpmc` installs the real release archive through the candidate index. The + consumer runs four producers enqueueing in bulk through tokens and four consumers waiting with timeouts; every item + must arrive once and in per-producer order. It then exercises a timed-out wait and the semaphore. Result: + `test result ok`. +- `python3 tests/mwrap/check_installed_template.py --package mcpplibs.mwrap.concurrentqueue@1.0.5` reports "Verified + real indexed archive installation and unmodified template consumer". + +## Mirror and updates + +This change has no `mcpp-res` access. The descriptor therefore uses the GitHub release URL as a plain string, and CN +users fall back to it. A maintainer can add the GitCode mirror with identical bytes. `extensions.autoupdate` follows +the repository's own `mwrap-${version}` tags. + +## Motivation + +openmui.core's UI queue is a `moodycamel::ConcurrentQueue`. Through this module its implementation unit imports +`mcpplibs.mwrap.concurrentqueue` instead of including concurrentqueue.h. diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index f9e67a73..b098ad7a 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1623,6 +1623,11 @@ jobs: shell: bash run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.libpng@1.6.43 + - name: Installed concurrentqueue wrapper archive and actual default pipeline template + if: contains(format(' {0} ', env.MEMBERS), ' concurrentqueue-mpmc ') && matrix.toolchain == 'default' + shell: bash + run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.concurrentqueue@1.0.5 + - name: Installed FriBidi wrapper archive, actual default bidi template and its relocated LGPL notices if: contains(format(' {0} ', env.MEMBERS), ' fribidi-bidi ') && matrix.toolchain == 'default' shell: bash diff --git a/mcpp.toml b/mcpp.toml index bd93dfed..ae69346c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -20,6 +20,7 @@ members = [ "tests/mwrap/lua-embed", "tests/mwrap/quickjs-embed", "tests/mwrap/libpng-image", + "tests/mwrap/concurrentqueue-mpmc", "tests/mwrap/fribidi-bidi", "tests/mwrap/freetype-render", "tests/mwrap/harfbuzz-shape", diff --git a/pkgs/mwrap/concurrentqueue.lua b/pkgs/mwrap/concurrentqueue.lua new file mode 100644 index 00000000..b087d872 --- /dev/null +++ b/pkgs/mwrap/concurrentqueue.lua @@ -0,0 +1,38 @@ +-- Immutable upstream-aligned mwrap source release (concurrentqueue-m mwrap-1.0.5). +-- The CN mirror is added by a maintainer with mcpp-res access; until then CN +-- users fall back to the GitHub release (docs/cn-mirror.md). +package = { + spec = "1", + namespace = "mcpplibs.mwrap", + name = "concurrentqueue", + description = "concurrentqueue-m: moodycamel::ConcurrentQueue (lock-free MPMC queue) as C++23 modules", + licenses = { "Apache-2.0" }, + repo = "https://github.com/mcpplibs/concurrentqueue-m", + type = "package", + extensions = { + autoupdate = { + tag = "mwrap-${version}", + url = "https://github.com/mcpplibs/concurrentqueue-m/releases/download/${tag}/concurrentqueue-m-${version}.tar.gz", + }, + }, + xpm = { + linux = { + ["1.0.5"] = { + url = "https://github.com/mcpplibs/concurrentqueue-m/releases/download/mwrap-1.0.5/concurrentqueue-m-1.0.5.tar.gz", + sha256 = "896e0adb535d74633bed4cd10cfe161bc6b45ff9b386faf141ab808f196050d1", + }, + }, + macosx = { + ["1.0.5"] = { + url = "https://github.com/mcpplibs/concurrentqueue-m/releases/download/mwrap-1.0.5/concurrentqueue-m-1.0.5.tar.gz", + sha256 = "896e0adb535d74633bed4cd10cfe161bc6b45ff9b386faf141ab808f196050d1", + }, + }, + windows = { + ["1.0.5"] = { + url = "https://github.com/mcpplibs/concurrentqueue-m/releases/download/mwrap-1.0.5/concurrentqueue-m-1.0.5.tar.gz", + sha256 = "896e0adb535d74633bed4cd10cfe161bc6b45ff9b386faf141ab808f196050d1", + }, + }, + }, +} diff --git a/tests/mwrap/concurrentqueue-mpmc/mcpp.toml b/tests/mwrap/concurrentqueue-mpmc/mcpp.toml new file mode 100644 index 00000000..699531bd --- /dev/null +++ b/tests/mwrap/concurrentqueue-mpmc/mcpp.toml @@ -0,0 +1,11 @@ +[package] +name = "concurrentqueue-mpmc" +version = "1.0.5" +description = "Consume the immutable concurrentqueue wrapper: multi-producer multi-consumer traffic with tokens and bulk operations, the blocking queue's timed waits, the semaphore, the mapped macros" + +[indices] +# Use the complete candidate built-in index prepared by tests/run_members.sh. +# An explicit empty table overrides the legacy workspace redirect. + +[dependencies] +"mcpplibs.mwrap.concurrentqueue" = "1.0.5" diff --git a/tests/mwrap/concurrentqueue-mpmc/tests/behavior.cpp b/tests/mwrap/concurrentqueue-mpmc/tests/behavior.cpp new file mode 100644 index 00000000..b49817d8 --- /dev/null +++ b/tests/mwrap/concurrentqueue-mpmc/tests/behavior.cpp @@ -0,0 +1,57 @@ +import std; +import mcpplibs.mwrap.concurrentqueue; + +namespace macros = mcpplibs::mwrap::concurrentqueue::macros; + +int main() { + static_assert(macros::MOODYCAMEL_EXCEPTIONS_ENABLED); + constexpr int producers = 4, consumers = 4, per_producer = 10000, total = producers * per_producer; + // Producers enqueue in bulk through their tokens; consumers wait with a + // timeout and stop once everything is taken. + moodycamel::BlockingConcurrentQueue> queue; + std::vector> seen(producers, std::vector(per_producer, 0)); + std::mutex seen_lock; + std::atomic taken{0}; + bool ordered = true; + { + std::vector threads; + for (int p = 0; p < producers; ++p) + threads.emplace_back([&, p] { + moodycamel::ProducerToken token(queue); + std::array, 100> batch; + for (int i = 0; i < per_producer; i += 100) { + for (int k = 0; k < 100; ++k) batch[k] = {p, i + k}; + queue.enqueue_bulk(token, batch.begin(), batch.size()); + } + }); + for (int c = 0; c < consumers; ++c) + threads.emplace_back([&] { + moodycamel::ConsumerToken token(queue); + std::array, 64> batch; + std::array last{-1, -1, -1, -1}; + while (taken.load() < total) { + const auto n = queue.wait_dequeue_bulk_timed(token, batch.begin(), batch.size(), std::chrono::milliseconds(1)); + std::lock_guard lock(seen_lock); + for (std::size_t k = 0; k < n; ++k) { + auto [p, i] = batch[k]; + if (i <= last[p]) ordered = false; // per-producer FIFO + last[p] = i; + ++seen[p][i]; + } + taken.fetch_add(static_cast(n)); + } + }); + } + for (const auto& p : seen) + for (char c : p) + if (c != 1) return 1; + if (!ordered) return 2; + // A timed wait on an empty queue times out; the semaphore counts. + std::pair none; + if (queue.wait_dequeue_timed(none, std::chrono::milliseconds(2))) return 3; + moodycamel::LightweightSemaphore sema; + sema.signal(2); + if (sema.waitMany(5) != 2 || sema.tryWait()) return 4; + std::println("concurrentqueue-mpmc: ok ({} items, {} producers, {} consumers)", total, producers, consumers); + return 0; +}