Compare commits

...

114 Commits

Author SHA1 Message Date
dependabot[bot]
079c4cd232 build(deps): bump extern/googletest from 7140cd4 to 973323e
Bumps [extern/googletest](https://github.com/google/googletest) from `7140cd4` to `973323e`.
- [Release notes](https://github.com/google/googletest/releases)
- [Commits](7140cd416c...973323ed64)

---
updated-dependencies:
- dependency-name: extern/googletest
  dependency-version: 973323ed64a05b128418e7eab67016db5ba049df
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-01 14:53:00 +00:00
dependabot[bot]
8c60e8f1f7 build(deps): bump extern/googletest from 9156d4c to 7140cd4
Bumps [extern/googletest](https://github.com/google/googletest) from `9156d4c` to `7140cd4`.
- [Release notes](https://github.com/google/googletest/releases)
- [Commits](9156d4caac...7140cd416c)

---
updated-dependencies:
- dependency-name: extern/googletest
  dependency-version: a721f1b20c605f635413e22d8b7427a8b4f3956c
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-10 21:05:44 +02:00
Dennis Klein
a34fa64dc1 ci: pin third-party actions to release commit SHAs
- hendrikmuhs/ccache-action: the v1 major tag also lagged behind the
  latest release (v1.2.20 instead of v1.2.23), so this is a bump too
- threeal/cmake-action: v2 == v2.1.0, pinned as-is
2026-06-10 19:31:19 +02:00
Dennis Klein
ed87268394 ci: fix UBSan option spelling so it takes effect
- ENABLE_SANITIZER_UNDEFINED_BEHAVIOUR never matched the CMake option
  ENABLE_SANITIZER_UNDEFINED_BEHAVIOR (cmake/FairMQProjectSettings.cmake),
  so the asan+lsan+ubsan job has never actually enabled UBSan
2026-06-10 19:31:19 +02:00
Dennis Klein
7e63d4ae9a test: drop thread-sanitizer suppressions
- every entry stood in for a library tsan could not see into; with libzmq,
  libsodium and libstdc++ now tsan-instrumented in the tsan CI job, the
  happens-before edges they establish are visible and nothing is left to
  suppress
- suppressions were blunt (a race: entry matches any frame in the stack),
  so they could also mask real races passing through those frames
2026-06-10 19:31:19 +02:00
Dennis Klein
1fb5ad8c33 ci: switch thread-sanitizer job to gcc with instrumented deps
- build with the spack gcc toolchain like every other job: no clang++, no
  -fuse-ld=lld and no lld install step (the GNU BFD failure on tsan objects
  was specific to clang's tsan runtime)
- use gcc 15 for the freshest libtsan runtime; the asan entry stays on
  gcc 14, so the matrix now carries per-entry gcc and spack env names
- consume the tsan spack env and load the instrumented libstdc++ into each
  test's environment via FAIRMQ_TEST_LD_LIBRARY_PATH (it shares the soname
  of the compiler's own, so it substitutes process-wide at load time)
- use -fno-omit-frame-pointer for readable reports; optimization comes
  from the project's Debug -Og
- verify the wiring: assert the test environment resolves libstdc++ to the
  instrumented copy and that libzmq is tsan-instrumented, since both
  failure modes are silent (the suite still passes, with reduced race
  coverage)
2026-06-10 19:31:19 +02:00
Dennis Klein
add85cb18d ci: add tsan spack environment with instrumented libzmq
- mirror spack-latest.yaml, with -fsanitize=thread on the libzmq and
  libsodium nodes so tsan can observe the happens-before edges established
  inside libzmq's lock-free queues, plus the libstdcxx-tsan root spec
- flags are applied per node instead of via the propagating '==' operator,
  which could reach the gcc node and trigger a compiler rebuild
- unchanged roots (fairlogger, boost, ninja, cmake) keep their spec hashes,
  so they are shared with the regular buildcache entries; the instrumented
  nodes hash differently and coexist in the content-addressed cache
- exclude libstdcxx-tsan from concretizer reuse so recipe changes always
  take effect; unchanged recipes still hit the buildcache because the spec
  hash is identical
- add the tsan env to the buildcache matrix (rebuilding also on spack_repo
  changes) so the instrumented binaries are cached instead of rebuilt on
  every CI run
2026-06-10 19:31:19 +02:00
Dennis Klein
331c50ab0e ci: add spack package for a tsan-instrumented libstdc++
- gcc ships no supported switch to build libstdc++ with -fsanitize=thread,
  and spack's gcc recipe filters all flags out of the target-library build
  (CXXFLAGS_FOR_TARGET is owned by its generated --with-build-config=spack
  makefile), so provide a dedicated libstdcxx-tsan package in a custom repo
- build only the libstdc++-v3 subtree from the matching gcc release tarball,
  configured standalone against the already-installed toolchain (recipe
  modeled on https://iree.dev/developers/debugging/sanitizers/), instead of
  rebuilding all of gcc
- the result is a drop-in runtime replacement for the compiler's libstdc++
  (same soname and symbol versions), to be loaded only by the instrumented
  test executables
- normalize the install layout after make install: the standalone build puts
  the runtime libraries into the multilib os dir (lib64 on x86_64) regardless
  of --libdir, and --with-toolexeclibdir only applies to cross builds
- register the repo in the setup-deps action before creating the env
2026-06-10 19:31:19 +02:00
Dennis Klein
988ef81922 ci: tolerate cache-satisfied specs in the buildcache pushes
- buildcache push expands its selection into the full dependency
  closure, build-time dependencies included; specs that were satisfied
  from the buildcache do not have those installed locally, and the push
  fails with PackageNotInstalledError
- both push sites (the early gcc node push and the env-level push) only
  ever ran in fresh-build scenarios before, so the failure surfaced once
  the cache was warm
- pass --allow-missing to skip what is not installed (a best-effort push
  of everything that is); a freshly built gcc thus still uploads its
  build-time dependencies, which a future gcc rebuild can then pull as
  binaries
2026-06-10 19:31:19 +02:00
Dennis Klein
b568535910 test: support an alternative runtime library dir per test
- introduce FAIRMQ_TEST_LD_LIBRARY_PATH, which prepends a directory to
  each test's environment via ctest, so the tests can run against an
  alternative runtime library (e.g. a tsan-instrumented libstdc++)
- LD_LIBRARY_PATH rather than an injected rpath: an rpath added via the
  linker flags cannot precede the rpath spack's gcc adds through its
  specs file, so the compiler's own libstdc++ would keep winning the
  runtime search order
- scoped per test on purpose: an instrumented library has unresolved
  __tsan_* symbols and must not be loaded into uninstrumented tools
  like cmake, ctest or ninja
- fail the configuration instead of silently dropping the injection on
  CMake < 3.22 (ENVIRONMENT_MODIFICATION)
- cover the example tests too; they share the instrumented runtime but
  not the locale-cache warmup (their main() is the installed public
  header). The custom-controller env block was dead before: it tested
  lsan_options, which only ever existed in the add_example() function
  scope, so the test also never received the LSan suppressions
2026-06-10 19:31:19 +02:00
Dennis Klein
2febbe3146 build: append linker flags as strings, not lists
- CMAKE_EXE_LINKER_FLAGS and CMAKE_SHARED_LINKER_FLAGS are command-line
  strings; list(APPEND) inserts a semicolon once the variable is
  non-empty, which splits the link command at the shell level
- latent until now because nothing passed linker flags on the cmake
  command line
2026-06-10 19:31:19 +02:00
Dennis Klein
2bd9a072a9 test: pre-fill libstdc++ ctype caches before threads exist
- std::ctype<char> caches narrow()/widen() results per character in
  plain char arrays of the global classic-locale facet, written without
  synchronization from header-inlined code (locale_facets.h); two
  threads exercising an uncached character concurrently (e.g. compiling
  a std::regex in Channel::Validate) constitute a true data race that
  ThreadSanitizer rightfully reports
- the stores are real and unsynchronized, so a tsan-instrumented
  libstdc++ cannot help here; instead fill the caches before any thread
  is spawned, which turns every later access into a pure read
- warm the lazily-installed num_put/num_get caches used by stream
  insertion/extraction as well, via a small format/parse round-trip
- wire the warm-up into the gtest runner main() and, via a static
  initializer, into the test device runner
2026-06-10 19:31:19 +02:00
Dennis Klein
fc69b5e7ae refactor: compile channel name validation regex only once
- the pattern is constant; compiling it on every Validate() call is
  wasted work and, when channels are validated from multiple threads,
  needlessly exercises libstdc++'s lazily-populated ctype caches
2026-06-10 19:31:19 +02:00
Dennis Klein
19e607e486 test: fix racy loop-variable capture in SubscriptionThreadSafety
- the subscriber threads captured the loop counter by reference while
  the spawning loop kept incrementing it: a genuine data race
- depending on timing, threads could also end up with duplicate
  subscriber names; capture the counter by value instead
2026-06-10 19:31:19 +02:00
Dennis Klein
f08d42fcb8 fix: serialize console output in fair::mq::tools::execute
- concurrent execute() calls print captured subprocess lines to
  std::cout from multiple threads; the standard allows that, but
  libstdc++ maintains the formatted-output state (ios_base::width)
  with plain reads and writes -- a data race ThreadSanitizer reports
  once libstdc++ itself is instrumented
- a mutex around the insertion also keeps whole lines from
  interleaving
2026-06-10 19:31:19 +02:00
Alexey Rybalchenko
1597999aed fix(shmem): don't cache nullptr in GetRegionFromCache
A failed region lookup was inserted into the thread-local cache as
nullptr, making the failure permanent for the lifetime of the cache
generation - retrying never healed because the fast path would return
nullptr without calling GetRegion again. Skip the cache insert on
failure so subsequent calls retry the slow path.
2026-06-10 18:51:04 +02:00
Alexey Rybalchenko
215c31428b feat(shmem): expose side-channel metadata API for unsent messages
Add two public entry points needed by the ALICE use case where shmem
messages are allocated via a transport but never sent — their metadata
is instead serialised into Arrow tables and delivered over a separate
channel, allowing consumer devices to resolve the payload pointer
without taking ownership.

shmem::Message::GetMeta() returns the MetaHeader of the message,
mirroring the existing positional-init pattern already used in Socket.h.

shmem::GetDataAddressFromHandle(TransportFactory&, const MetaHeader&)
is a free function declared in Common.h and defined in Manager.cxx.
Keeping it out of the TransportFactory class body means callers only
need to include Common.h (available transitively via Message.h) and do
not drag in Socket.h or zmq.h. The implementation handles both managed
segments and unmanaged regions, and throws SharedMemoryError with a
typed message on a bad segment or region id. TransportFactory also
gains a same-named member for callers that already have the concrete
type. Lifetime of the returned pointer is the caller's responsibility;
the cache device is expected to hold the messages alive.

A SideChannel test covers the GetMeta/GetDataAddressFromHandle
round-trip for both standard and expanded-metadata configurations.
2026-06-10 18:51:04 +02:00
Dennis Klein
a0e8271aca test: suppress libzmq-induced thread-sanitizer false positives
- libzmq is not tsan-instrumented, so tsan cannot see the happens-before
  its queues establish between user threads and libzmq I/O threads,
  producing false-positive data races on message buffers
- add test/thread_sanitizer_suppressions.txt and point TSAN_OPTIONS at it
  via the sanitizers job env so it reaches the tests and their device
  subprocesses
- suppress: accesses made directly from libzmq, the zero-copy message
  deleters libzmq runs from msg_t::close, shmem receive-side metadata
  reads, and std::regex/locale lazy-init races in libstdc++
2026-06-09 23:00:58 +02:00
Dennis Klein
4b2c6cafac build: avoid -Wignored-attributes on the popen deleter
- glibc declares pclose with function attributes (nothrow/leaf), so
  decltype(pclose)* carries attributes that gcc ignores on the unique_ptr
  template argument, emitting -Wignored-attributes
- spell the deleter as a plain int(*)(FILE*) instead; pclose converts to
  it silently and the deleter behaves identically (both popen branches)
2026-06-09 23:00:58 +02:00
Dennis Klein
ab4bc49088 style: drop redundant member initializers in shmem UnmanagedRegion
- fShmemObject() and fFileMapping() default-construct anyway
- clang-tidy readability-redundant-member-init
  https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-member-init.html
2026-06-09 23:00:58 +02:00
Dennis Klein
89e16c0b19 build: run only the curated clang-tidy checks
- clang-tidy enables the clang-analyzer-* group by default; in this
  codebase it only yields false positives (intentional moved-from
  asserts, a bitmask enum cast) and noise inside third-party boost
  headers that HeaderFilterRegex does not filter
- prefix the check list with -* so only the explicitly curated checks run
2026-06-09 23:00:58 +02:00
Dennis Klein
35d713ebaa refactor: use default member initializers
- move constant constructor initializers into in-class default member
  initializers
- clang-tidy modernize-use-default-member-init
  https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-default-member-init.html
2026-06-09 23:00:58 +02:00
Dennis Klein
4dfe24d411 style: drop redundant member initializers
- remove constructor member initializers that duplicate the default
  member initialization
- clang-tidy readability-redundant-member-init
  https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-member-init.html
2026-06-09 23:00:58 +02:00
Dennis Klein
d2aa2f10de style: drop redundant empty-string initializers
- remove `= ""` initialization from strings that default-construct empty
- clang-tidy readability-redundant-string-init
  https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-string-init.html
2026-06-09 23:00:58 +02:00
Dennis Klein
d5e0c29ced perf: emplace elements instead of inserting temporaries
- construct container elements in place instead of inserting a temporary
- clang-tidy modernize-use-emplace
  https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-emplace.html
2026-06-09 23:00:58 +02:00
Dennis Klein
0feda158b4 perf: avoid copies in range-based for loops
- take loop variables by const reference where the body does not modify
  them
- clang-tidy performance-for-range-copy
  https://clang.llvm.org/extra/clang-tidy/checks/performance/for-range-copy.html
2026-06-09 23:00:58 +02:00
Dennis Klein
7a44c5e19e style: add braces around single-statement bodies
- wrap single-statement control-flow bodies in braces
- clang-tidy readability-braces-around-statements
  https://clang.llvm.org/extra/clang-tidy/checks/readability/braces-around-statements.html
2026-06-09 23:00:58 +02:00
Dennis Klein
e23c4d8ff1 style: use raw string literals for escaped strings
- replace string literals containing many escaped characters with raw
  string literals
- clang-tidy modernize-raw-string-literal
  https://clang.llvm.org/extra/clang-tidy/checks/modernize/raw-string-literal.html
2026-06-09 23:00:58 +02:00
Dennis Klein
8ab00ecddc refactor: use nullptr for null pointer literals
- replace `0` null pointer literals (e.g. `void* hint = 0`) with `nullptr`
- clang-tidy modernize-use-nullptr
  https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-nullptr.html
2026-06-09 23:00:58 +02:00
Dennis Klein
b53d25738e build: curate clang-tidy to an enforceable check set
- drop the broad `cppcoreguidelines-*` glob: it produced ~4500 findings
  (magic numbers, non-private members, owning-memory, pointer arithmetic,
  ...) that are aspirational and out of scope for the warning gate
- drop modernize-use-equals-default: in this codebase it only yields
  false/unsafe positives, e.g. `= default` on a constructor that
  explicitly initializes atomic members (which default-init leaves
  indeterminate in C++17), and invalid output on constructors with a
  member-init list
- drop modernize-pass-by-value: it rewrites constructor parameters to
  by-value + std::move, changing public constructor signatures, which is
  an ABI-relevant change unsuitable for a library's public headers
- keep the deliberately-listed modernize/readability/performance checks
2026-06-09 23:00:58 +02:00
Dennis Klein
824825e911 ci: make the static-analysis warning gate actually fail
- the gate did `grep -q warning: build.log`, but build.log was never
  produced by the cmake-action build, so under `set -e` the grep in the
  `if` condition just reported "no match" and the job always passed
- as a result ~4961 clang-tidy warnings were silently ignored
- build manually and capture output to build.log with pipefail, and
  fail explicitly if the log is missing or contains a warning
2026-06-09 23:00:58 +02:00
Dennis Klein
2cf49cc50d ci: fix thread-sanitizer build with lld and PIC googletest
- tsan build failed at link with GNU ld:
  "failed to set dynamic section sizes: bad value" (known binutils +
  ThreadSanitizer incompatibility); install lld and select it via
  -fuse-ld=lld for the tsan job only
- pass -fuse-ld=lld through cxx-flags so it reaches the link line,
  avoiding the semicolon-list pitfall of
  list(APPEND CMAKE_EXE_LINKER_FLAGS ...)
- build the bundled googletest with CMAKE_POSITION_INDEPENDENT_CODE=ON:
  lld rejects R_X86_64_32 relocations from the non-PIC libgtest.a when
  producing the position-independent tsan executable; the bundle is built
  by a separate cmake invocation, so the flag must be set there
2026-06-09 23:00:58 +02:00
Dennis Klein
7e30c33bcf ci: raise locked-memory limit before running tests
- Region.PreallocateInsideSession.shmem and Example.region.shmem failed
  because mlock() of the managed segment/region hit RLIMIT_MEMLOCK on the
  runner ("Cannot allocate memory"); the region example then hung until
  its 30s timeout
- raise the limit via `sudo prlimit` in the same shell that launches
  ctest (per-process, so it must be done here, not in a prior step)
- replace threeal/ctest-action with the equivalent ctest invocation
2026-06-09 23:00:58 +02:00
Dennis Klein
0fd27cbbc3 ci: match renamed libzmq leak frame in lsan suppressions
- LSan symbolizes the leak as the C++ method `zmq::msg_t::init_size`
  in the Debug sanitizer build, no longer the C wrapper
  `zmq_msg_init_size`
- substring match failed (`_` vs `::`), so the suppression no longer
  applied and the asan+lsan+ubsan job failed in Pair/PubSub/Poller tests
- add the demangled frame, keep the old pattern for older libzmq
2026-06-09 23:00:58 +02:00
Dennis Klein
f374e228ff ci: cache gcc as a buildcache node instead of committed lockfiles
Committed lockfiles pinned gcc as a host-path external (from spack compiler
find), which is not portable across runners and broke CI. Cache the gcc
compiler itself as a buildcache node instead, so CI pulls it (~1 min) rather
than building it from source (~1 h).

- push the freshly-built gcc node in setup-deps BEFORE spack compiler find
  (which marks it external and excludes it from buildcache push), gated behind
  a push-gcc input used only by the buildcache workflow
- drop the committed-lockfile approach: remove test/ci/locks, the lockfile
  install path in setup-deps, and the lockfile export in the buildcache workflow
- drop the ignored ref input from setup-spack (v3 renamed it to spack_ref)
2026-06-08 23:04:29 +02:00
Dennis Klein
bb5c0a998c ci: install deps from committed lockfiles when present
Reusing concretization between the weekly buildcache (fresh) and weekday CI
(reuse) can drift if runner externals change, causing avoidable cache misses.

- setup-deps installs from test/ci/locks/<env>-gcc<N>.lock when it exists,
  skipping concretization for byte-identical hashes; falls back to the spec
  yaml otherwise
- buildcache exports each env's spack.lock as a downloadable artifact so the
  lockfiles can be regenerated on the ubuntu-24.04 runner and committed
- document the manual regeneration flow in test/ci/locks/README.md
2026-05-31 21:15:44 +02:00
Dennis Klein
ffc9c60f73 ci: cache fairmq compilation with ccache
FairMQ's own sources (library, examples, tests) were recompiled from scratch
in every matrix job on every push.

- add hendrikmuhs/ccache-action to build, sanitizers and static-analysis jobs
- set CMAKE_C/CXX_COMPILER_LAUNCHER=ccache so cmake routes through it
- key the cache per (job, env, gcc) since ccache hashes the compiler
2026-05-31 21:15:44 +02:00
Dennis Klein
1186bda040 ci: only run buildcache on dev/master pushes
The push trigger had a path filter but no branch filter, so any PR-branch push
touching those paths (e.g. a dependabot rebase pulling in setup-deps changes)
launched the full fresh buildcache matrix concurrently with CI.

- restrict the push trigger to branches [dev, master]
- frees runners for CI; the cache still refreshes via cron and on dev/master
2026-05-31 21:15:44 +02:00
Dennis Klein
14be1ce368 ci: fetch gcc from buildcache mirror and pin runner image
gcc was built from source (~58 min/job) because the FairMQ buildcache mirror
is only configured inside the env yaml, while gcc is installed before the env
is created.

- register the mirror globally after spack setup so "Install GCC" pulls the
  compiler as a binary
- pin runners to ubuntu-24.04 so the weekly buildcache and weekday CI share an
  image and concretize to matching hashes
- bump setup-spack to v3 to match the update-index job
2026-05-31 21:15:44 +02:00
dependabot[bot]
83cef5bdca build(deps): bump spack/setup-spack from 2 to 3
Bumps [spack/setup-spack](https://github.com/spack/setup-spack) from 2 to 3.
- [Release notes](https://github.com/spack/setup-spack/releases)
- [Changelog](https://github.com/spack/setup-spack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/spack/setup-spack/compare/v2...v3)

---
updated-dependencies:
- dependency-name: spack/setup-spack
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 13:19:00 +02:00
dependabot[bot]
7f44fba4c0 build(deps): bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 13:17:09 +02:00
dependabot[bot]
3dcfc29ec2 build(deps): bump extern/googletest from 7d76a23 to 04ee1b4
Bumps [extern/googletest](https://github.com/google/googletest) from `7d76a23` to `04ee1b4`.
- [Release notes](https://github.com/google/googletest/releases)
- [Commits](7d76a231b0...04ee1b4f2a)

---
updated-dependencies:
- dependency-name: extern/googletest
  dependency-version: 04ee1b4f2aefdffb0135d7cf2a2c519fe50dabe4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 14:47:59 +01:00
Dennis Klein
fa64faf3f7 fix(boost): add compatibility for Boost.Process v1 API in Boost 1.89+
Boost 1.88 replaced Boost.Process with v2, breaking the v1 API.
Boost 1.89 restores v1 compatibility via <boost/process/v1.hpp>.

- Fail configuration if Boost 1.88 is detected
- Define FAIRMQ_BOOST_PROCESS_V1_HEADER for Boost >= 1.89
- Use conditional includes to select v1.hpp or process.hpp
- Add namespace aliases (bp, bp_this) for portable API access
2026-01-05 14:11:19 +01:00
Dennis Klein
25abd605f3 ci: trigger buildcache on setup-deps changes 2025-12-01 09:29:24 +01:00
Dennis Klein
695ed89b6c ci: fix gcc version disambiguation in setup-deps
Use JSON output and jq to select the newest installed gcc version
when multiple versions match, avoiding conflicts between system and
spack-installed compilers.
2025-12-01 09:03:12 +01:00
Dennis Klein
cd074a3f1e ci: add boost187 variant to CI build matrix
Add boost187 environment variant to match the buildcache workflow,
using gcc-15 with the boost187 spack environment.
2025-12-01 08:58:20 +01:00
Dennis Klein
00c343858e ci: name setup-deps steps consistently 2025-11-30 21:03:27 +01:00
Dennis Klein
5dfeebba95 ci: add log grouping to setup-deps action 2025-11-30 20:59:46 +01:00
Dennis Klein
642a4e06f0 ci: force generic x86_64_v3 target for all packages 2025-11-30 20:48:13 +01:00
Dennis Klein
89f9f09c82 ci: deduplicate buildcache workflow using setup-deps action 2025-11-30 20:34:20 +01:00
Dennis Klein
a422361ee9 ci: set x86_64_v3 target for consistent buildcache 2025-11-30 20:10:50 +01:00
Dennis Klein
d1fbe4e89a ci: add named spack environments with boost187 variant 2025-11-30 19:58:19 +01:00
Dennis Klein
ef98c9c7ec ci: add --fresh flag to gcc installation
Force fresh concretization to avoid pulling gcc-runtime from public
buildcache which may be built with an incompatible gcc version.
2025-11-30 16:03:39 +01:00
Dennis Klein
399170879c ci: improve buildcache workflow
- rename mirror to ghcr-buildcache
- find system compiler before building gcc
- separate update-index job to avoid race condition
- always attempt push even on partial failure
2025-11-30 15:51:13 +01:00
Dennis Klein
1392a31250 ci: fix OCI registry authentication for buildcache push 2025-11-30 14:50:02 +01:00
Dennis Klein
060a5f2599 ci: add OCI registry login for buildcache push 2025-11-29 10:50:40 +01:00
Dennis Klein
7a8ccb8df6 ci: migrate from Jenkins to GitHub Actions 2025-11-28 21:13:30 +01:00
Dennis Klein
dcea48fcee fix: parse errors
```
/test/memory_resources/_memory_resources.cxx: In member function ‘virtual void {anonymous}::MemoryResources_allocator_Test::TestBody()’:
/test/memory_resources/_memory_resources.cxx:104:12: error: parse error in template argument list
  104 |     config.SetProperty<string>("session", to_string(session));
      |            ^~~~~~~~~~~~~~~~~~~
/test/memory_resources/_memory_resources.cxx:104:31: error: no matching function for call to ‘fair::mq::ProgOptions::SetProperty<<expression error> >(const char [8], std::string)’
  104 |     config.SetProperty<string>("session", to_string(session));
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/dklein/projects/FairMQ2/test/memory_resources/_memory_resources.cxx:11:
/fairmq/ProgOptions.h:269:6: note: candidate: ‘template<class T> void fair::mq::ProgOptions::SetProperty(const std::string&, T)’
  269 | void fair::mq::ProgOptions::SetProperty(const std::string& key, T val)
      |      ^~~~
/fairmq/ProgOptions.h:269:6: note:   template argument deduction/substitution failed:
/test/memory_resources/_memory_resources.cxx:104:31: error: template argument 1 is invalid
  104 |     config.SetProperty<string>("session", to_string(session));
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/test/memory_resources/_memory_resources.cxx: In member function ‘virtual void {anonymous}::MemoryResources_getMessage_Test::TestBody()’:
/test/memory_resources/_memory_resources.cxx:132:12: error: parse error in template argument list
  132 |     config.SetProperty<string>("session", to_string(session));
      |            ^~~~~~~~~~~~~~~~~~~
/test/memory_resources/_memory_resources.cxx:132:31: error: no matching function for call to ‘fair::mq::ProgOptions::SetProperty<<expression error> >(const char [8], std::string)’
  132 |     config.SetProperty<string>("session", to_string(session));
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/fairmq/ProgOptions.h:269:6: note: candidate: ‘template<class T> void fair::mq::ProgOptions::SetProperty(const std::string&, T)’
  269 | void fair::mq::ProgOptions::SetProperty(const std::string& key, T val)
      |      ^~~~
/fairmq/ProgOptions.h:269:6: note:   template argument deduction/substitution failed:
/test/memory_resources/_memory_resources.cxx:132:31: error: template argument 1 is invalid
  132 |     config.SetProperty<string>("session", to_string(session));
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
2025-06-13 08:17:53 +02:00
Giulio Eulisse
67dcf77a7f De-boostify: use std::pmr from C++17 2025-06-13 08:02:06 +02:00
Alexey Rybalchenko
24e7a5b8d0 Make shmem headers public 2025-03-17 15:16:46 +01:00
Giulio Eulisse
c11506e958 feat(EventManager): Out of line some methods 2025-01-23 15:35:26 +01:00
Dennis Klein
e4f258c9ea fix: Update copyright 2025-01-09 17:09:57 +01:00
Dennis Klein
324a27a2e1 fix(tools): No longer use removed alias io_service
Deprecated via d3bbf3756d
and removed via 49fcd03434
in Boost 1.87 or Asio 1.33.
2025-01-09 17:09:57 +01:00
Dennis Klein
c80f97b338 fix(tools): No longer use removed query API
Deprecated via 74fe2b8e14
and removed via e916bdfb1a
in Boost 1.87 or Asio 1.33.
2025-01-09 17:09:57 +01:00
Dennis Klein
76824fee36 build(googletest): Update metadata 2025-01-09 17:09:57 +01:00
dependabot[bot]
d2e4679dc8 build(deps): bump extern/googletest from 530d5c8 to 7d76a23
Bumps [extern/googletest](https://github.com/google/googletest) from `530d5c8` to `7d76a23`.
- [Release notes](https://github.com/google/googletest/releases)
- [Commits](530d5c8c84...7d76a231b0)

---
updated-dependencies:
- dependency-name: extern/googletest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-07 18:24:47 +01:00
Alexey Rybalchenko
6bb70bd519 Update Mac CI nodes 2025-01-07 17:42:16 +01:00
Giulio Eulisse
fe2127e12f Reduce bloat due to statics
Avoid disseminating every compile unit including Message.h with TransportNames and
TransportTypes and the associated unordered_map helper methods (e.g.
murmur_hash).
2025-01-07 17:41:30 +01:00
Giulio Eulisse
41165cf16b Out of line ProgOption::SetProperty for int and std::string
The specializations are common enough to show up in O2 compilation profiles
and they are not time critical (once per run at max).
2025-01-07 17:34:22 +01:00
Dennis Klein
8fe95e644e ci: Update 2024-08-20 15:56:21 +02:00
Dennis Klein
6628a231e2 build: Adopt all CMake policies up to 3.30
Modernizing to the policy range syntax supported by
[`cmake_minimum_required`](https://cmake.org/cmake/help/latest/command/cmake_minimum_required.html)
since CMake 3.12.
2024-08-20 15:56:21 +02:00
Giulio Eulisse
91b31f0799 Hide actual container from the API 2024-05-23 15:54:24 +02:00
Alexey Rybalchenko
39cb021827 Add 'no control' controller 2024-02-19 22:09:54 +01:00
Alexey Rybalchenko
36b48f5594 Update MacOS CI entires 2024-02-16 13:12:40 +01:00
Alexey Rybalchenko
0e221b28b8 shm: use node_allocator for ref counts 2024-01-25 10:45:34 +01:00
Alexey Rybalchenko
1ee0977df4 shm: use (de)allocate_one() for ref counts 2024-01-25 10:45:34 +01:00
Alexey Rybalchenko
24d578a4ba shm: extend monitor output for refCount region 2024-01-25 10:45:34 +01:00
Christian Tacke
ce1a4499cc ci: Check codemeta/zenodo with AUTHORS/CONTRIBUTORS
If AUTHORS or CONTRIBUTORS are changed,
check that the changes are merged into codemeta.json,
and .zenodo.json.
2023-12-20 16:51:13 +01:00
Dennis Klein
7d009f0915 docs: Update installation section 2023-12-14 13:15:18 +00:00
Dennis Klein
b70b181c38 ci: Create devcontainer.json 2023-12-14 13:40:47 +01:00
dependabot[bot]
94602d23b3 build(deps): bump extern/googletest from a1cc8c5 to 530d5c8
Bumps [extern/googletest](https://github.com/google/googletest) from `a1cc8c5` to `530d5c8`.
- [Release notes](https://github.com/google/googletest/releases)
- [Commits](a1cc8c5519...530d5c8c84)

---
updated-dependencies:
- dependency-name: extern/googletest
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 13:35:01 +01:00
Dennis Klein
41ac755c57 ci: Fix dependabot gitsubmodule directory 2023-12-13 15:48:21 +01:00
Dennis Klein
6d4a82427b Update dependabot.yml 2023-12-13 15:31:14 +01:00
Dennis Klein
0966dee55d build: Enable dependabot 2023-12-13 15:26:44 +01:00
Christian Tacke
b649356c5a chore: upgrade checkout step to v4 2023-12-13 13:34:35 +01:00
Alexey Rybalchenko
2df3d909fa shm: when refCount segment size is zero, fallback to old behaviour
, which is to store reference counts inside the main data segment
2023-11-29 19:21:42 +01:00
Alexey Rybalchenko
05a2ae6a31 example: configure new script too 2023-11-29 19:21:42 +01:00
Alexey Rybalchenko
58ffdfd1f4 Remove unused ctor and constant 2023-11-29 19:21:42 +01:00
Alexey Rybalchenko
addfd071bb Fix incorrect parameters in region example scripts 2023-11-24 14:19:21 +01:00
Alexey Rybalchenko
2d27abc533 Examples: add a script for externally created region 2023-11-24 14:19:21 +01:00
Alexey Rybalchenko
faf577086a shm: fix initialization of rc segment when region is created externally 2023-11-24 14:19:21 +01:00
Alexey Rybalchenko
ff1f9b94ef shm: include rcCountSegment free memory in the monitor output 2023-11-24 14:19:21 +01:00
Alexey Rybalchenko
34e8a24c86 Examples: use multipart in the region example 2023-11-15 12:52:14 +01:00
Alexey Rybalchenko
7567a10513 shm: Bump the ref segment size 10x 2023-11-15 12:52:14 +01:00
Alexey Rybalchenko
424e22b41a shm: Throw RefCountBadAlloc if insufficient space in the ref count segment 2023-11-15 12:52:14 +01:00
Dennis Klein
961eca5276 test(PluginServices): state change subscription thread-safety 2023-11-10 13:13:13 +01:00
Alexey Rybalchenko
fbb6577625 StateMachine: Guard access to subscription containers 2023-11-10 13:13:13 +01:00
Alexey Rybalchenko
6122010694 Fix address clashes in tests 2023-10-24 15:22:21 +02:00
Giulio Eulisse
b40db42196 Use std::move rather than just move
Apparently:

  "warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]"

is default in new XCode.
2023-10-23 08:00:23 +02:00
Giulio Eulisse
f732b87def Drop unused variable
The else clause at the end makes the postincrement impossible.
2023-10-23 08:00:23 +02:00
Alexey Rybalchenko
f05a09da5a shm: Message: refactor ctors 2023-10-19 19:16:00 +02:00
Alexey Rybalchenko
5aa6c99442 shm: remove alignment member from Message 2023-10-19 19:16:00 +02:00
Alexey Rybalchenko
3c714fd9e0 Message::SetUsedSize: add optional alignment argument, to avoid storing alignment with the msg object 2023-10-19 19:16:00 +02:00
Alexey Rybalchenko
1b7532a520 Refactor shm::Message to contain sorted members of MetaHeader
Move the members of MetaHeader flat into shmem::Message and sort them by
size to reduce the size of the class.
2023-10-19 19:16:00 +02:00
Alexey Rybalchenko
f092b94c96 Update comment 2023-10-04 11:25:47 +02:00
Alexey Rybalchenko
8d28824489 Shm: Use MakeShmName to construct shm object names 2023-09-29 11:18:24 +02:00
Alexey Rybalchenko
4310d07ed1 deduplicate ipc address in a test 2023-09-29 11:18:24 +02:00
Alexey Rybalchenko
7bd31f8ff0 apply readability-else-after-return 2023-09-29 11:18:24 +02:00
Alexey Rybalchenko
1a0ab3a4e2 shm: Ref counting for unmanaged regions in a dedicated segment 2023-09-29 11:18:24 +02:00
Alexey Rybalchenko
cacf69d5f6 Replace boost::variant with std::variant 2023-09-29 11:18:24 +02:00
Alexey Rybalchenko
46f50a10ea Add example with ref-counted copy from unmanaged region 2023-09-29 11:18:24 +02:00
Alexey Rybalchenko
68038c4693 shm: Move ShmHeader into Common.h 2023-09-29 11:18:24 +02:00
Dennis Klein
1036e204d0 docs: Add "releaseNotes" field to codemeta 2023-09-11 18:07:29 +02:00
Dennis Klein
fddbbc1732 docs: Add "softwareVersion" field to codemeta 2023-09-11 18:01:42 +02:00
95 changed files with 2126 additions and 986 deletions

View File

@@ -1,3 +1,3 @@
---
Checks: 'cppcoreguidelines-*,misc-unused-alias-decls,misc-unused-parameters,modernize-pass-by-value,modernize-deprecated-headers,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-using,performance-faster-string-find,performance-for-range-copy,performance-unnecessary-copy-initialization,readability-avoid-const-params-in-decls,readability-braces-around-statements,readability-container-size-empty,readability-delete-null-pointer,readability-redundant-member-init,readability-redundant-string-init,readability-static-accessed-through-instance,readability-string-compare'
Checks: '-*,misc-unused-alias-decls,misc-unused-parameters,modernize-deprecated-headers,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-emplace,modernize-use-equals-delete,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-using,performance-faster-string-find,performance-for-range-copy,performance-unnecessary-copy-initialization,readability-avoid-const-params-in-decls,readability-braces-around-statements,readability-container-size-empty,readability-delete-null-pointer,readability-redundant-member-init,readability-redundant-string-init,readability-static-accessed-through-instance,readability-string-compare'
HeaderFilterRegex: '/(fairmq/)'

View File

@@ -0,0 +1,5 @@
{
"image": "ghcr.io/fairrootgroup/fairmq-dev/fedora-38:latest",
"features": {
}
}

78
.github/actions/setup-deps/action.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: Install dependencies
description: Setup spack and install dependencies
inputs:
gcc:
description: 'GCC version to use'
required: true
env:
description: 'Spack environment name (latest, boost187, tsan)'
default: 'latest'
fresh:
description: 'Use fresh concretization'
default: 'false'
push-gcc:
description: 'Push the freshly-built gcc node to the buildcache (buildcache workflow only)'
default: 'false'
push-token:
description: 'Token with packages:write, required when push-gcc is true'
default: ''
runs:
using: composite
# Composite action step names are not shown in the UI (https://github.com/actions/runner/issues/1877),
# so we use ::group:: to make them visible.
steps:
- name: Setup spack
uses: spack/setup-spack@v3
with:
color: true
buildcache: true
- name: Find system compiler
shell: spack-bash {0}
run: |
echo "::group::Find system compiler"
spack compiler find
echo "::endgroup::"
- name: Add FairMQ buildcache mirror
shell: spack-bash {0}
run: |
echo "::group::Add FairMQ buildcache mirror"
spack mirror add --unsigned --type binary \
ghcr-buildcache oci://ghcr.io/fairrootgroup/fairmq-spack-buildcache
echo "::endgroup::"
- name: Install GCC
shell: spack-bash {0}
env:
GITHUB_TOKEN: ${{ inputs.push-token }}
run: |
echo "::group::Install GCC"
spack install ${{ inputs.fresh == 'true' && '--fresh' || '' }} gcc@${{ inputs.gcc }} target=x86_64_v3
gcc_hash=$(spack find --json gcc@${{ inputs.gcc }} target=x86_64_v3 | jq -r 'sort_by(.version | split(".") | map(tonumber)) | last | .hash')
if [ "${{ inputs.push-gcc }}" = "true" ]; then
# Push the gcc node now, BEFORE `spack compiler find` registers it as an
# external -- externals are excluded from `buildcache push`. This is what
# lets CI pull gcc from the cache (~1 min) instead of building it (~1 h).
# --allow-missing: when gcc itself was pulled from the cache, its
# build-time dependencies are not installed locally and would
# otherwise fail the push.
spack mirror set --oci-username ${{ github.actor }} --oci-password-variable GITHUB_TOKEN ghcr-buildcache
spack buildcache push --unsigned --allow-missing ghcr-buildcache /$gcc_hash
fi
spack compiler find "$(spack location -i /$gcc_hash)"
echo "::endgroup::"
- name: Install dependencies
shell: spack-bash {0}
run: |
echo "::group::Install dependencies"
spack repo add "$GITHUB_WORKSPACE/test/ci/spack_repo/fairmq_ci"
spack env create fairmq test/ci/spack-${{ inputs.env }}.yaml
spack -e fairmq add gcc@${{ inputs.gcc }}
spack -e fairmq config add "packages:all:require:'%gcc@${{ inputs.gcc }}'"
spack -e fairmq install --fail-fast ${{ inputs.fresh == 'true' && '--fresh' || '' }}
spack env activate --sh fairmq | grep '^export ' | sed 's/^export //;s/;$//' >> $GITHUB_ENV
echo "::endgroup::"

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "dev"
schedule:
interval: "monthly"
- package-ecosystem: "gitsubmodule"
directory: "/"
target-branch: "dev"
schedule:
interval: "monthly"

82
.github/workflows/buildcache.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: Spack Buildcache
on:
workflow_dispatch:
schedule:
- cron: '0 3 * * 0' # Weekly on Sunday at 3am UTC
push:
branches: [dev, master]
paths:
- 'test/ci/spack-*.yaml'
- 'test/ci/spack_repo/**'
- '.github/workflows/buildcache.yml'
- '.github/actions/setup-deps/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.repository == 'FairRootGroup/FairMQ'
name: ${{ matrix.env }}-gcc-${{ matrix.gcc }}
runs-on: ubuntu-24.04
permissions:
packages: write
strategy:
fail-fast: false
matrix:
gcc: ['12', '13', '14', '15']
env: ['latest']
include:
- gcc: '15'
env: 'boost187'
- gcc: '15'
env: 'tsan'
steps:
- uses: actions/checkout@v6
- name: Setup spack environment
uses: ./.github/actions/setup-deps
with:
gcc: ${{ matrix.gcc }}
env: ${{ matrix.env }}
fresh: 'true'
push-gcc: 'true'
push-token: ${{ secrets.GITHUB_TOKEN }}
- name: Push to buildcache
if: ${{ !cancelled() }}
shell: spack-bash {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
spack -e fairmq mirror set --oci-username ${{ github.actor }} --oci-password-variable GITHUB_TOKEN ghcr-buildcache
# --allow-missing: build-time dependencies of specs that were
# satisfied from the buildcache are not installed locally and
# would otherwise fail the whole push.
spack -e fairmq buildcache push --unsigned --allow-missing ghcr-buildcache
update-index:
if: github.repository == 'FairRootGroup/FairMQ' && !cancelled()
needs: build
runs-on: ubuntu-24.04
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
packages: write
steps:
- uses: actions/checkout@v6
- name: Setup spack
uses: spack/setup-spack@v3
with:
color: true
- name: Update buildcache index
shell: spack-bash {0}
run: |
spack env create fairmq test/ci/spack-latest.yaml
spack -e fairmq mirror set --oci-username ${{ github.actor }} --oci-password-variable GITHUB_TOKEN ghcr-buildcache
spack -e fairmq buildcache update-index ghcr-buildcache

29
.github/workflows/check_metadata.yaml vendored Normal file
View File

@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: 2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH, Darmstadt, Germany
#
# SPDX-License-Identifier: CC0-1.0
name: Check AUTHORS and CONTRIBUTORS in metadata
on:
push:
paths:
- AUTHORS
- CONTRIBUTORS
- codemeta.json
- .zenodo.json
pull_request:
paths:
- AUTHORS
- CONTRIBUTORS
- codemeta.json
- .zenodo.json
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Try updating metadata
run: python meta_update.py
- name: Check for Updates
run: git diff --exit-code

209
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,209 @@
name: CI
on:
push:
branches: [master, dev]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.repository == 'FairRootGroup/FairMQ'
name: ${{ matrix.env }}-gcc-${{ matrix.gcc }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
gcc: ['12', '13', '14', '15']
env: ['latest']
include:
- gcc: '15'
env: 'boost187'
steps:
- uses: actions/checkout@v6
with:
submodules: true
fetch-tags: true
fetch-depth: 0
- name: Setup spack environment
uses: ./.github/actions/setup-deps
with:
gcc: ${{ matrix.gcc }}
env: ${{ matrix.env }}
- name: ccache
uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23
with:
key: ${{ github.job }}-${{ matrix.env }}-gcc${{ matrix.gcc }}
max-size: 500M
- name: Configure and Build
uses: threeal/cmake-action@725d1314ccf9ea922805d7e3f9d9bcbca892b406 # v2.1.0
with:
generator: Ninja
options: |
CMAKE_BUILD_TYPE=RelWithDebInfo
CMAKE_INSTALL_PREFIX=${{ github.workspace }}/install
BUILD_TESTING=ON
CMAKE_C_COMPILER_LAUNCHER=ccache
CMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Test
run: |
# Region/segment tests mlock() shared memory; raise the locked-memory
# limit so it does not hit RLIMIT_MEMLOCK on the runner.
sudo prlimit --pid $$ --memlock=unlimited:unlimited
ulimit -l
ctest --test-dir build --output-on-failure --no-tests=error
- name: Install
run: cmake --install build
sanitizers:
if: github.repository == 'FairRootGroup/FairMQ'
name: ${{ matrix.sanitizer.name }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
sanitizer:
- name: asan+lsan+ubsan
gcc: '14'
env: latest
options: |
ENABLE_SANITIZER_ADDRESS=ON
ENABLE_SANITIZER_LEAK=ON
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR=ON
cxx-flags: -O1 -fno-omit-frame-pointer
- name: tsan
gcc: '15'
env: tsan
options: ENABLE_SANITIZER_THREAD=ON
cxx-flags: -fno-omit-frame-pointer
steps:
- uses: actions/checkout@v6
with:
submodules: true
fetch-tags: true
fetch-depth: 0
- name: Setup spack environment
uses: ./.github/actions/setup-deps
with:
gcc: ${{ matrix.sanitizer.gcc }}
env: ${{ matrix.sanitizer.env }}
- name: ccache
uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23
with:
key: ${{ github.job }}-${{ matrix.sanitizer.name }}
max-size: 500M
- name: Locate instrumented libstdc++
if: matrix.sanitizer.name == 'tsan'
shell: spack-bash {0}
# The test processes must load the tsan-instrumented libstdc++
# instead of the compiler's own (same soname, LD_LIBRARY_PATH beats
# the RUNPATH). Set per test via ctest, not at the job level: like
# any shared library built with gcc -fsanitize=thread it has
# unresolved __tsan_* symbols, so loading it into uninstrumented
# tools (cmake, ctest, ninja) would break them.
run: |
prefix=$(spack -e fairmq location -i libstdcxx-tsan)
echo "test_library_path=$prefix/lib" >> $GITHUB_ENV
- name: Configure and Build
uses: threeal/cmake-action@725d1314ccf9ea922805d7e3f9d9bcbca892b406 # v2.1.0
with:
generator: Ninja
cxx-flags: ${{ matrix.sanitizer.cxx-flags }}
options: |
CMAKE_BUILD_TYPE=Debug
BUILD_TESTING=ON
CMAKE_C_COMPILER_LAUNCHER=ccache
CMAKE_CXX_COMPILER_LAUNCHER=ccache
FAIRMQ_TEST_LD_LIBRARY_PATH=${{ env.test_library_path }}
${{ matrix.sanitizer.options }}
- name: Verify tsan instrumentation wiring
if: matrix.sanitizer.name == 'tsan'
shell: spack-bash {0}
run: |
set -x
# the test environment must resolve libstdc++ to the instrumented copy
LD_LIBRARY_PATH=$test_library_path ldd build/test/testsuite_Channel \
| grep 'libstdc++' | tee /dev/stderr | grep -q libstdcxx-tsan
# libzmq must be instrumented
nm -D --undefined-only "$(spack -e fairmq location -i libzmq)/lib/libzmq.so" \
| grep -q __tsan_
# the instrumented libstdc++ must match the compiler release exactly,
# or binaries may reference GLIBCXX versions the runtime lacks
# (--color=never: the CI config forces SPACK_COLOR=always, which
# would wrap the version in ANSI escapes)
test "$(spack --color=never -e fairmq find --format '{version}' libstdcxx-tsan)" \
= "$(g++ -dumpfullversion)"
# the LD_LIBRARY_PATH prepend must reach the registered tests
# (via a file: grep -q quits on first match, and SIGPIPE on the
# large json output would fail the step under pipefail)
ctest --test-dir build --show-only=json-v1 > ctest-show-only.json
grep -q libstdcxx-tsan ctest-show-only.json
- name: Test
run: |
# Region/segment tests mlock() shared memory; raise the locked-memory
# limit so it does not hit RLIMIT_MEMLOCK on the runner.
sudo prlimit --pid $$ --memlock=unlimited:unlimited
ulimit -l
ctest --test-dir build --output-on-failure --no-tests=error
static-analysis:
if: github.repository == 'FairRootGroup/FairMQ'
name: static-analysis
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
submodules: true
fetch-tags: true
fetch-depth: 0
- name: Setup spack environment
uses: ./.github/actions/setup-deps
with:
gcc: '14'
- name: ccache
uses: hendrikmuhs/ccache-action@d62db5f07c26379fc4b4e0916f098a92573c3b03 # v1.2.23
with:
key: ${{ github.job }}
max-size: 500M
- name: Configure
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DBUILD_TESTING=ON \
-DRUN_STATIC_ANALYSIS=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build
run: |
set -o pipefail
cmake --build build 2>&1 | tee build.log
- name: Check for warnings
run: |
test -f build.log || { echo "::error::build.log was not produced"; exit 1; }
if grep -q "warning:" build.log; then
echo "::error::Static analysis found warnings"
grep "warning:" build.log
exit 1
fi

View File

@@ -16,6 +16,6 @@ jobs:
container:
image: gitlab-registry.in2p3.fr/escape2020/wp3/eossr:v1.0
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: validate codemeta
run: eossr-metadata-validator codemeta.json

View File

@@ -1,5 +1,5 @@
################################################################################
# Copyright (C) 2018-2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# Copyright (C) 2018-2024 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# #
# This software is distributed under the terms of the #
# GNU Lesser General Public Licence (LGPL) version 3, #
@@ -8,8 +8,7 @@
# Project ######################################################################
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
cmake_policy(VERSION 3.15...3.26)
cmake_minimum_required(VERSION 3.15...3.30 FATAL_ERROR)
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
include(GitHelper)
@@ -47,6 +46,13 @@ include(FairMQDependencies)
# Targets ######################################################################
if(FAIRMQ_TEST_LD_LIBRARY_PATH AND CMAKE_VERSION VERSION_LESS 3.22)
# The per-test injection relies on ctest's ENVIRONMENT_MODIFICATION, which
# older CMake silently drops -- the tests would run against the default
# runtime while looking green.
message(FATAL_ERROR "FAIRMQ_TEST_LD_LIBRARY_PATH requires CMake >= 3.22")
endif()
if(BUILD_FAIRMQ)
add_subdirectory(fairmq)
endif()

View File

@@ -1,93 +0,0 @@
################################################################################
# Copyright (C) 2021-2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# #
# This software is distributed under the terms of the #
# GNU Lesser General Public Licence (LGPL) version 3, #
# copied verbatim in the file "LICENSE" #
################################################################################
cmake_host_system_information(RESULT fqdn QUERY FQDN)
set(CTEST_SOURCE_DIRECTORY .)
set(CTEST_BINARY_DIRECTORY build)
set(CTEST_CMAKE_GENERATOR "Ninja")
set(CTEST_USE_LAUNCHERS ON)
set(CTEST_CONFIGURATION_TYPE "RelWithDebInfo")
set(CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE 102400)
if(NOT NCPUS)
if(ENV{SLURM_CPUS_PER_TASK})
set(NCPUS $ENV{SLURM_CPUS_PER_TASK})
else()
include(ProcessorCount)
ProcessorCount(NCPUS)
if(NCPUS EQUAL 0)
set(NCPUS 1)
endif()
endif()
endif()
if ("$ENV{CTEST_SITE}" STREQUAL "")
set(CTEST_SITE "${fqdn}")
else()
set(CTEST_SITE $ENV{CTEST_SITE})
endif()
if ("$ENV{LABEL}" STREQUAL "")
set(CTEST_BUILD_NAME "build")
else()
set(CTEST_BUILD_NAME $ENV{LABEL})
endif()
ctest_start(Continuous)
list(APPEND options "-DDISABLE_COLOR=ON" "-DBUILD_EXAMPLES=ON" "-DBUILD_TESTING=ON")
if(RUN_STATIC_ANALYSIS)
list(APPEND options "-DRUN_STATIC_ANALYSIS=ON")
endif()
if(CMAKE_BUILD_TYPE)
set(CTEST_CONFIGURATION_TYPE ${CMAKE_BUILD_TYPE})
endif()
if(ENABLE_SANITIZER_ADDRESS)
list(APPEND options "-DENABLE_SANITIZER_ADDRESS=ON")
endif()
if(ENABLE_SANITIZER_LEAK)
list(APPEND options "-DENABLE_SANITIZER_LEAK=ON")
endif()
if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR)
list(APPEND options "-DENABLE_SANITIZER_UNDEFINED_BEHAVIOR=ON")
endif()
if(ENABLE_SANITIZER_MEMORY)
list(APPEND options "-DENABLE_SANITIZER_MEMORY=ON")
endif()
if(ENABLE_SANITIZER_THREAD)
list(APPEND options "-DENABLE_SANITIZER_THREAD=ON")
endif()
if(CMAKE_CXX_COMPILER)
list(APPEND options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}")
endif()
if(CMAKE_CXX_FLAGS)
list(APPEND options "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}")
endif()
list(REMOVE_DUPLICATES options)
list(JOIN options ";" optionsstr)
ctest_configure(OPTIONS "${optionsstr}")
ctest_submit()
ctest_build(FLAGS "-j${NCPUS}")
ctest_submit()
if(NOT RUN_STATIC_ANALYSIS)
ctest_test(BUILD "${CTEST_BINARY_DIRECTORY}"
PARALLEL_LEVEL ${NCPUS}
SCHEDULE_RANDOM ON
RETURN_VALUE _ctest_test_ret_val)
ctest_submit()
endif()
if(_ctest_test_ret_val)
Message(FATAL_ERROR "Some tests failed.")
endif()

114
Jenkinsfile vendored
View File

@@ -1,114 +0,0 @@
#!groovy
def jobMatrix(String type, List specs) {
def nodes = [:]
for (spec in specs) {
def job = ""
def selector = "slurm"
def os = ""
def ver = ""
if (type == 'build') {
job = "${spec.os}-${spec.ver}-${spec.arch}-${spec.compiler}"
if (spec.os =~ /^macos/) {
selector = "${spec.os}-${spec.ver}-${spec.arch}"
}
os = spec.os
ver = spec.ver
} else { // == 'check'
job = "${spec.name}"
os = 'fedora'
ver = '36'
}
def label = "${job}"
def extra = spec.extra
nodes[label] = {
node(selector) {
githubNotify(context: "${label}", description: 'Building ...', status: 'PENDING')
try {
deleteDir()
checkout scm
def jobscript = 'job.sh'
def ctestcmd = "ctest ${extra} -S FairMQTest.cmake -V --output-on-failure"
sh "echo \"set -e\" >> ${jobscript}"
sh "echo \"export LABEL=\\\"\${JOB_BASE_NAME} ${label}\\\"\" >> ${jobscript}"
if (selector =~ /^macos/) {
sh """\
echo \"${ctestcmd}\" >> ${jobscript}
"""
sh "cat ${jobscript}"
sh "bash ${jobscript}"
} else { // selector == "slurm"
def imageurl = "oras://ghcr.io/fairrootgroup/fairmq-dev/${os}-${ver}-sif:latest"
def execopts = "--net --ipc --uts --pid -B/shared"
def containercmd = "singularity exec ${execopts} ${imageurl} bash -l -c \\\"${ctestcmd} ${extra}\\\""
sh """\
echo \"echo \\\"*** Job started at .......: \\\$(date -R)\\\"\" >> ${jobscript}
echo \"echo \\\"*** Job ID ...............: \\\${SLURM_JOB_ID}\\\"\" >> ${jobscript}
echo \"echo \\\"*** Compute node .........: \\\$(hostname -f)\\\"\" >> ${jobscript}
echo \"unset http_proxy\" >> ${jobscript}
echo \"unset HTTP_PROXY\" >> ${jobscript}
echo \"${containercmd}\" >> ${jobscript}
"""
sh "cat ${jobscript}"
sh "test/ci/slurm-submit.sh \"FairMQ \${JOB_BASE_NAME} ${label}\" ${jobscript}"
if (job == "static-analyzers") {
recordIssues(enabledForFailure: true,
tools: [gcc(pattern: 'build/Testing/Temporary/*.log')],
filters: [excludeFile('extern/*'), excludeFile('usr/*')],
skipBlames: true,
skipPublishingChecks: true)
}
}
deleteDir()
githubNotify(context: "${label}", description: 'Success', status: 'SUCCESS')
} catch (e) {
deleteDir()
githubNotify(context: "${label}", description: 'Error', status: 'ERROR')
throw e
}
}
}
}
return nodes
}
pipeline{
agent none
stages {
stage("CI") {
steps{
script {
def builds = jobMatrix('build', [
[os: 'ubuntu', ver: '20.04', arch: 'x86_64', compiler: 'gcc-9'],
[os: 'ubuntu', ver: '22.04', arch: 'x86_64', compiler: 'gcc-11'],
[os: 'fedora', ver: '33', arch: 'x86_64', compiler: 'gcc-10'],
[os: 'fedora', ver: '34', arch: 'x86_64', compiler: 'gcc-11'],
[os: 'fedora', ver: '35', arch: 'x86_64', compiler: 'gcc-11'],
[os: 'fedora', ver: '36', arch: 'x86_64', compiler: 'gcc-12'],
[os: 'fedora', ver: '37', arch: 'x86_64', compiler: 'gcc-12'],
[os: 'fedora', ver: '38', arch: 'x86_64', compiler: 'gcc-13'],
[os: 'macos', ver: '12', arch: 'x86_64', compiler: 'apple-clang-14'],
[os: 'macos', ver: '13', arch: 'arm64', compiler: 'apple-clang-14'],
])
def all_debug = "-DCMAKE_BUILD_TYPE=Debug"
def checks = jobMatrix('check', [
[name: 'static-analyzers', extra: "${all_debug} -DRUN_STATIC_ANALYSIS=ON"],
[name: '{address,leak,ub}-sanitizers',
extra: "${all_debug} -DENABLE_SANITIZER_ADDRESS=ON -DENABLE_SANITIZER_LEAK=ON -DENABLE_SANITIZER_UNDEFINED_BEHAVIOUR=ON -DCMAKE_CXX_FLAGS='-O1 -fno-omit-frame-pointer'"],
[name: 'thread-sanitizer', extra: "${all_debug} -DENABLE_SANITIZER_THREAD=ON -DCMAKE_CXX_COMPILER=clang++"],
])
parallel(builds + checks)
}
}
}
}
}

View File

@@ -45,9 +45,9 @@ Recommended:
```bash
git clone https://github.com/FairRootGroup/FairMQ fairmq_source
cmake -S fairmq_source -B fairmq_build -GNinja -DCMAKE_BUILD_TYPE=Release
cmake -S fairmq_source -B fairmq_build -GNinja -DCMAKE_BUILD_TYPE=Release [-DBUILD_TESTING=ON]
cmake --build fairmq_build
ctest --test-dir fairmq_build --output-on-failure --schedule-random -j<ncpus>
[ctest --test-dir fairmq_build --output-on-failure --schedule-random -j<ncpus>] # needs -DBUILD_TESTING=ON
cmake --install fairmq_build --prefix $(pwd)/fairmq_install
```
@@ -56,6 +56,24 @@ Please consult the [manpages of your CMake version](https://cmake.org/cmake/help
If dependencies are not installed in standard system directories, you can hint the installation location via
`-DCMAKE_PREFIX_PATH=...` or per dependency via `-D{DEPENDENCY}_ROOT=...` (`*_ROOT` variables can also be environment variables).
## Installation via Spack
Prerequisite: [Spack](https://spack.readthedocs.io/en/latest/getting_started.html)
```bash
spack info fairmq # inspect build options
spack install fairmq # build latest packaged version with default options
```
Build FairMQ's dependencies via Spack for development:
```bash
git clone -b dev https://github.com/FairRootGroup/FairMQ fairmq_source
spack --env fairmq_source install # installs deps declared in fairmq_source/spack.yaml
spack env activate fairmq_source # sets $CMAKE_PREFIX_PATH which is used by CMake to find FairMQ's deps
cmake -S fairmq_source -B fairmq_build -GNinja -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
# develop, compile, test
spack env deactivate # at end of dev session, or simply close the shell
```
## Usage

View File

@@ -68,6 +68,7 @@ function(build_bundled package bundle)
exec(${CMAKE_COMMAND} -S ${${package}_SOURCE_DIR} -B ${${package}_BINARY_DIR} -G ${CMAKE_GENERATOR}
-DCMAKE_INSTALL_PREFIX=${${package}_INSTALL_DIR} -DBUILD_GMOCK=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
)
exec(${CMAKE_COMMAND} --build ${${package}_BINARY_DIR})
exec(${CMAKE_COMMAND} --build ${${package}_BINARY_DIR} --target install)

View File

@@ -1,5 +1,5 @@
################################################################################
# Copyright (C) 2018-2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# Copyright (C) 2018-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# #
# This software is distributed under the terms of the #
# GNU Lesser General Public Licence (LGPL) version 3, #
@@ -23,6 +23,18 @@ if(BUILD_FAIRMQ OR BUILD_TIDY_TOOL)
find_package2(PUBLIC Boost REQUIRED VERSION 1.66
COMPONENTS container program_options filesystem date_time regex
)
# Check Boost.Process compatibility
# Boost 1.88 has broken Boost.Process v2 without v1 compatibility headers
# Boost 1.89+ provides <boost/process/v1.hpp> for the old API
if(Boost_VERSION VERSION_EQUAL "1.88.0")
message(FATAL_ERROR "Boost version 1.88 is not supported due to Boost.Process API changes. "
"Please use Boost < 1.88 or >= 1.89")
endif()
if(Boost_VERSION VERSION_GREATER_EQUAL "1.89")
set(FAIRMQ_BOOST_PROCESS_V1_HEADER ON CACHE INTERNAL "Use boost/process/v1.hpp for Boost >= 1.89")
endif()
endif()
if(BUILD_FAIRMQ)
@@ -41,7 +53,7 @@ if(BUILD_TESTING)
endif()
find_package2(BUNDLED GTest REQUIRED)
if(GTest_BUNDLED)
set(GTest_VERSION "Apr 8 2022 @a1cc8c55")
set(GTest_VERSION "Dec 26 2024 @7d76a23")
set(GTest_PREFIX "<bundled>")
endif()
endif()

View File

@@ -54,8 +54,8 @@ endif()
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${PROJECT_INSTALL_LIBDIR}" isSystemDir)
if("${isSystemDir}" STREQUAL "-1")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND CMAKE_EXE_LINKER_FLAGS "-Wl,--enable-new-dtags")
list(APPEND CMAKE_SHARED_LINKER_FLAGS "-Wl,--enable-new-dtags")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,--enable-new-dtags")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--enable-new-dtags")
list(PREPEND CMAKE_INSTALL_RPATH "$ORIGIN/../${PROJECT_INSTALL_LIBDIR}")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
list(PREPEND CMAKE_INSTALL_RPATH "@loader_path/../${PROJECT_INSTALL_LIBDIR}")

View File

@@ -6,6 +6,8 @@
"license": "./COPYRIGHT",
"datePublished": "2018-04-15",
"developmentStatus": "active",
"softwareVersion": "master",
"releaseNotes": "https://github.com/FairRootGroup/FairMQ/releases",
"codeRepository": "https://github.com/FairRootGroup/FairMQ/",
"readme": "https://github.com/FairRootGroup/FairMQ/#readme",
"issueTracker": "https://github.com/FairRootGroup/FairMQ/issues",

View File

@@ -12,6 +12,22 @@ set(test_script_prefix "test-ex")
set(testsuite "Example")
set(transports "zeromq" "shmem")
# Environment for every example test (directory scope, so the subdirectories
# see it too)
set(env_mods)
if(ENABLE_SANITIZER_LEAK AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.22)
get_filename_component(lsan_supps "${CMAKE_SOURCE_DIR}/test/leak_sanitizer_suppressions.txt" ABSOLUTE)
list(APPEND env_mods "LSAN_OPTIONS=set:suppressions=${lsan_supps}")
endif()
# Run the example tests against an alternative runtime library directory as
# well (see test/CMakeLists.txt). The test scripts only launch instrumented
# binaries (bash itself does not link libstdc++). Note: unlike the
# testsuites, the example binaries get no locale-cache warmup -- their
# main() comes from the installed public header fairmq/runDevice.h.
if(FAIRMQ_TEST_LD_LIBRARY_PATH)
list(APPEND env_mods "LD_LIBRARY_PATH=path_list_prepend:${FAIRMQ_TEST_LD_LIBRARY_PATH}")
endif()
function(add_example)
cmake_parse_arguments(PARSE_ARGV 0 ARG
"CONFIG;NO_TRANSPORT;NO_TEST"
@@ -29,11 +45,6 @@ function(add_example)
message(FATAL_ERROR "NAME arg is required")
endif()
if(ENABLE_SANITIZER_LEAK AND CMAKE_VERSION VERSION_GREATER_EQUAL 3.22)
get_filename_component(lsan_supps "${CMAKE_SOURCE_DIR}/test/leak_sanitizer_suppressions.txt" ABSOLUTE)
set(lsan_options "LSAN_OPTIONS=set:suppressions=${lsan_supps}")
endif()
if(ARG_DEVICE)
set(exe_targets)
foreach(device IN LISTS ARG_DEVICE)
@@ -61,7 +72,7 @@ function(add_example)
set(FAIRMQ_BIN_DIR ${CMAKE_BINARY_DIR}/fairmq)
foreach(script IN LISTS scripts)
set(script_file "${script_prefix}-${script}.sh")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${script_file}.in" "${CMAKE_CURRENT_BINARY_DIR}/${script_file}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${script_file}.in" "${CMAKE_CURRENT_BINARY_DIR}/${script_file}" @ONLY)
endforeach()
if(ARG_CONFIG)
@@ -78,8 +89,8 @@ function(add_example)
set(test "${testsuite}.${name}.${transport}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport})
set_tests_properties(${test} PROPERTIES TIMEOUT "30")
if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
if(env_mods)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}")
endif()
else()
foreach(transport IN LISTS transports)
@@ -88,16 +99,16 @@ function(add_example)
set(test "${testsuite}.${name}.${variant}.${transport}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport} ${variant})
set_tests_properties(${test} PROPERTIES TIMEOUT "30")
if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
if(env_mods)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}")
endif()
endforeach()
else()
set(test "${testsuite}.${name}.${transport}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport})
set_tests_properties(${test} PROPERTIES TIMEOUT "30")
if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
if(env_mods)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}")
endif()
endif()
endforeach()
@@ -119,7 +130,7 @@ function(add_example)
set(FAIRMQ_BIN_DIR ${CMAKE_INSTALL_PREFIX}/${PROJECT_INSTALL_BINDIR}/fairmq)
foreach(script IN LISTS scripts)
set(script_file "${script_prefix}-${script}.sh")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${script_file}.in" "${CMAKE_CURRENT_BINARY_DIR}/${script_file}_install")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${script_file}.in" "${CMAKE_CURRENT_BINARY_DIR}/${script_file}_install" @ONLY)
install(
PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${script_file}_install"
DESTINATION ${PROJECT_INSTALL_BINDIR}

View File

@@ -15,6 +15,8 @@ set_target_properties(${exe} PROPERTIES ENABLE_EXPORTS ON)
set(test "${testsuite}.${name}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${exe})
set_tests_properties(${test} PROPERTIES TIMEOUT 30)
if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
# (the previous `if(lsan_options)` here could never fire -- that variable
# only ever existed in the add_example() function scope)
if(env_mods)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}")
endif()

View File

@@ -7,5 +7,6 @@
################################################################################
add_example(NAME region
DEVICE sampler sink keep-alive
DEVICE sampler processor sink keep-alive
SCRIPT region region-advanced region-advanced-external
)

View File

@@ -0,0 +1,95 @@
#!/bin/bash
export FAIRMQ_PATH=@FAIRMQ_BIN_DIR@
transport=${1:-shmem}
msgSize=${2:-1000000}
SAMPLER="fairmq-ex-region-sampler"
SAMPLER+=" --id sampler1"
# SAMPLER+=" --sampling-rate 10"
SAMPLER+=" --severity debug"
SAMPLER+=" --msg-size $msgSize"
SAMPLER+=" --transport $transport"
SAMPLER+=" --shmid 1"
SAMPLER+=" --shm-monitor false"
SAMPLER+=" --rc-segment-size 200000000"
SAMPLER+=" --external-region true"
SAMPLER+=" --shm-no-cleanup true"
SAMPLER+=" --chan-name data1"
SAMPLER+=" --channel-config name=data1,type=push,method=bind,address=tcp://127.0.0.1:7777"
xterm -geometry 90x60+0+0 -hold -e @EX_BIN_DIR@/$SAMPLER &
PROCESSOR1="fairmq-ex-region-processor"
PROCESSOR1+=" --id processor1"
PROCESSOR1+=" --severity debug"
PROCESSOR1+=" --transport $transport"
PROCESSOR1+=" --shmid 1"
PROCESSOR1+=" --shm-segment-id 1"
PROCESSOR1+=" --shm-monitor false"
PROCESSOR1+=" --shm-no-cleanup true"
PROCESSOR1+=" --channel-config name=data1,type=pull,method=connect,address=tcp://127.0.0.1:7777"
PROCESSOR1+=" name=data2,type=push,method=bind,address=tcp://127.0.0.1:7778"
PROCESSOR1+=" name=data3,type=push,method=bind,address=tcp://127.0.0.1:7779"
xterm -geometry 90x40+550+40 -hold -e @EX_BIN_DIR@/$PROCESSOR1 &
PROCESSOR2="fairmq-ex-region-processor"
PROCESSOR2+=" --id processor2"
PROCESSOR2+=" --severity debug"
PROCESSOR2+=" --transport $transport"
PROCESSOR2+=" --shmid 1"
PROCESSOR2+=" --shm-segment-id 2"
PROCESSOR2+=" --shm-monitor false"
PROCESSOR2+=" --shm-no-cleanup true"
PROCESSOR2+=" --channel-config name=data1,type=pull,method=connect,address=tcp://127.0.0.1:7777"
PROCESSOR2+=" name=data2,type=push,method=bind,address=tcp://127.0.0.1:7788"
PROCESSOR2+=" name=data3,type=push,method=bind,address=tcp://127.0.0.1:7789"
xterm -geometry 90x40+550+600 -hold -e @EX_BIN_DIR@/$PROCESSOR2 &
SINK1_1="fairmq-ex-region-sink"
SINK1_1+=" --id sink1_1"
SINK1_1+=" --severity debug"
SINK1_1+=" --chan-name data2"
SINK1_1+=" --transport $transport"
SINK1_1+=" --shmid 1"
SINK1_1+=" --shm-segment-id 1"
SINK1_1+=" --shm-monitor false"
SINK1_1+=" --shm-no-cleanup true"
SINK1_1+=" --channel-config name=data2,type=pull,method=connect,address=tcp://127.0.0.1:7778"
xterm -geometry 90x20+1100+0 -hold -e @EX_BIN_DIR@/$SINK1_1 &
SINK1_2="fairmq-ex-region-sink"
SINK1_2+=" --id sink1_2"
SINK1_2+=" --severity debug"
SINK1_2+=" --chan-name data3"
SINK1_2+=" --transport $transport"
SINK1_2+=" --shmid 1"
SINK1_2+=" --shm-segment-id 1"
SINK1_2+=" --shm-monitor false"
SINK1_2+=" --shm-no-cleanup true"
SINK1_2+=" --channel-config name=data3,type=pull,method=connect,address=tcp://127.0.0.1:7779"
xterm -geometry 90x20+1100+300 -hold -e @EX_BIN_DIR@/$SINK1_2 &
SINK2_1="fairmq-ex-region-sink"
SINK2_1+=" --id sink2_1"
SINK2_1+=" --severity debug"
SINK2_1+=" --chan-name data2"
SINK2_1+=" --transport $transport"
SINK2_1+=" --shmid 1"
SINK2_1+=" --shm-segment-id 2"
SINK2_1+=" --shm-monitor false"
SINK2_1+=" --shm-no-cleanup true"
SINK2_1+=" --channel-config name=data2,type=pull,method=connect,address=tcp://127.0.0.1:7788"
xterm -geometry 90x20+1100+600 -hold -e @EX_BIN_DIR@/$SINK2_1 &
SINK2_2="fairmq-ex-region-sink"
SINK2_2+=" --id sink2_2"
SINK2_2+=" --severity debug"
SINK2_2+=" --chan-name data3"
SINK2_2+=" --transport $transport"
SINK2_2+=" --shmid 1"
SINK2_2+=" --shm-segment-id 2"
SINK2_2+=" --shm-monitor false"
SINK2_2+=" --shm-no-cleanup true"
SINK2_2+=" --channel-config name=data3,type=pull,method=connect,address=tcp://127.0.0.1:7789"
xterm -geometry 90x20+1100+900 -hold -e @EX_BIN_DIR@/$SINK2_2 &

View File

@@ -0,0 +1,80 @@
#!/bin/bash
export FAIRMQ_PATH=@FAIRMQ_BIN_DIR@
transport=${1:-shmem}
msgSize=${2:-1000000}
SAMPLER="fairmq-ex-region-sampler"
SAMPLER+=" --id sampler1"
# SAMPLER+=" --sampling-rate 10"
SAMPLER+=" --severity debug"
SAMPLER+=" --msg-size $msgSize"
SAMPLER+=" --transport $transport"
#SAMPLER+=" --rc-segment-size 0"
SAMPLER+=" --shm-monitor true"
SAMPLER+=" --chan-name data1"
SAMPLER+=" --channel-config name=data1,type=push,method=bind,address=tcp://127.0.0.1:7777"
xterm -geometry 90x60+0+0 -hold -e @EX_BIN_DIR@/$SAMPLER &
PROCESSOR1="fairmq-ex-region-processor"
PROCESSOR1+=" --id processor1"
PROCESSOR1+=" --severity debug"
PROCESSOR1+=" --transport $transport"
PROCESSOR1+=" --shm-segment-id 1"
PROCESSOR1+=" --shm-monitor true"
PROCESSOR1+=" --channel-config name=data1,type=pull,method=connect,address=tcp://127.0.0.1:7777"
PROCESSOR1+=" name=data2,type=push,method=bind,address=tcp://127.0.0.1:7778"
PROCESSOR1+=" name=data3,type=push,method=bind,address=tcp://127.0.0.1:7779"
xterm -geometry 90x40+550+40 -hold -e @EX_BIN_DIR@/$PROCESSOR1 &
PROCESSOR2="fairmq-ex-region-processor"
PROCESSOR2+=" --id processor2"
PROCESSOR2+=" --severity debug"
PROCESSOR2+=" --transport $transport"
PROCESSOR2+=" --shm-segment-id 2"
PROCESSOR2+=" --shm-monitor true"
PROCESSOR2+=" --channel-config name=data1,type=pull,method=connect,address=tcp://127.0.0.1:7777"
PROCESSOR2+=" name=data2,type=push,method=bind,address=tcp://127.0.0.1:7788"
PROCESSOR2+=" name=data3,type=push,method=bind,address=tcp://127.0.0.1:7789"
xterm -geometry 90x40+550+600 -hold -e @EX_BIN_DIR@/$PROCESSOR2 &
SINK1_1="fairmq-ex-region-sink"
SINK1_1+=" --id sink1_1"
SINK1_1+=" --severity debug"
SINK1_1+=" --chan-name data2"
SINK1_1+=" --transport $transport"
SINK1_1+=" --shm-segment-id 1"
SINK1_1+=" --shm-monitor true"
SINK1_1+=" --channel-config name=data2,type=pull,method=connect,address=tcp://127.0.0.1:7778"
xterm -geometry 90x20+1100+0 -hold -e @EX_BIN_DIR@/$SINK1_1 &
SINK1_2="fairmq-ex-region-sink"
SINK1_2+=" --id sink1_2"
SINK1_2+=" --severity debug"
SINK1_2+=" --chan-name data3"
SINK1_2+=" --transport $transport"
SINK1_2+=" --shm-segment-id 1"
SINK1_2+=" --shm-monitor true"
SINK1_2+=" --channel-config name=data3,type=pull,method=connect,address=tcp://127.0.0.1:7779"
xterm -geometry 90x20+1100+300 -hold -e @EX_BIN_DIR@/$SINK1_2 &
SINK2_1="fairmq-ex-region-sink"
SINK2_1+=" --id sink2_1"
SINK2_1+=" --severity debug"
SINK2_1+=" --chan-name data2"
SINK2_1+=" --transport $transport"
SINK2_1+=" --shm-segment-id 2"
SINK2_1+=" --shm-monitor true"
SINK2_1+=" --channel-config name=data2,type=pull,method=connect,address=tcp://127.0.0.1:7788"
xterm -geometry 90x20+1100+600 -hold -e @EX_BIN_DIR@/$SINK2_1 &
SINK2_2="fairmq-ex-region-sink"
SINK2_2+=" --id sink2_2"
SINK2_2+=" --severity debug"
SINK2_2+=" --chan-name data3"
SINK2_2+=" --transport $transport"
SINK2_2+=" --shm-segment-id 2"
SINK2_2+=" --shm-monitor true"
SINK2_2+=" --channel-config name=data3,type=pull,method=connect,address=tcp://127.0.0.1:7789"
xterm -geometry 90x20+1100+900 -hold -e @EX_BIN_DIR@/$SINK2_2 &

View File

@@ -2,16 +2,8 @@
export FAIRMQ_PATH=@FAIRMQ_BIN_DIR@
transport="shmem"
msgSize="1000000"
if [[ $1 =~ ^[a-z]+$ ]]; then
transport=$1
fi
if [[ $2 =~ ^[0-9]+$ ]]; then
msgSize=$1
fi
transport=${1:-shmem}
msgSize=${2:-1000000}
SAMPLER="fairmq-ex-region-sampler"
SAMPLER+=" --id sampler1"

View File

@@ -95,10 +95,11 @@ struct ShmManager
uint64_t size = stoull(conf.at(1));
fair::mq::RegionConfig cfg;
cfg.id = id;
cfg.rcSegmentSize = 0;
cfg.size = size;
regionCfgs.push_back(cfg);
auto ret = regions.emplace(id, make_unique<fair::mq::shmem::UnmanagedRegion>(shmId, id, size));
auto ret = regions.emplace(id, make_unique<fair::mq::shmem::UnmanagedRegion>(shmId, cfg));
fair::mq::shmem::UnmanagedRegion& region = *(ret.first->second);
LOG(info) << "Created unamanged region " << id << " of size " << region.GetSize()
<< ", starting at " << region.GetData() << ". Locking...";

View File

@@ -0,0 +1,80 @@
/********************************************************************************
* Copyright (C) 2014-2021 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" *
********************************************************************************/
#include <fairmq/Device.h>
#include <fairmq/runDevice.h>
#include <memory>
namespace bpo = boost::program_options;
using namespace std;
using namespace fair::mq;
namespace {
struct Processor : Device
{
void InitTask() override
{
fMaxIterations = fConfig->GetProperty<uint64_t>("max-iterations");
GetChannel("data1", 0).Transport()->SubscribeToRegionEvents([](RegionInfo info) {
LOG(info) << "Region event: " << info.event << ": "
<< (info.managed ? "managed" : "unmanaged") << ", id: " << info.id
<< ", ptr: " << info.ptr << ", size: " << info.size
<< ", flags: " << info.flags;
});
}
void Run() override
{
Channel& dataIn = GetChannel("data1", 0);
Channel& dataOut1 = GetChannel("data2", 0);
Channel& dataOut2 = GetChannel("data3", 0);
while (!NewStatePending()) {
fair::mq::Parts inParts;
dataIn.Receive(inParts);
fair::mq::Parts outParts1;
fair::mq::Parts outParts2;
for (const auto& inPart : inParts) {
outParts1.AddPart(NewMessage());
outParts1.fParts.back()->Copy(*inPart);
outParts2.AddPart(NewMessage());
outParts2.fParts.back()->Copy(*inPart);
}
dataOut1.Send(outParts1);
dataOut2.Send(outParts2);
if (fMaxIterations > 0 && ++fNumIterations >= fMaxIterations) {
LOG(info) << "Configured max number of iterations reached. Leaving RUNNING state.";
break;
}
}
}
void ResetTask() override
{
GetChannel("data1", 0).Transport()->UnsubscribeFromRegionEvents();
}
private:
uint64_t fMaxIterations = 0;
uint64_t fNumIterations = 0;
};
} // namespace
void addCustomOptions(bpo::options_description& options)
{
options.add_options()("max-iterations", bpo::value<uint64_t>()->default_value(0), "Maximum number of iterations of Run/ConditionalRun/OnData (0 - infinite)");
}
unique_ptr<Device> getDevice(ProgOptions& /*config*/) { return make_unique<Processor>(); }

View File

@@ -8,6 +8,7 @@
#include <fairmq/Device.h>
#include <fairmq/runDevice.h>
#include <fairmq/tools/RateLimit.h>
#include <cstdint>
#include <mutex>
@@ -23,8 +24,11 @@ struct Sampler : fair::mq::Device
fMsgSize = fConfig->GetProperty<int>("msg-size");
fLinger = fConfig->GetProperty<uint32_t>("region-linger");
fMaxIterations = fConfig->GetProperty<uint64_t>("max-iterations");
fChanName = fConfig->GetProperty<std::string>("chan-name");
fSamplingRate = fConfig->GetProperty<float>("sampling-rate");
fRCSegmentSize = fConfig->GetProperty<uint64_t>("rc-segment-size");
GetChannel("data", 0).Transport()->SubscribeToRegionEvents([](fair::mq::RegionInfo info) {
GetChannel(fChanName, 0).Transport()->SubscribeToRegionEvents([](fair::mq::RegionInfo info) {
LOG(info) << "Region event: " << info.event << ": "
<< (info.managed ? "managed" : "unmanaged")
<< ", id: " << info.id
@@ -42,8 +46,9 @@ struct Sampler : fair::mq::Device
}
regionCfg.lock = !fExternalRegion; // mlock region after creation
regionCfg.zero = !fExternalRegion; // zero region content after creation
regionCfg.rcSegmentSize = fRCSegmentSize; // size of the corresponding reference count segment
fRegion = fair::mq::UnmanagedRegionPtr(NewUnmanagedRegionFor(
"data", // region is created using the transport of this channel...
fChanName, // region is created using the transport of this channel...
0, // ... and this sub-channel
10000000, // region size
[this](const std::vector<fair::mq::RegionBlock>& blocks) { // callback to be called when message buffers no longer needed by transport
@@ -59,22 +64,33 @@ struct Sampler : fair::mq::Device
void Run() override
{
fair::mq::tools::RateLimiter rateLimiter(fSamplingRate);
while (!NewStatePending()) {
fair::mq::MessagePtr msg(NewMessageFor("data", // channel
fair::mq::Parts parts;
// make 64 parts
for (int i = 0; i < 64; ++i) {
parts.AddPart(NewMessageFor(
fChanName, // channel
0, // sub-channel
fRegion, // region
fRegion->GetData(), // ptr within region
fMsgSize, // offset from ptr
nullptr // hint
));
}
std::lock_guard<std::mutex> lock(fMtx);
++fNumUnackedMsgs;
if (Send(msg, "data", 0) > 0) {
fNumUnackedMsgs += parts.Size();
if (Send(parts, fChanName, 0) > 0) {
if (fMaxIterations > 0 && ++fNumIterations >= fMaxIterations) {
LOG(info) << "Configured maximum number of iterations reached. Stopping sending.";
break;
}
if (fSamplingRate > 0.001) {
rateLimiter.maybe_sleep();
}
}
}
@@ -99,7 +115,7 @@ struct Sampler : fair::mq::Device
void ResetTask() override
{
fRegion.reset();
GetChannel("data", 0).Transport()->UnsubscribeFromRegionEvents();
GetChannel(fChanName, 0).Transport()->UnsubscribeFromRegionEvents();
}
private:
@@ -108,18 +124,24 @@ struct Sampler : fair::mq::Device
uint32_t fLinger = 100;
uint64_t fMaxIterations = 0;
uint64_t fNumIterations = 0;
uint64_t fRCSegmentSize = 10000000;
fair::mq::UnmanagedRegionPtr fRegion = nullptr;
std::mutex fMtx;
uint64_t fNumUnackedMsgs = 0;
std::string fChanName;
float fSamplingRate = 0.;
};
void addCustomOptions(bpo::options_description& options)
{
options.add_options()
("chan-name", bpo::value<std::string>()->default_value("data"), "name of the output channel")
("msg-size", bpo::value<int>()->default_value(1000), "Message size in bytes")
("sampling-rate", bpo::value<float>()->default_value(0.), "Sampling rate (Hz).")
("region-linger", bpo::value<uint32_t>()->default_value(100), "Linger period for regions")
("max-iterations", bpo::value<uint64_t>()->default_value(0), "Maximum number of iterations of Run/ConditionalRun/OnData (0 - infinite)")
("external-region", bpo::value<bool>()->default_value(false), "Use region created by another process");
("external-region", bpo::value<bool>()->default_value(false), "Use region created by another process")
("rc-segment-size", bpo::value<uint64_t>()->default_value(10000000), "Size of the reference count segment for Unamanged Region");
}
std::unique_ptr<fair::mq::Device> getDevice(fair::mq::ProgOptions& /*config*/)

View File

@@ -22,7 +22,8 @@ struct Sink : Device
{
// Get the fMaxIterations value from the command line options (via fConfig)
fMaxIterations = fConfig->GetProperty<uint64_t>("max-iterations");
GetChannel("data", 0).Transport()->SubscribeToRegionEvents([](RegionInfo info) {
fChanName = fConfig->GetProperty<std::string>("chan-name");
GetChannel(fChanName, 0).Transport()->SubscribeToRegionEvents([](RegionInfo info) {
LOG(info) << "Region event: " << info.event << ": "
<< (info.managed ? "managed" : "unmanaged") << ", id: " << info.id
<< ", ptr: " << info.ptr << ", size: " << info.size
@@ -32,15 +33,11 @@ struct Sink : Device
void Run() override
{
Channel& dataInChannel = GetChannel("data", 0);
Channel& dataIn = GetChannel(fChanName, 0);
while (!NewStatePending()) {
auto msg(dataInChannel.Transport()->CreateMessage());
dataInChannel.Receive(msg);
// void* ptr = msg->GetData();
// char* cptr = static_cast<char*>(ptr);
// LOG(info) << "check: " << cptr[3];
fair::mq::Parts parts;
dataIn.Receive(parts);
if (fMaxIterations > 0 && ++fNumIterations >= fMaxIterations) {
LOG(info) << "Configured max number of iterations reached. Leaving RUNNING state.";
@@ -51,22 +48,22 @@ struct Sink : Device
void ResetTask() override
{
GetChannel("data", 0).Transport()->UnsubscribeFromRegionEvents();
GetChannel(fChanName, 0).Transport()->UnsubscribeFromRegionEvents();
}
private:
uint64_t fMaxIterations = 0;
uint64_t fNumIterations = 0;
std::string fChanName;
};
} // namespace
void addCustomOptions(bpo::options_description& options)
{
options.add_options()(
"max-iterations",
bpo::value<uint64_t>()->default_value(0),
"Maximum number of iterations of Run/ConditionalRun/OnData (0 - infinite)");
options.add_options()
("chan-name", bpo::value<std::string>()->default_value("data"), "name of the input channel")
("max-iterations", bpo::value<uint64_t>()->default_value(0), "Maximum number of iterations of Run/ConditionalRun/OnData (0 - infinite)");
}
unique_ptr<Device> getDevice(ProgOptions& /*config*/) { return make_unique<Sink>(); }

View File

@@ -1,5 +1,5 @@
################################################################################
# Copyright (C) 2012-2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# Copyright (C) 2012-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# #
# This software is distributed under the terms of the #
# GNU Lesser General Public Licence (LGPL) version 3, #
@@ -63,14 +63,21 @@ if(BUILD_FAIRMQ)
Tools.h
TransportFactory.h
Transports.h
TransportEnum.h
UnmanagedRegion.h
options/FairMQProgOptions.h
runDevice.h
runFairMQDevice.h
shmem/Common.h
shmem/Manager.h
shmem/Message.h
shmem/Monitor.h
shmem/Poller.h
shmem/Segment.h
shmem/Socket.h
shmem/TransportFactory.h
shmem/UnmanagedRegion.h
shmem/UnmanagedRegionImpl.h
tools/Compiler.h
tools/CppSTL.h
tools/Exceptions.h
@@ -95,12 +102,6 @@ if(BUILD_FAIRMQ)
plugins/Builtin.h
plugins/config/Config.h
plugins/control/Control.h
shmem/Message.h
shmem/Poller.h
shmem/UnmanagedRegionImpl.h
shmem/Socket.h
shmem/TransportFactory.h
shmem/Manager.h
zeromq/Common.h
zeromq/Context.h
zeromq/Message.h
@@ -118,6 +119,7 @@ if(BUILD_FAIRMQ)
Channel.cxx
Device.cxx
DeviceRunner.cxx
EventManager.cxx
JSONParser.cxx
MemoryResources.cxx
Plugin.cxx
@@ -174,6 +176,9 @@ if(BUILD_FAIRMQ)
FAIRMQ_HAS_STD_FILESYSTEM=${FAIRMQ_HAS_STD_FILESYSTEM}
FAIRMQ_HAS_STD_PMR=${FAIRMQ_HAS_STD_PMR}
)
if(FAIRMQ_BOOST_PROCESS_V1_HEADER)
target_compile_definitions(${target} PRIVATE FAIRMQ_BOOST_PROCESS_V1_HEADER)
endif()
if(DEFINED FAIRMQ_CHANNEL_DEFAULT_AUTOBIND)
# translate CMake boolean (TRUE, FALSE, 0, 1, OFF, ON) into C++ boolean literal (true, false)
if(FAIRMQ_CHANNEL_DEFAULT_AUTOBIND)

View File

@@ -12,6 +12,7 @@
#include <fairmq/Channel.h>
#include <fairmq/Properties.h>
#include <fairmq/Tools.h>
#include <fairmq/Transports.h>
#include <random>
#include <regex>
#include <set>
@@ -180,7 +181,8 @@ try {
// validate channel name
smatch m;
if (regex_search(fName, m, regex("[^a-zA-Z0-9\\-_\\[\\]#]"))) {
static regex const invalidName(R"([^a-zA-Z0-9\-_\[\]#])");
if (regex_search(fName, m, invalidName)) {
ss << "INVALID";
LOG(debug) << ss.str();
LOG(error) << "channel name contains illegal character: '" << m.str(0) << "', allowed characters are: a-z, A-Z, 0-9, -, _, [, ], #";
@@ -383,4 +385,10 @@ bool Channel::BindEndpoint(string& endpoint)
}
}
std::string Channel::GetTransportName() const { return TransportName(fTransportType); }
Transport Channel::GetTransportType() const { return fTransportType; }
void Channel::UpdateTransport(const std::string& transport) { fTransportType = TransportType(transport); Invalidate(); }
} // namespace fair::mq

View File

@@ -14,7 +14,7 @@
#include <fairmq/Properties.h>
#include <fairmq/Socket.h>
#include <fairmq/TransportFactory.h>
#include <fairmq/Transports.h>
#include <fairmq/TransportEnum.h>
#include <fairmq/UnmanagedRegion.h>
#include <cstdint> // int64_t
@@ -145,11 +145,11 @@ class Channel
/// Get channel transport name ("default", "zeromq" or "shmem")
/// @return Returns channel transport name (e.g. "default", "zeromq" or "shmem")
std::string GetTransportName() const { return TransportName(fTransportType); }
std::string GetTransportName() const;
/// Get channel transport type
/// @return Returns channel transport type
mq::Transport GetTransportType() const { return fTransportType; }
mq::Transport GetTransportType() const;
/// Get socket send buffer size (in number of messages)
/// @return Returns socket send buffer size (in number of messages)
@@ -221,7 +221,7 @@ class Channel
/// Set channel transport
/// @param transport transport string ("default", "zeromq" or "shmem")
void UpdateTransport(const std::string& transport) { fTransportType = TransportType(transport); Invalidate(); }
void UpdateTransport(const std::string& transport);
/// Set socket send buffer size
/// @param sndBufSize Socket send buffer size (in number of messages)
@@ -438,7 +438,7 @@ class Channel
}
void CheckSendCompatibility(Parts& parts) { CheckSendCompatibility(parts.fParts); }
void CheckSendCompatibility(std::vector<MessagePtr>& msgVec)
void CheckSendCompatibility(Parts::container & msgVec)
{
for (auto& msg : msgVec) {
if (fTransportType != msg->GetType()) {
@@ -468,7 +468,7 @@ class Channel
}
void CheckReceiveCompatibility(Parts& parts) { CheckReceiveCompatibility(parts.fParts); }
void CheckReceiveCompatibility(std::vector<MessagePtr>& msgVec)
void CheckReceiveCompatibility(Parts::container& msgVec)
{
for (auto& msg : msgVec) {
if (fTransportType != msg->GetType()) {

View File

@@ -9,6 +9,7 @@
// FairMQ
#include <fairmq/Device.h>
#include <fairmq/Tools.h>
#include <fairmq/Transports.h>
// boost
#include <boost/algorithm/string.hpp> // join/split
@@ -176,7 +177,6 @@ void Device::InitWrapper()
// Fill the uninitialized channel containers
for (auto& channel : GetChannels()) {
int subChannelIndex = 0;
for (auto& subChannel : channel.second) {
// set channel transport
LOG(debug) << "Initializing transport for channel " << subChannel.fName << ": " << TransportNames.at(subChannel.fTransportType);
@@ -208,8 +208,6 @@ void Device::InitWrapper()
LOG(error) << "Cannot update configuration. Socket method (bind/connect) for channel '" << subChannel.fName << "' not specified.";
throw runtime_error(tools::ToString("Cannot update configuration. Socket method (bind/connect) for channel ", subChannel.fName, " not specified."));
}
subChannelIndex++;
}
}
@@ -515,7 +513,8 @@ void Device::HandleMultipleTransportInput()
fMultitransportProceed = true;
for (const auto& i : fMultitransportInputs) {
threads.emplace_back(thread(&Device::PollForTransport, this, fTransports.at(i.first).get(), i.second));
threads.emplace_back(
&Device::PollForTransport, this, fTransports.at(i.first).get(), i.second);
}
for (thread& t : threads) {

View File

@@ -19,7 +19,7 @@
#include <fairmq/StateQueue.h>
#include <fairmq/Tools.h>
#include <fairmq/TransportFactory.h>
#include <fairmq/Transports.h>
#include <fairmq/TransportEnum.h>
#include <fairmq/UnmanagedRegion.h>
// logger

View File

@@ -66,10 +66,13 @@ bool DeviceRunner::HandleGeneralOptions(const fair::mq::ProgOptions& config, boo
if (printLogo) {
LOG(info) << endl
<< " ______ _ _______ _________ " << endl
<< " / ____/___ _(_)_______ |/ /_ __ \\ version " << FAIRMQ_GIT_VERSION << endl
<< " / /_ / __ `/ / ___/__ /|_/ /_ / / / build " << FAIRMQ_BUILD_TYPE << endl
<< " / ____/___ _(_)_______ |/ /_ __ \\ version "
<< FAIRMQ_GIT_VERSION << endl
<< " / /_ / __ `/ / ___/__ /|_/ /_ / / / build " << FAIRMQ_BUILD_TYPE
<< endl
<< " / __/ / /_/ / / / _ / / / / /_/ / " << FAIRMQ_REPO_URL << endl
<< " /_/ \\__,_/_/_/ /_/ /_/ \\___\\_\\ " << FAIRMQ_LICENSE << " © " << FAIRMQ_COPYRIGHT << endl;
<< R"( /_/ \__,_/_/_/ /_/ /_/ \___\_\ )" << FAIRMQ_LICENSE
<< " © " << FAIRMQ_COPYRIGHT << endl;
}
}

20
fairmq/EventManager.cxx Normal file
View File

@@ -0,0 +1,20 @@
/********************************************************************************
* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" *
********************************************************************************/
#include "EventManager.h"
#include <string>
#include <typeindex>
template std::shared_ptr<
fair::mq::EventManager::Signal<fair::mq::PropertyChangeAsString, std::string>>
fair::mq::EventManager::GetSignal<fair::mq::PropertyChangeAsString, std::string>(
const std::pair<std::type_index, std::type_index>& key) const;
template void fair::mq::EventManager::Subscribe<fair::mq::PropertyChangeAsString, std::string>(
const std::string& subscriber,
std::function<void(typename fair::mq::PropertyChangeAsString::KeyType, std::string)>);

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2014-2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2014-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
@@ -57,27 +57,8 @@ class EventManager
template<typename E, typename ...Args>
using Signal = boost::signals2::signal<void(typename E::KeyType, Args...)>;
template<typename E, typename ...Args>
auto Subscribe(const std::string& subscriber, std::function<void(typename E::KeyType, Args...)> callback) -> void
{
const std::type_index event_type_index{typeid(E)};
const std::type_index callback_type_index{typeid(std::function<void(typename E::KeyType, Args...)>)};
const auto signalsKey = std::make_pair(event_type_index, callback_type_index);
const auto connectionsKey = std::make_pair(subscriber, signalsKey);
const auto connection = GetSignal<E, Args...>(signalsKey)->connect(callback);
{
std::lock_guard<std::mutex> lock{fMutex};
if (fConnections.find(connectionsKey) != fConnections.end())
{
fConnections.at(connectionsKey).disconnect();
fConnections.erase(connectionsKey);
}
fConnections.insert({connectionsKey, connection});
}
}
template<typename E, typename... Args>
auto Subscribe(const std::string& subscriber, std::function<void(typename E::KeyType, Args...)> callback) -> void;
template<typename E, typename ...Args>
auto Unsubscribe(const std::string& subscriber) -> void
@@ -119,12 +100,17 @@ class EventManager
mutable std::mutex fMutex;
template<typename E, typename ...Args>
auto GetSignal(const SignalsKey& key) const -> std::shared_ptr<Signal<E, Args...>>
{
auto GetSignal(const SignalsKey& key) const -> std::shared_ptr<Signal<E, Args...>>;
}; /* class EventManager */
struct PropertyChangeAsString : Event<std::string> {};
template<typename E, typename... Args>
auto EventManager::GetSignal(const SignalsKey& key) const -> std::shared_ptr<Signal<E, Args...>>
{
std::lock_guard<std::mutex> lock{fMutex};
if (fSignals.find(key) == fSignals.end())
{
if (fSignals.find(key) == fSignals.end()) {
// wrapper is needed because boost::signals2::signal is neither copyable nor movable
// and I don't know how else to insert it into the map
auto signal = std::make_shared<Signal<E, Args...>>();
@@ -132,8 +118,40 @@ class EventManager
}
return boost::any_cast<std::shared_ptr<Signal<E, Args...>>>(fSignals.at(key));
}
template<typename E, typename... Args>
auto EventManager::Subscribe(const std::string& subscriber,
std::function<void(typename E::KeyType, Args...)> callback) -> void
{
const std::type_index event_type_index{typeid(E)};
const std::type_index callback_type_index{
typeid(std::function<void(typename E::KeyType, Args...)>)};
const auto signalsKey = std::make_pair(event_type_index, callback_type_index);
const auto connectionsKey = std::make_pair(subscriber, signalsKey);
const auto connection = GetSignal<E, Args...>(signalsKey)->connect(callback);
{
std::lock_guard<std::mutex> lock{fMutex};
if (fConnections.find(connectionsKey) != fConnections.end()) {
fConnections.at(connectionsKey).disconnect();
fConnections.erase(connectionsKey);
}
}; /* class EventManager */
fConnections.insert({connectionsKey, connection});
}
}
extern template std::shared_ptr<
fair::mq::EventManager::Signal<fair::mq::PropertyChangeAsString, std::string>>
fair::mq::EventManager::GetSignal<fair::mq::PropertyChangeAsString, std::string>(
const std::pair<std::type_index, std::type_index>& key) const;
extern template void
fair::mq::EventManager::Subscribe<fair::mq::PropertyChangeAsString, std::string>(
const std::string& subscriber,
std::function<void(typename fair::mq::PropertyChangeAsString::KeyType, std::string)>);
} // namespace fair::mq

View File

@@ -17,7 +17,7 @@
#include <boost/container/container_fwd.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/pmr/memory_resource.hpp>
#include <memory_resource>
#include <cstring>
#include <fairmq/Message.h>
#include <stdexcept>
@@ -27,7 +27,7 @@ namespace fair::mq {
class TransportFactory;
using byte = unsigned char;
namespace pmr = boost::container::pmr;
namespace pmr = std::pmr;
/// All FairMQ related memory resources need to inherit from this interface
/// class for the

View File

@@ -10,7 +10,7 @@
#define FAIR_MQ_MESSAGE_H
#include <cstddef> // for size_t
#include <fairmq/Transports.h>
#include <fairmq/TransportEnum.h>
#include <memory> // unique_ptr
#include <stdexcept>
@@ -46,7 +46,7 @@ struct Message
virtual void* GetData() const = 0;
virtual size_t GetSize() const = 0;
virtual bool SetUsedSize(size_t size) = 0;
virtual bool SetUsedSize(size_t size, Alignment alignment = Alignment{0}) = 0;
virtual Transport GetType() const = 0;
TransportFactory* GetTransport() { return fTransport; }
@@ -76,6 +76,11 @@ struct MessageBadAlloc : std::runtime_error
using std::runtime_error::runtime_error;
};
struct RefCountBadAlloc : std::runtime_error
{
using std::runtime_error::runtime_error;
};
} // namespace fair::mq
using fairmq_free_fn [[deprecated("Use fair::mq::FreeFn")]] = fair::mq::FreeFn;

View File

@@ -87,7 +87,7 @@ struct Parts
const_iterator end() const noexcept { return fParts.end(); }
const_iterator cend() const noexcept { return fParts.cend(); }
container fParts{};
container fParts;
};
} // namespace fair::mq

View File

@@ -32,12 +32,9 @@ const std::string fair::mq::PluginManager::fgkLibPrefixAlt = "FairMQPlugin_";
std::vector<boost::dll::shared_library> fair::mq::PluginManager::fgDLLKeepAlive =
std::vector<boost::dll::shared_library>();
fair::mq::PluginManager::PluginManager()
: fPluginServices()
{}
fair::mq::PluginManager::PluginManager() {}
fair::mq::PluginManager::PluginManager(const vector<string>& args)
: fPluginServices()
{
// Parse command line options
auto options = ProgramOptions();

View File

@@ -17,7 +17,9 @@ auto PluginServices::ChangeDeviceState(const string& controller, const DeviceSta
{
lock_guard<mutex> lock{fDeviceControllerMutex};
if (!fDeviceController) fDeviceController = controller;
if (!fDeviceController) {
fDeviceController = controller;
}
if (fDeviceController == controller) {
return fDevice.ChangeState(next);

View File

@@ -189,7 +189,7 @@ vector<string> ProgOptions::GetPropertyKeys() const
vector<string> keys;
for (const auto& it : fVarMap) {
keys.push_back(it.first.c_str());
keys.emplace_back(it.first.c_str());
}
return keys;
@@ -448,3 +448,6 @@ void ProgOptions::PrintOptionsRaw() const
}
} // namespace fair::mq
template void fair::mq::ProgOptions::SetProperty<std::string>(const std::string& key, std::string val);
template void fair::mq::ProgOptions::SetProperty<int>(const std::string& key, int val);

View File

@@ -129,17 +129,7 @@ class ProgOptions
/// @param key
/// @param val
template<typename T>
void SetProperty(const std::string& key, T val)
{
std::unique_lock<std::mutex> lock(fMtx);
SetVarMapValue<typename std::decay<T>::type>(key, val);
lock.unlock();
fEvents.Emit<fair::mq::PropertyChange, typename std::decay<T>::type>(key, val);
fEvents.Emit<fair::mq::PropertyChangeAsString, std::string>(key, GetPropertyAsString(key));
}
void SetProperty(const std::string& key, T val);
/// @brief Updates an existing config property (or fails if it doesn't exist)
/// @param key
@@ -275,5 +265,20 @@ class ProgOptions
};
} // namespace fair::mq
template <typename T>
void fair::mq::ProgOptions::SetProperty(const std::string& key, T val)
{
std::unique_lock<std::mutex> lock(fMtx);
SetVarMapValue<typename std::decay<T>::type>(key, val);
lock.unlock();
fEvents.Emit<fair::mq::PropertyChange, typename std::decay<T>::type>(key, val);
fEvents.Emit<fair::mq::PropertyChangeAsString, std::string>(key, GetPropertyAsString(key));
}
extern template void fair::mq::ProgOptions::SetProperty<int>(const std::string& key, int val);
extern template void fair::mq::ProgOptions::SetProperty<std::string>(const std::string& key, std::string val);
#endif /* FAIR_MQ_PROGOPTIONS_H */

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2014-2018 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2014-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
@@ -29,7 +29,6 @@ using Property = boost::any;
using Properties = std::map<std::string, Property>;
struct PropertyChange : Event<std::string> {};
struct PropertyChangeAsString : Event<std::string> {};
class PropertyHelper
{

View File

@@ -52,8 +52,8 @@ struct Socket
virtual int64_t Send(MessagePtr& msg, int timeout = -1) = 0;
virtual int64_t Receive(MessagePtr& msg, int timeout = -1) = 0;
virtual int64_t Send(std::vector<std::unique_ptr<Message>>& msgVec, int timeout = -1) = 0;
virtual int64_t Receive(std::vector<std::unique_ptr<Message>>& msgVec, int timeout = -1) = 0;
virtual int64_t Send(Parts::container& msgVec, int timeout = -1) = 0;
virtual int64_t Receive(Parts::container & msgVec, int timeout = -1) = 0;
virtual int64_t Send(Parts& parts, int timeout = -1) { return Send(parts.fParts, timeout); }
virtual int64_t Receive(Parts& parts, int timeout = -1) { return Receive(parts.fParts, timeout); }

View File

@@ -177,6 +177,7 @@ struct Machine_ : public state_machine_def<Machine_>
atomic<bool> fLastTransitionResult;
mutex fStateMtx;
mutex fSubscriptionsMtx;
atomic<bool> fNewStatePending;
condition_variable fNewStatePendingCV;
@@ -312,12 +313,15 @@ void StateMachine::SubscribeToStateChange(const string& key, function<void(const
{
// Check if the key has a integer value as prefix, if yes, decode it.
int i = strtol(key.c_str(), nullptr, 10);
static_pointer_cast<FairMQFSM>(fFsm)->fStateChangeSignalsMap.insert({key, static_pointer_cast<FairMQFSM>(fFsm)->fStateChangeSignal.connect(i, callback)});
auto fsm = static_pointer_cast<FairMQFSM>(fFsm);
lock_guard<mutex> lock(fsm->fSubscriptionsMtx);
fsm->fStateChangeSignalsMap.insert({key, fsm->fStateChangeSignal.connect(i, callback)});
}
void StateMachine::UnsubscribeFromStateChange(const string& key)
{
auto fsm = static_pointer_cast<FairMQFSM>(fFsm);
lock_guard<mutex> lock(fsm->fSubscriptionsMtx);
if (fsm->fStateChangeSignalsMap.count(key)) {
fsm->fStateChangeSignalsMap.at(key).disconnect();
fsm->fStateChangeSignalsMap.erase(key);
@@ -357,12 +361,15 @@ void StateMachine::StopHandlingStates()
void StateMachine::SubscribeToNewTransition(const string& key, function<void(const Transition)> callback)
{
static_pointer_cast<FairMQFSM>(fFsm)->fNewTransitionSignalsMap.insert({key, static_pointer_cast<FairMQFSM>(fFsm)->fNewTransitionSignal.connect(callback)});
auto fsm = static_pointer_cast<FairMQFSM>(fFsm);
lock_guard<mutex> lock(fsm->fSubscriptionsMtx);
fsm->fNewTransitionSignalsMap.insert({key, fsm->fNewTransitionSignal.connect(callback)});
}
void StateMachine::UnsubscribeFromNewTransition(const string& key)
{
auto fsm = static_pointer_cast<FairMQFSM>(fFsm);
lock_guard<mutex> lock(fsm->fSubscriptionsMtx);
if (fsm->fNewTransitionSignalsMap.count(key)) {
fsm->fNewTransitionSignalsMap.at(key).disconnect();
fsm->fNewTransitionSignalsMap.erase(key);

View File

@@ -79,7 +79,7 @@ Properties SuboptParser(const vector<string>& channelConfig, const string& devic
ptree channelsArray;
for (auto token : channelConfig) {
for (const auto& token : channelConfig) {
string channelName;
ptree channelProperties;

22
fairmq/TransportEnum.h Normal file
View File

@@ -0,0 +1,22 @@
/********************************************************************************
* Copyright (C) 2014-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" *
********************************************************************************/
#ifndef FAIR_MQ_TRANSPORTENUMS_H
#define FAIR_MQ_TRANSPORTENUMS_H
namespace fair::mq {
enum class Transport {
DEFAULT,
ZMQ,
SHM
};
}
#endif // FAIR_MQ_TRANSPORTENUMS_H

View File

@@ -14,7 +14,7 @@
#include <fairmq/Message.h>
#include <fairmq/Poller.h>
#include <fairmq/Socket.h>
#include <fairmq/Transports.h>
#include <fairmq/TransportEnum.h>
#include <fairmq/UnmanagedRegion.h>
#include <memory> // shared_ptr
#include <stdexcept>

View File

@@ -10,6 +10,7 @@
#define FAIR_MQ_TRANSPORTS_H
#include <fairmq/tools/Strings.h>
#include <fairmq/TransportEnum.h>
#include <memory>
#include <ostream>
#include <stdexcept>
@@ -18,13 +19,6 @@
namespace fair::mq {
enum class Transport
{
DEFAULT,
ZMQ,
SHM
};
struct TransportError : std::runtime_error
{
using std::runtime_error::runtime_error;

View File

@@ -9,7 +9,7 @@
#ifndef FAIR_MQ_UNMANAGEDREGION_H
#define FAIR_MQ_UNMANAGEDREGION_H
#include <fairmq/Transports.h>
#include <fairmq/TransportEnum.h>
#include <cstddef> // size_t
#include <cstdint> // uint32_t
@@ -134,7 +134,8 @@ struct RegionConfig
int creationFlags = 0; /// flags passed to the underlying transport on region creation
int64_t userFlags = 0; /// custom flags that have no effect on the transport, but can be retrieved from the region by the user
uint64_t size = 0; /// region size
std::string path = ""; /// file path, if the region is backed by a file
uint64_t rcSegmentSize = 100000000; /// size of the segment that stores reference counts when "soft"-copying the messages
std::string path; /// file path, if the region is backed by a file
std::optional<uint16_t> id = std::nullopt; /// region id
uint32_t linger = 100; /// delay in ms before region destruction to collect outstanding events
};

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2018-2022 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2018-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
@@ -19,7 +19,7 @@
#define FAIRMQ_GIT_DATE "@PROJECT_GIT_DATE@"
#define FAIRMQ_REPO_URL "https://github.com/FairRootGroup/FairMQ"
#define FAIRMQ_LICENSE "LGPL-3.0"
#define FAIRMQ_COPYRIGHT "2012-2023 GSI"
#define FAIRMQ_COPYRIGHT "2012-2025 GSI"
#define FAIRMQ_BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#endif // FAIR_MQ_VERSION_H

View File

@@ -65,13 +65,17 @@ Control::Control(const string& name, Plugin::Version version, const string& main
});
try {
TakeDeviceControl();
auto control = GetProperty<string>("control");
if (control != "none") {
TakeDeviceControl();
}
if (control == "static") {
LOG(debug) << "Running builtin controller: static";
fControllerThread = thread(&Control::StaticMode, this);
} else if (control == "none") {
LOG(debug) << "Builtin controller: disabled";
} else if (control == "gui") {
LOG(debug) << "Running builtin controller: gui";
fControllerThread = thread(&Control::GUIMode, this);
@@ -142,7 +146,7 @@ auto ControlPluginProgramOptions() -> Plugin::ProgOptions
namespace po = boost::program_options;
auto pluginOptions = po::options_description{"Control (builtin) Plugin"};
pluginOptions.add_options()
("control", po::value<string>()->default_value("dynamic"), "Control mode, 'static' or 'dynamic' (aliases for dynamic are external and interactive)")
("control", po::value<string>()->default_value("dynamic"), "Control mode, 'static' or 'dynamic' (aliases for dynamic are external and interactive), 'none', 'gui'")
("catch-signals", po::value<int >()->default_value(1), "Enable signal handling (1/0).");
return pluginOptions;
}
@@ -271,11 +275,11 @@ auto Control::InteractiveMode() -> void
try {
RunStartupSequence();
if(!fDeviceShutdownRequested) {
if (!fDeviceShutdownRequested) {
RunREPL();
}
if(!fDeviceShutdownRequested) {
if (!fDeviceShutdownRequested) {
RunShutdownSequence();
}
} catch (PluginServices::DeviceControlError& e) {
@@ -404,7 +408,7 @@ try {
// or for device shutdown request (Ctrl-C)
fStateQueue.WaitForNextOrCustom([this]{ return fDeviceShutdownRequested.load(); });
if(!fDeviceShutdownRequested) {
if (!fDeviceShutdownRequested) {
RunShutdownSequence();
}
} catch (PluginServices::DeviceControlError& e) {
@@ -421,7 +425,7 @@ try {
// Wait for device shutdown request (Ctrl-C)
fStateQueue.WaitForCustom([this]{ return fDeviceShutdownRequested.load(); });
if(!fDeviceShutdownRequested) {
if (!fDeviceShutdownRequested) {
RunShutdownSequence();
}
} catch (PluginServices::DeviceControlError& e) {

View File

@@ -13,6 +13,8 @@
#include <functional> // std::equal_to
#include <boost/functional/hash.hpp>
// #include <boost/interprocess/allocators/adaptive_pool.hpp>
#include <boost/interprocess/allocators/node_allocator.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/containers/string.hpp>
@@ -21,10 +23,12 @@
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/mem_algo/simple_seq_fit.hpp>
#include <boost/unordered_map.hpp>
#include <boost/variant.hpp>
#include <variant>
#include <sys/types.h>
#include <fairmq/tools/Strings.h>
namespace fair::mq::shmem
{
@@ -41,6 +45,36 @@ using RBTreeBestFitSegment = boost::interprocess::basic_managed_shared_memory<ch
boost::interprocess::null_index>;
// boost::interprocess::iset_index>;
inline std::string MakeShmName(const std::string& shmId, const std::string& type) {
return std::string("fmq_" + shmId + "_" + type);
}
inline std::string MakeShmName(const std::string& shmId, const std::string& type, int index) {
return std::string(MakeShmName(shmId, type) + "_" + std::to_string(index));
}
struct RefCount
{
explicit RefCount(uint16_t c)
: count(c)
{}
uint16_t Get() { return count.load(); }
uint16_t Increment() { return count.fetch_add(1); }
uint16_t Decrement() { return count.fetch_sub(1); }
std::atomic<uint16_t> count;
};
// Number of nodes allocated at once when the allocator runs out of nodes.
static constexpr size_t numNodesPerBlock = 4096;
// Maximum number of totally free blocks that the adaptive node pool will hold.
// The rest of the totally free blocks will be deallocated with the segment manager.
// static constexpr size_t maxFreeBlocks = 2;
using RefCountPool = boost::interprocess::node_allocator<RefCount, boost::interprocess::managed_shared_memory::segment_manager, numNodesPerBlock>;
// using RefCountPool = boost::interprocess::adaptive_pool<RefCount, boost::interprocess::managed_shared_memory::segment_manager, numNodesPerBlock, maxFreeBlocks>;
using SegmentManager = boost::interprocess::managed_shared_memory::segment_manager;
using VoidAlloc = boost::interprocess::allocator<void, SegmentManager>;
using CharAlloc = boost::interprocess::allocator<char, SegmentManager>;
@@ -48,6 +82,90 @@ using Str = boost::interprocess::basic_string<char, std::char_traits<
using StrAlloc = boost::interprocess::allocator<Str, SegmentManager>;
using StrVector = boost::interprocess::vector<Str, StrAlloc>;
// ShmHeader stores user buffer alignment and the reference count in the following structure:
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// The alignment of Hdr depends on the alignment of std::atomic and is stored in the first entry
struct ShmHeader
{
struct Hdr
{
uint16_t userOffset;
std::atomic<uint16_t> refCount;
};
static Hdr* HdrPtr(char* ptr)
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// ^
return reinterpret_cast<Hdr*>(ptr + sizeof(uint16_t) + *(reinterpret_cast<uint16_t*>(ptr)));
}
static uint16_t HdrPartSize() // [HdrOffset(uint16_t)][Hdr alignment][Hdr]
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// <--------------------------------------->
return sizeof(uint16_t) + alignof(Hdr) + sizeof(Hdr);
}
static std::atomic<uint16_t>& RefCountPtr(char* ptr)
{
// get the ref count ptr from the Hdr
return HdrPtr(ptr)->refCount;
}
static uint16_t UserOffset(char* ptr)
{
return HdrPartSize() + HdrPtr(ptr)->userOffset;
}
static char* UserPtr(char* ptr)
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// ^
return ptr + HdrPartSize() + HdrPtr(ptr)->userOffset;
}
static uint16_t RefCount(char* ptr) { return RefCountPtr(ptr).load(); }
static uint16_t IncrementRefCount(char* ptr) { return RefCountPtr(ptr).fetch_add(1); }
static uint16_t DecrementRefCount(char* ptr) { return RefCountPtr(ptr).fetch_sub(1); }
static size_t FullSize(size_t size, size_t alignment)
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// <--------------------------------------------------------------------------->
return HdrPartSize() + alignment + size;
}
static void Construct(char* ptr, size_t alignment)
{
// place the Hdr in the aligned location, fill it and store its offset to HdrOffset
// the address alignment should be at least 2
assert(reinterpret_cast<uintptr_t>(ptr) % 2 == 0);
// offset to the beginning of the Hdr. store it in the beginning
uint16_t hdrOffset = alignof(Hdr) - ((reinterpret_cast<uintptr_t>(ptr) + sizeof(uint16_t)) % alignof(Hdr));
memcpy(ptr, &hdrOffset, sizeof(hdrOffset));
// offset to the beginning of the user buffer, store in Hdr together with the ref count
uint16_t userOffset = alignment - ((reinterpret_cast<uintptr_t>(ptr) + HdrPartSize()) % alignment);
new(ptr + sizeof(uint16_t) + hdrOffset) Hdr{ userOffset, std::atomic<uint16_t>(1) };
}
static void Destruct(char* ptr) { RefCountPtr(ptr).~atomic(); }
};
struct MetaHeader
{
size_t fSize; // size of the shm buffer
size_t fHint; // user-defined value, given by the user on message creation and returned to the user on "buffer no longer needed"-callbacks
boost::interprocess::managed_shared_memory::handle_t fHandle; // handle to shm buffer, convertible to shm buffer ptr
mutable boost::interprocess::managed_shared_memory::handle_t fShared; // handle to the buffer storing the ref count for shared buffers
uint16_t fRegionId; // id of the unmanaged region
mutable uint16_t fSegmentId; // id of the managed segment
bool fManaged; // true = managed segment, false = unmanaged region
};
enum class AllocationAlgorithm : int
{
rbtree_best_fit,
@@ -56,27 +174,20 @@ enum class AllocationAlgorithm : int
struct RegionInfo
{
RegionInfo(const VoidAlloc& alloc)
: fPath("", alloc)
, fCreationFlags(0)
, fUserFlags(0)
, fSize(0)
, fDestroyed(false)
{}
RegionInfo(const char* path, int flags, uint64_t userFlags, uint64_t size, const VoidAlloc& alloc)
RegionInfo(const char* path, int flags, uint64_t userFlags, uint64_t size, uint64_t rcSegmentSize, const VoidAlloc& alloc)
: fPath(path, alloc)
, fCreationFlags(flags)
, fUserFlags(userFlags)
, fSize(size)
, fDestroyed(false)
, fRCSegmentSize(rcSegmentSize)
{}
Str fPath;
int fCreationFlags;
uint64_t fUserFlags;
uint64_t fSize;
bool fDestroyed;
uint64_t fRCSegmentSize;
bool fDestroyed{false};
};
using Uint16RegionInfoPairAlloc = boost::interprocess::allocator<std::pair<const uint16_t, RegionInfo>, SegmentManager>;
@@ -143,17 +254,6 @@ struct RegionCounter
std::atomic<uint16_t> fCount;
};
struct MetaHeader
{
size_t fSize;
size_t fHint;
boost::interprocess::managed_shared_memory::handle_t fHandle;
mutable boost::interprocess::managed_shared_memory::handle_t fShared;
uint16_t fRegionId;
mutable uint16_t fSegmentId;
bool fManaged;
};
#ifdef FAIRMQ_DEBUG_MODE
struct MsgCounter
{
@@ -219,73 +319,7 @@ std::string makeShmIdStr(const std::string& sessionId);
std::string makeShmIdStr(uint64_t val);
uint64_t makeShmIdUint64(const std::string& sessionId);
struct SegmentSize : public boost::static_visitor<size_t>
{
template<typename S>
size_t operator()(S& s) const { return s.get_size(); }
};
struct SegmentAddress : public boost::static_visitor<void*>
{
template<typename S>
void* operator()(S& s) const { return s.get_address(); }
};
struct SegmentMemoryZeroer : public boost::static_visitor<>
{
template<typename S>
void operator()(S& s) const { s.zero_free_memory(); }
};
struct SegmentFreeMemory : public boost::static_visitor<size_t>
{
template<typename S>
size_t operator()(S& s) const { return s.get_free_memory(); }
};
struct SegmentHandleFromAddress : public boost::static_visitor<boost::interprocess::managed_shared_memory::handle_t>
{
SegmentHandleFromAddress(const void* _ptr) : ptr(_ptr) {}
template<typename S>
boost::interprocess::managed_shared_memory::handle_t operator()(S& s) const { return s.get_handle_from_address(ptr); }
const void* ptr;
};
struct SegmentAddressFromHandle : public boost::static_visitor<char*>
{
SegmentAddressFromHandle(const boost::interprocess::managed_shared_memory::handle_t _handle) : handle(_handle) {}
template<typename S>
char* operator()(S& s) const { return reinterpret_cast<char*>(s.get_address_from_handle(handle)); }
const boost::interprocess::managed_shared_memory::handle_t handle;
};
struct SegmentAllocate : public boost::static_visitor<char*>
{
SegmentAllocate(const size_t _size) : size(_size) {}
template<typename S>
char* operator()(S& s) const { return reinterpret_cast<char*>(s.allocate(size)); }
const size_t size;
};
struct SegmentAllocateAligned : public boost::static_visitor<void*>
{
SegmentAllocateAligned(const size_t _size, const size_t _alignment) : size(_size), alignment(_alignment) {}
template<typename S>
void* operator()(S& s) const { return s.allocate_aligned(size, alignment); }
const size_t size;
const size_t alignment;
};
struct SegmentBufferShrink : public boost::static_visitor<char*>
struct SegmentBufferShrink
{
SegmentBufferShrink(const size_t _new_size, char* _local_ptr)
: new_size(_new_size)
@@ -303,16 +337,15 @@ struct SegmentBufferShrink : public boost::static_visitor<char*>
mutable char* local_ptr;
};
struct SegmentDeallocate : public boost::static_visitor<>
{
SegmentDeallocate(char* _ptr) : ptr(_ptr) {}
} // namespace fair::mq::shmem
template<typename S>
void operator()(S& s) const { return s.deallocate(ptr); }
char* ptr;
};
namespace fair::mq { class TransportFactory; }
namespace fair::mq::shmem {
// Resolve a MetaHeader (received over a side channel) to the local data pointer.
// The caller is responsible for ensuring the backing buffer remains alive for the
// duration of access; FairMQ provides no refcount protection for this path.
char* GetDataAddressFromHandle(fair::mq::TransportFactory& factory, const MetaHeader& meta);
} // namespace fair::mq::shmem
#endif /* FAIR_MQ_SHMEM_COMMON_H_ */

View File

@@ -7,11 +7,18 @@
********************************************************************************/
#include "Manager.h"
#include "TransportFactory.h"
// Needed to compile-firewall the <boost/process/async.hpp> header because it
// interferes with the <asio/buffer.hpp> header. So, let's factor
// the whole dependency to Boost.Process out of the header.
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
#include <boost/process/v1.hpp>
namespace bp = boost::process::v1;
#else
#include <boost/process.hpp>
namespace bp = boost::process;
#endif
#include <fairlogger/Logger.h>
namespace fair::mq::shmem {
@@ -28,7 +35,7 @@ bool Manager::SpawnShmMonitor(const std::string& id)
path.emplace(path.begin(), env.at(fairmq_path_key).to_string());
}
auto exe(boost::process::search_path(shmmonitor_exe_name, path));
auto exe(bp::search_path(shmmonitor_exe_name, path));
if (exe.empty()) {
LOG(warn) << "could not find " << shmmonitor_exe_name << " in \"$" << fairmq_path_key
<< ":$PATH\"";
@@ -39,10 +46,18 @@ bool Manager::SpawnShmMonitor(const std::string& id)
bool verbose(env.count(shmmonitor_verbose_key)
&& env.at(shmmonitor_verbose_key).to_string() == "true");
boost::process::spawn(
bp::spawn(
exe, "-x", "-m", "--shmid", id, "-d", "-t", "2000", (verbose ? "--verbose" : ""), env);
return true;
}
char* GetDataAddressFromHandle(fair::mq::TransportFactory& factory, const MetaHeader& meta)
{
if (factory.GetType() != fair::mq::Transport::SHM) {
throw SharedMemoryError("GetDataAddressFromHandle called on a non-shmem transport");
}
return static_cast<TransportFactory&>(factory).GetDataAddressFromHandle(meta);
}
} // namespace fair::mq::shmem

View File

@@ -24,7 +24,6 @@
#include <boost/interprocess/sync/interprocess_condition.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/sync/named_mutex.hpp>
#include <boost/variant.hpp>
#include <algorithm> // max
#include <chrono>
@@ -42,6 +41,7 @@
#include <tuple>
#include <unordered_map>
#include <utility> // pair
#include <variant>
#include <vector>
#include <unistd.h> // getuid
@@ -51,79 +51,6 @@
namespace fair::mq::shmem
{
// ShmHeader stores user buffer alignment and the reference count in the following structure:
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// The alignment of Hdr depends on the alignment of std::atomic and is stored in the first entry
struct ShmHeader
{
struct Hdr
{
uint16_t userOffset;
std::atomic<uint16_t> refCount;
};
static Hdr* HdrPtr(char* ptr)
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// ^
return reinterpret_cast<Hdr*>(ptr + sizeof(uint16_t) + *(reinterpret_cast<uint16_t*>(ptr)));
}
static uint16_t HdrPartSize() // [HdrOffset(uint16_t)][Hdr alignment][Hdr]
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// <--------------------------------------->
return sizeof(uint16_t) + alignof(Hdr) + sizeof(Hdr);
}
static std::atomic<uint16_t>& RefCountPtr(char* ptr)
{
// get the ref count ptr from the Hdr
return HdrPtr(ptr)->refCount;
}
static uint16_t UserOffset(char* ptr)
{
return HdrPartSize() + HdrPtr(ptr)->userOffset;
}
static char* UserPtr(char* ptr)
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// ^
return ptr + HdrPartSize() + HdrPtr(ptr)->userOffset;
}
static uint16_t RefCount(char* ptr) { return RefCountPtr(ptr).load(); }
static uint16_t IncrementRefCount(char* ptr) { return RefCountPtr(ptr).fetch_add(1); }
static uint16_t DecrementRefCount(char* ptr) { return RefCountPtr(ptr).fetch_sub(1); }
static size_t FullSize(size_t size, size_t alignment)
{
// [HdrOffset(uint16_t)][Hdr alignment][Hdr][user buffer alignment][user buffer]
// <--------------------------------------------------------------------------->
return HdrPartSize() + alignment + size;
}
static void Construct(char* ptr, size_t alignment)
{
// place the Hdr in the aligned location, fill it and store its offset to HdrOffset
// the address alignment should be at least 2
assert(reinterpret_cast<uintptr_t>(ptr) % 2 == 0);
// offset to the beginning of the Hdr. store it in the beginning
uint16_t hdrOffset = alignof(Hdr) - ((reinterpret_cast<uintptr_t>(ptr) + sizeof(uint16_t)) % alignof(Hdr));
memcpy(ptr, &hdrOffset, sizeof(hdrOffset));
// offset to the beginning of the user buffer, store in Hdr together with the ref count
uint16_t userOffset = alignment - ((reinterpret_cast<uintptr_t>(ptr) + HdrPartSize()) % alignment);
new(ptr + sizeof(uint16_t) + hdrOffset) Hdr{ userOffset, std::atomic<uint16_t>(1) };
}
static void Destruct(char* ptr) { RefCountPtr(ptr).~atomic(); }
};
class Manager
{
public:
@@ -131,7 +58,7 @@ class Manager
: fShmId64(config ? config->GetProperty<uint64_t>("shmid", makeShmIdUint64(sessionName)) : makeShmIdUint64(sessionName))
, fShmId(makeShmIdStr(fShmId64))
, fSegmentId(config ? config->GetProperty<uint16_t>("shm-segment-id", 0) : 0)
, fManagementSegment(boost::interprocess::open_or_create, std::string("fmq_" + fShmId + "_mng").c_str(), kManagementSegmentSize)
, fManagementSegment(boost::interprocess::open_or_create, MakeShmName(fShmId, "mng").c_str(), kManagementSegmentSize)
, fShmVoidAlloc(fManagementSegment.get_segment_manager())
, fShmMtx(fManagementSegment.find_or_construct<boost::interprocess::interprocess_mutex>(boost::interprocess::unique_instance)())
, fNumObservedEvents(0)
@@ -231,7 +158,7 @@ class Manager
bool createdSegment = false;
try {
std::string segmentName("fmq_" + fShmId + "_m_" + std::to_string(fSegmentId));
std::string segmentName = MakeShmName(fShmId, "m", fSegmentId);
auto it = fShmSegments->find(fSegmentId);
if (it == fShmSegments->end()) {
// no segment with given id exists, creating
@@ -266,8 +193,8 @@ class Manager
}
}
LOG(debug) << (createdSegment ? "Created" : "Opened") << " managed shared memory segment " << "fmq_" << fShmId << "_m_" << fSegmentId
<< ". Size: " << boost::apply_visitor(SegmentSize(), fSegments.at(fSegmentId)) << " bytes."
<< " Available: " << boost::apply_visitor(SegmentFreeMemory(), fSegments.at(fSegmentId)) << " bytes."
<< ". Size: " << std::visit([](auto& s) { return s.get_size(); }, fSegments.at(fSegmentId)) << " bytes."
<< " Available: " << std::visit([](auto& s) { return s.get_free_memory(); }, fSegments.at(fSegmentId)) << " bytes."
<< " Allocation algorithm: " << allocationAlgorithm;
} catch (interprocess_exception& bie) {
LOG(error) << "Failed to create/open shared memory segment '" << "fmq_" << fShmId << "_m_" << fSegmentId << "': " << bie.what();
@@ -305,14 +232,16 @@ class Manager
void ZeroSegment(uint16_t id)
{
LOG(debug) << "Zeroing the managed segment free memory...";
boost::apply_visitor(SegmentMemoryZeroer(), fSegments.at(id));
std::visit([](auto& s) { return s.zero_free_memory(); }, fSegments.at(id));
LOG(debug) << "Successfully zeroed the managed segment free memory.";
}
void MlockSegment(uint16_t id)
{
LOG(debug) << "Locking the managed segment memory pages...";
if (mlock(boost::apply_visitor(SegmentAddress(), fSegments.at(id)), boost::apply_visitor(SegmentSize(), fSegments.at(id))) == -1) {
if (mlock(
std::visit([](auto& s) { return s.get_address(); }, fSegments.at(id)),
std::visit([](auto& s) { return s.get_size(); }, fSegments.at(id))) == -1) {
LOG(error) << "Could not lock the managed segment memory. Code: " << errno << ", reason: " << strerror(errno);
throw TransportError(tools::ToString("Could not lock the managed segment memory: ", strerror(errno)));
}
@@ -327,7 +256,7 @@ class Manager
{
using namespace boost::interprocess;
try {
named_mutex monitorStatus(open_only, std::string("fmq_" + id + "_ms").c_str());
named_mutex monitorStatus(open_only, MakeShmName(id, "ms").c_str());
LOG(debug) << "Found fairmq-shmmonitor for shared memory id " << id;
} catch (interprocess_exception&) {
LOG(debug) << "no fairmq-shmmonitor found for shared memory id " << id << ", starting...";
@@ -336,7 +265,7 @@ class Manager
int numTries = 0;
do {
try {
named_mutex monitorStatus(open_only, std::string("fmq_" + id + "_ms").c_str());
named_mutex monitorStatus(open_only, MakeShmName(id, "ms").c_str());
LOG(debug) << "Started fairmq-shmmonitor for shared memory id " << id;
break;
} catch (interprocess_exception&) {
@@ -410,6 +339,12 @@ class Manager
LOG(debug) << "Unmanaged region (view) already present, promoting to controller";
region->BecomeController(cfg);
} else {
// we need to update local config, if the region information already exists
auto info = fShmRegions->find(id);
if (info != fShmRegions->end()) {
cfg.rcSegmentSize = info->second.fRCSegmentSize;
}
auto res = fRegions.emplace(id, std::make_unique<UnmanagedRegion>(fShmId, size, true, cfg));
region = res.first->second.get();
}
@@ -455,8 +390,10 @@ class Manager
}
auto* lRegion = GetRegion(id);
fTlRegionCache.fRegionsTLCache.emplace_back(std::make_tuple(lRegion, id, fShmId64));
if (lRegion) {
fTlRegionCache.fRegionsTLCache.emplace_back(lRegion, id, fShmId64);
fTlRegionCache.fRegionsTLCacheGen = fRegionsGen;
}
return lRegion;
}
@@ -466,7 +403,8 @@ class Manager
auto it = fRegions.find(id);
if (it != fRegions.end()) {
return it->second.get();
} else {
}
try {
RegionConfig cfg;
// get region info
@@ -475,6 +413,7 @@ class Manager
RegionInfo regionInfo = fShmRegions->at(id);
cfg.id = id;
cfg.creationFlags = regionInfo.fCreationFlags;
cfg.rcSegmentSize = regionInfo.fRCSegmentSize;
cfg.path = regionInfo.fPath.c_str();
}
// LOG(debug) << "Located remote region with id '" << id << "', path: '" << cfg.path << "', flags: '" << cfg.creationFlags << "'";
@@ -492,7 +431,6 @@ class Manager
return nullptr;
}
}
}
void RemoveRegion(uint16_t id)
{
@@ -529,8 +467,8 @@ class Manager
info.managed = true;
info.id = segmentId;
info.event = RegionEvent::created;
info.ptr = boost::apply_visitor(SegmentAddress(), fSegments.at(segmentId));
info.size = boost::apply_visitor(SegmentSize(), fSegments.at(segmentId));
info.ptr = std::visit([](auto& s) { return s.get_address(); }, fSegments.at(segmentId));
info.size = std::visit([](auto& s) { return s.get_size(); }, fSegments.at(segmentId));
result.push_back(info);
} catch (const std::out_of_range& oor) {
LOG(error) << "could not find segment with id " << segmentId;
@@ -549,6 +487,7 @@ class Manager
cfg.id = info.id;
cfg.creationFlags = regionInfo.fCreationFlags;
cfg.path = regionInfo.fPath.c_str();
cfg.rcSegmentSize = regionInfo.fRCSegmentSize;
regionCfgs.emplace(info.id, cfg);
// fill the ptr+size info after shmLock is released, to avoid constructing local region under it
} else {
@@ -710,9 +649,9 @@ class Manager
using namespace boost::interprocess;
if (segmentInfo.fAllocationAlgorithm == AllocationAlgorithm::rbtree_best_fit) {
fSegments.emplace(id, RBTreeBestFitSegment(open_only, std::string("fmq_" + fShmId + "_m_" + std::to_string(id)).c_str()));
fSegments.emplace(id, RBTreeBestFitSegment(open_only, MakeShmName(fShmId, "m", id).c_str()));
} else {
fSegments.emplace(id, SimpleSeqFitSegment(open_only, std::string("fmq_" + fShmId + "_m_" + std::to_string(id)).c_str()));
fSegments.emplace(id, SimpleSeqFitSegment(open_only, MakeShmName(fShmId, "m", id).c_str()));
}
} catch (std::out_of_range& oor) {
LOG(error) << "Could not get segment with id '" << id << "': " << oor.what();
@@ -724,11 +663,11 @@ class Manager
boost::interprocess::managed_shared_memory::handle_t GetHandleFromAddress(const void* ptr, uint16_t segmentId) const
{
return boost::apply_visitor(SegmentHandleFromAddress(ptr), fSegments.at(segmentId));
return std::visit([ptr](auto& s) { return s.get_handle_from_address(ptr); }, fSegments.at(segmentId));
}
char* GetAddressFromHandle(const boost::interprocess::managed_shared_memory::handle_t handle, uint16_t segmentId) const
{
return boost::apply_visitor(SegmentAddressFromHandle(handle), fSegments.at(segmentId));
return std::visit([handle](auto& s) { return reinterpret_cast<char*>(s.get_address_from_handle(handle)); }, fSegments.at(segmentId));
}
char* Allocate(size_t size, size_t alignment = 0)
@@ -741,24 +680,32 @@ class Manager
while (!ptr) {
try {
size_t segmentSize = boost::apply_visitor(SegmentSize(), fSegments.at(fSegmentId));
size_t segmentSize = std::visit([](auto& s) { return s.get_size(); }, fSegments.at(fSegmentId));
if (fullSize > segmentSize) {
throw MessageBadAlloc(tools::ToString("Requested message size (", fullSize, ") exceeds segment size (", segmentSize, ")"));
}
ptr = boost::apply_visitor(SegmentAllocate{fullSize}, fSegments.at(fSegmentId));
ptr = std::visit([fullSize](auto& s) { return reinterpret_cast<char*>(s.allocate(fullSize)); }, fSegments.at(fSegmentId));
ShmHeader::Construct(ptr, alignment);
} catch (boost::interprocess::bad_alloc& ba) {
// LOG(warn) << "Shared memory full...";
if (fBadAllocMaxAttempts >= 0 && ++numAttempts >= fBadAllocMaxAttempts) {
throw MessageBadAlloc(tools::ToString("shmem: could not create a message of size ", size, ", alignment: ", (alignment != 0) ? std::to_string(alignment) : "default", ", free memory: ", boost::apply_visitor(SegmentFreeMemory(), fSegments.at(fSegmentId))));
throw MessageBadAlloc(tools::ToString("shmem: could not create a message of size ", size,
", alignment: ", (alignment != 0) ? std::to_string(alignment) : "default",
", free memory: ", std::visit([](auto& s) { return s.get_free_memory(); }, fSegments.at(fSegmentId))));
}
if (numAttempts == 1 && fBadAllocMaxAttempts > 1) {
LOG(warn) << tools::ToString("shmem: could not create a message of size ", size, ", alignment: ", (alignment != 0) ? std::to_string(alignment) : "default", ", free memory: ", boost::apply_visitor(SegmentFreeMemory(), fSegments.at(fSegmentId)), ". Will try ", (fBadAllocMaxAttempts > 1 ? (std::to_string(fBadAllocMaxAttempts - 1)) + " more times" : " until success"), ", in ", fBadAllocAttemptIntervalInMs, "ms intervals");
LOG(warn) << tools::ToString("shmem: could not create a message of size ", size,
", alignment: ", (alignment != 0) ? std::to_string(alignment) : "default",
", free memory: ", std::visit([](auto& s) { return s.get_free_memory(); }, fSegments.at(fSegmentId)),
". Will try ", (fBadAllocMaxAttempts > 1 ? (std::to_string(fBadAllocMaxAttempts - 1)) + " more times" : " until success"),
", in ", fBadAllocAttemptIntervalInMs, "ms intervals");
}
std::this_thread::sleep_for(std::chrono::milliseconds(fBadAllocAttemptIntervalInMs));
if (Interrupted()) {
throw MessageBadAlloc(tools::ToString("shmem: could not create a message of size ", size, ", alignment: ", (alignment != 0) ? std::to_string(alignment) : "default", ", free memory: ", boost::apply_visitor(SegmentFreeMemory(), fSegments.at(fSegmentId))));
throw MessageBadAlloc(tools::ToString("shmem: could not create a message of size ", size,
", alignment: ", (alignment != 0) ? std::to_string(alignment) : "default",
", free memory: ", std::visit([](auto& s) { return s.get_free_memory(); }, fSegments.at(fSegmentId))));
} else {
continue;
}
@@ -792,12 +739,12 @@ class Manager
}
#endif
ShmHeader::Destruct(ptr);
boost::apply_visitor(SegmentDeallocate(ptr), fSegments.at(segmentId));
std::visit([ptr](auto& s) { s.deallocate(ptr); }, fSegments.at(segmentId));
}
char* ShrinkInPlace(size_t newSize, char* localPtr, uint16_t segmentId)
{
return boost::apply_visitor(SegmentBufferShrink(newSize, localPtr), fSegments.at(segmentId));
return std::visit(SegmentBufferShrink(newSize, localPtr), fSegments.at(segmentId));
}
uint16_t GetSegmentId() const { return fSegmentId; }
@@ -831,6 +778,30 @@ class Manager
auto GetMetadataMsgSize() const noexcept { return fMetadataMsgSize; }
// Resolve a MetaHeader (received over a side channel) to the local data pointer.
// The caller is responsible for ensuring the backing buffer remains alive for the
// duration of access; FairMQ provides no refcount protection for this path.
char* GetDataAddressFromHandle(const MetaHeader& meta)
{
if (meta.fManaged) {
if (meta.fSize == 0) {
return nullptr;
}
GetSegment(meta.fSegmentId);
auto it = fSegments.find(meta.fSegmentId);
if (it == fSegments.end()) {
throw SharedMemoryError(tools::ToString("GetDataAddressFromHandle: cannot open segment with id ", meta.fSegmentId));
}
return ShmHeader::UserPtr(GetAddressFromHandle(meta.fHandle, meta.fSegmentId));
} else {
UnmanagedRegion* region = GetRegionFromCache(meta.fRegionId);
if (!region) {
throw SharedMemoryError(tools::ToString("GetDataAddressFromHandle: cannot get unmanaged region with id ", meta.fRegionId));
}
return reinterpret_cast<char*>(region->GetData()) + meta.fHandle;
}
}
~Manager()
{
fRegionsGen += 1; // signal TL cache invalidation
@@ -845,7 +816,7 @@ class Manager
uint64_t fShmId64;
std::string fShmId;
uint16_t fSegmentId;
std::unordered_map<uint16_t, boost::variant<RBTreeBestFitSegment, SimpleSeqFitSegment>> fSegments; // TODO: refactor to use Segment class
std::unordered_map<uint16_t, std::variant<RBTreeBestFitSegment, SimpleSeqFitSegment>> fSegments; // TODO: refactor to use Segment class
boost::interprocess::managed_shared_memory fManagementSegment; // TODO: refactor to use ManagementSegment class
VoidAlloc fShmVoidAlloc;
boost::interprocess::interprocess_mutex* fShmMtx;

View File

@@ -14,6 +14,7 @@
#include "UnmanagedRegionImpl.h"
#include <fairmq/Message.h>
#include <fairmq/UnmanagedRegion.h>
#include <fairmq/Transports.h>
#include <fairlogger/Logger.h>
@@ -36,60 +37,34 @@ class Message final : public fair::mq::Message
public:
Message(Manager& manager, fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{0, 0, -1, -1, 0, fManager.GetSegmentId(), true}
, fRegionPtr(nullptr)
, fLocalPtr(nullptr)
{
fManager.IncrementMsgCounter();
}
: Message(manager, Alignment{0}, factory)
{}
Message(Manager& manager, Alignment alignment, fair::mq::TransportFactory* factory = nullptr)
Message(Manager& manager, Alignment /* alignment */, fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{0, 0, -1, -1, 0, fManager.GetSegmentId(), true}
, fAlignment(alignment.alignment)
, fRegionPtr(nullptr)
, fLocalPtr(nullptr)
, fSegmentId(fManager.GetSegmentId())
{
fManager.IncrementMsgCounter();
}
Message(Manager& manager, const size_t size, fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{0, 0, -1, -1, 0, fManager.GetSegmentId(), true}
, fRegionPtr(nullptr)
, fLocalPtr(nullptr)
{
InitializeChunk(size);
fManager.IncrementMsgCounter();
}
: Message(manager, size, Alignment{0}, factory)
{}
Message(Manager& manager, const size_t size, Alignment alignment, fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{0, 0, -1, -1, 0, fManager.GetSegmentId(), true}
, fAlignment(alignment.alignment)
, fRegionPtr(nullptr)
, fLocalPtr(nullptr)
, fSegmentId(fManager.GetSegmentId())
{
InitializeChunk(size, fAlignment);
InitializeChunk(size, alignment.alignment);
fManager.IncrementMsgCounter();
}
Message(Manager& manager, void* data, const size_t size, fair::mq::FreeFn* ffn, void* hint = nullptr, fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{0, 0, -1, -1, 0, fManager.GetSegmentId(), true}
, fRegionPtr(nullptr)
, fLocalPtr(nullptr)
, fSegmentId(fManager.GetSegmentId())
{
if (InitializeChunk(size)) {
std::memcpy(fLocalPtr, data, size);
@@ -102,13 +77,20 @@ class Message final : public fair::mq::Message
fManager.IncrementMsgCounter();
}
Message(Manager& manager, UnmanagedRegionPtr& region, void* data, const size_t size, void* hint = 0, fair::mq::TransportFactory* factory = nullptr)
Message(Manager& manager,
UnmanagedRegionPtr& region,
void* data,
const size_t size,
void* hint = nullptr,
fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{size, reinterpret_cast<size_t>(hint), -1, -1, static_cast<UnmanagedRegionImpl*>(region.get())->fRegionId, fManager.GetSegmentId(), false}
, fRegionPtr(nullptr)
, fLocalPtr(static_cast<char*>(data))
, fSize(size)
, fHint(reinterpret_cast<size_t>(hint))
, fRegionId(static_cast<UnmanagedRegionImpl*>(region.get())->fRegionId)
, fSegmentId(fManager.GetSegmentId())
, fManaged(false)
{
if (region->GetType() != GetType()) {
LOG(error) << "region type (" << region->GetType() << ") does not match message type (" << GetType() << ")";
@@ -117,7 +99,7 @@ class Message final : public fair::mq::Message
if (reinterpret_cast<const char*>(data) >= reinterpret_cast<const char*>(region->GetData()) &&
reinterpret_cast<const char*>(data) <= reinterpret_cast<const char*>(region->GetData()) + region->GetSize()) {
fMeta.fHandle = (boost::interprocess::managed_shared_memory::handle_t)(reinterpret_cast<const char*>(data) - reinterpret_cast<const char*>(region->GetData()));
fHandle = (boost::interprocess::managed_shared_memory::handle_t)(reinterpret_cast<const char*>(data) - reinterpret_cast<const char*>(region->GetData()));
} else {
LOG(error) << "trying to create region message with data from outside the region";
throw TransportError("trying to create region message with data from outside the region");
@@ -128,10 +110,13 @@ class Message final : public fair::mq::Message
Message(Manager& manager, MetaHeader& hdr, fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fManager(manager)
, fQueued(false)
, fMeta{hdr}
, fRegionPtr(nullptr)
, fLocalPtr(nullptr)
, fSize(hdr.fSize)
, fHint(hdr.fHint)
, fHandle(hdr.fHandle)
, fShared(hdr.fShared)
, fRegionId(hdr.fRegionId)
, fSegmentId(hdr.fSegmentId)
, fManaged(hdr.fManaged)
{
fManager.IncrementMsgCounter();
}
@@ -147,11 +132,10 @@ class Message final : public fair::mq::Message
fQueued = false;
}
void Rebuild(Alignment alignment) override
void Rebuild(Alignment /* alignment */) override
{
CloseMessage();
fQueued = false;
fAlignment = alignment.alignment;
}
void Rebuild(size_t size) override
@@ -165,8 +149,7 @@ class Message final : public fair::mq::Message
{
CloseMessage();
fQueued = false;
fAlignment = alignment.alignment;
InitializeChunk(size, fAlignment);
InitializeChunk(size, alignment.alignment);
}
void Rebuild(void* data, size_t size, fair::mq::FreeFn* ffn, void* hint = nullptr) override
@@ -184,20 +167,25 @@ class Message final : public fair::mq::Message
}
}
MetaHeader GetMeta() const
{
return {fSize, fHint, fHandle, fShared, fRegionId, fSegmentId, fManaged};
}
void* GetData() const override
{
if (!fLocalPtr) {
if (fMeta.fManaged) {
if (fMeta.fSize > 0) {
fManager.GetSegment(fMeta.fSegmentId);
fLocalPtr = ShmHeader::UserPtr(fManager.GetAddressFromHandle(fMeta.fHandle, fMeta.fSegmentId));
if (fManaged) {
if (fSize > 0) {
fManager.GetSegment(fSegmentId);
fLocalPtr = ShmHeader::UserPtr(fManager.GetAddressFromHandle(fHandle, fSegmentId));
} else {
fLocalPtr = nullptr;
}
} else {
fRegionPtr = fManager.GetRegionFromCache(fMeta.fRegionId);
fRegionPtr = fManager.GetRegionFromCache(fRegionId);
if (fRegionPtr) {
fLocalPtr = reinterpret_cast<char*>(fRegionPtr->GetData()) + fMeta.fHandle;
fLocalPtr = reinterpret_cast<char*>(fRegionPtr->GetData()) + fHandle;
} else {
// LOG(warn) << "could not get pointer from a region message";
fLocalPtr = nullptr;
@@ -208,37 +196,41 @@ class Message final : public fair::mq::Message
return static_cast<void*>(fLocalPtr);
}
size_t GetSize() const override { return fMeta.fSize; }
size_t GetSize() const override { return fSize; }
bool SetUsedSize(size_t newSize) override
bool SetUsedSize(size_t newSize, Alignment alignment = Alignment{0}) override
{
if (newSize == fMeta.fSize) {
if (newSize == fSize) {
return true;
} else if (newSize == 0) {
Deallocate();
return true;
} else if (newSize <= fMeta.fSize) {
} else if (newSize <= fSize) {
try {
char* oldPtr = fManager.GetAddressFromHandle(fHandle, fSegmentId);
try {
char* oldPtr = fManager.GetAddressFromHandle(fMeta.fHandle, fMeta.fSegmentId);
uint16_t userOffset = ShmHeader::UserOffset(oldPtr);
char* ptr = fManager.ShrinkInPlace(userOffset + newSize, oldPtr, fMeta.fSegmentId);
char* ptr = fManager.ShrinkInPlace(userOffset + newSize, oldPtr, fSegmentId);
fLocalPtr = ShmHeader::UserPtr(ptr);
fMeta.fSize = newSize;
fSize = newSize;
return true;
} catch (boost::interprocess::bad_alloc& e) {
// if shrinking fails (can happen due to boost alignment requirements):
// unused size >= 1000000 bytes: reallocate fully
// unused size < 1000000 bytes: simply reset the size and keep the rest of the buffer until message destruction
if (fMeta.fSize - newSize >= 1000000) {
char* ptr = fManager.Allocate(newSize, fAlignment);
if (fSize - newSize >= 1000000) {
if (alignment.alignment == 0) {
// if no alignment is provided, take the minimum alignment of the old pointer, but no more than 4096
alignment.alignment = 1 << std::min(__builtin_ctz(reinterpret_cast<size_t>(oldPtr)), 12);
}
char* ptr = fManager.Allocate(newSize, alignment.alignment);
char* userPtr = ShmHeader::UserPtr(ptr);
std::memcpy(userPtr, fLocalPtr, newSize);
fManager.Deallocate(fMeta.fHandle, fMeta.fSegmentId);
fManager.Deallocate(fHandle, fSegmentId);
fLocalPtr = userPtr;
fMeta.fHandle = fManager.GetHandleFromAddress(ptr, fMeta.fSegmentId);
fHandle = fManager.GetHandleFromAddress(ptr, fSegmentId);
}
fMeta.fSize = newSize;
fSize = newSize;
return true;
}
} catch (boost::interprocess::interprocess_exception& e) {
@@ -255,123 +247,178 @@ class Message final : public fair::mq::Message
uint16_t GetRefCount() const
{
if (fMeta.fHandle < 0) {
if (fHandle < 0) {
return 1;
}
if (fMeta.fManaged) { // managed segment
fManager.GetSegment(fMeta.fSegmentId);
return ShmHeader::RefCount(fManager.GetAddressFromHandle(fMeta.fHandle, fMeta.fSegmentId));
} else { // unmanaged region
if (fMeta.fShared < 0) { // UR msg is not yet shared
return 1;
} else {
fManager.GetSegment(fMeta.fSegmentId);
return ShmHeader::RefCount(fManager.GetAddressFromHandle(fMeta.fShared, fMeta.fSegmentId));
if (fManaged) { // managed segment
fManager.GetSegment(fSegmentId);
return ShmHeader::RefCount(fManager.GetAddressFromHandle(fHandle, fSegmentId));
}
if (fShared < 0) { // UR msg is not yet shared
return 1;
}
fRegionPtr = fManager.GetRegionFromCache(fRegionId);
if (!fRegionPtr) {
throw TransportError(tools::ToString("Cannot get unmanaged region with id ", fRegionId));
}
if (fRegionPtr->fRcSegmentSize > 0) {
return fRegionPtr->GetRefCountAddressFromHandle(fShared)->Get();
} else {
fManager.GetSegment(fSegmentId);
return ShmHeader::RefCount(fManager.GetAddressFromHandle(fShared, fSegmentId));
}
}
void Copy(const fair::mq::Message& other) override
{
const Message& otherMsg = static_cast<const Message&>(other);
if (otherMsg.fMeta.fHandle < 0) {
// if the other message is not initialized, close this one too and return
if (otherMsg.fHandle < 0) {
CloseMessage();
return;
}
if (fMeta.fHandle >= 0) {
// if this msg is already initialized, close it first
if (fHandle >= 0) {
CloseMessage();
}
if (otherMsg.fMeta.fManaged) { // managed segment
fMeta = otherMsg.fMeta;
fManager.GetSegment(fMeta.fSegmentId);
ShmHeader::IncrementRefCount(fManager.GetAddressFromHandle(fMeta.fHandle, fMeta.fSegmentId));
} else { // unmanaged region
if (otherMsg.fMeta.fShared < 0) { // if UR msg is not yet shared
// TODO: minimize the size to 0 and don't create extra space for user buffer alignment
// increment ref count
if (otherMsg.fManaged) { // msg in managed segment
fManager.GetSegment(otherMsg.fSegmentId);
ShmHeader::IncrementRefCount(fManager.GetAddressFromHandle(otherMsg.fHandle, otherMsg.fSegmentId));
} else { // msg in unmanaged region
fRegionPtr = fManager.GetRegionFromCache(otherMsg.fRegionId);
if (!fRegionPtr) {
throw TransportError(tools::ToString("Cannot get unmanaged region with id ", otherMsg.fRegionId));
}
if (fRegionPtr->fRcSegmentSize > 0) {
if (otherMsg.fShared < 0) {
// UR msg not yet shared, create the reference counting object with count 2
try {
otherMsg.fShared = fRegionPtr->HandleFromAddress(&(fRegionPtr->MakeRefCount(2)));
} catch (boost::interprocess::bad_alloc& ba) {
throw RefCountBadAlloc(tools::ToString("Insufficient space in the reference count segment ", otherMsg.fRegionId, ", original exception: bad_alloc: ", ba.what()));
}
} else {
fRegionPtr->GetRefCountAddressFromHandle(otherMsg.fShared)->Increment();
}
} else { // if RefCount segment size is 0, store the ref count in the managed segment
if (otherMsg.fShared < 0) { // if UR msg is not yet shared
char* ptr = fManager.Allocate(2, 0);
// point the fShared in the unmanaged region message to the refCount holder
otherMsg.fMeta.fShared = fManager.GetHandleFromAddress(ptr, fMeta.fSegmentId);
otherMsg.fShared = fManager.GetHandleFromAddress(ptr, fSegmentId);
// the message needs to be able to locate in which segment the refCount is stored
otherMsg.fMeta.fSegmentId = fMeta.fSegmentId;
// point this message to the same content as the unmanaged region message
fMeta = otherMsg.fMeta;
// increment the refCount
otherMsg.fSegmentId = fSegmentId;
ShmHeader::IncrementRefCount(ptr);
} else { // if the UR msg is already shared
fMeta = otherMsg.fMeta;
fManager.GetSegment(fMeta.fSegmentId);
ShmHeader::IncrementRefCount(fManager.GetAddressFromHandle(fMeta.fShared, fMeta.fSegmentId));
fManager.GetSegment(otherMsg.fSegmentId);
ShmHeader::IncrementRefCount(fManager.GetAddressFromHandle(otherMsg.fShared, otherMsg.fSegmentId));
}
}
}
// copy meta data
fSize = otherMsg.fSize;
fHint = otherMsg.fHint;
fHandle = otherMsg.fHandle;
fShared = otherMsg.fShared;
fRegionId = otherMsg.fRegionId;
fSegmentId = otherMsg.fSegmentId;
fManaged = otherMsg.fManaged;
}
~Message() override { CloseMessage(); }
private:
Manager& fManager;
bool fQueued;
MetaHeader fMeta;
size_t fAlignment;
mutable UnmanagedRegion* fRegionPtr;
mutable char* fLocalPtr;
mutable UnmanagedRegion* fRegionPtr = nullptr;
mutable char* fLocalPtr = nullptr;
size_t fSize = 0; // size of the shm buffer
size_t fHint = 0; // user-defined value, given by the user on message creation and returned to the user on "buffer no longer needed"-callbacks
boost::interprocess::managed_shared_memory::handle_t fHandle = -1; // handle to shm buffer, convertible to shm buffer ptr
mutable boost::interprocess::managed_shared_memory::handle_t fShared = -1; // handle to the buffer storing the ref count for shared buffers
uint16_t fRegionId = 0; // id of the unmanaged region
mutable uint16_t fSegmentId; // id of the managed segment
bool fManaged = true; // true = managed segment, false = unmanaged region
bool fQueued = false;
void SetMeta(const MetaHeader& meta)
{
fSize = meta.fSize;
fHint = meta.fHint;
fHandle = meta.fHandle;
fShared = meta.fShared;
fRegionId = meta.fRegionId;
fSegmentId = meta.fSegmentId;
fManaged = meta.fManaged;
}
char* InitializeChunk(const size_t size, size_t alignment = 0)
{
if (size == 0) {
fMeta.fSize = 0;
fSize = 0;
return fLocalPtr;
}
char* ptr = fManager.Allocate(size, alignment);
fMeta.fHandle = fManager.GetHandleFromAddress(ptr, fMeta.fSegmentId);
fMeta.fSize = size;
fHandle = fManager.GetHandleFromAddress(ptr, fSegmentId);
fSize = size;
fLocalPtr = ShmHeader::UserPtr(ptr);
return fLocalPtr;
}
void Deallocate()
{
if (fMeta.fHandle >= 0 && !fQueued) {
if (fMeta.fManaged) { // managed segment
fManager.GetSegment(fMeta.fSegmentId);
uint16_t refCount = ShmHeader::DecrementRefCount(fManager.GetAddressFromHandle(fMeta.fHandle, fMeta.fSegmentId));
if (fHandle >= 0 && !fQueued) {
if (fManaged) { // managed segment
fManager.GetSegment(fSegmentId);
uint16_t refCount = ShmHeader::DecrementRefCount(fManager.GetAddressFromHandle(fHandle, fSegmentId));
if (refCount == 1) {
fManager.Deallocate(fMeta.fHandle, fMeta.fSegmentId);
fManager.Deallocate(fHandle, fSegmentId);
}
} else { // unmanaged region
if (fMeta.fShared >= 0) {
// make sure segment is initialized in this transport
fManager.GetSegment(fMeta.fSegmentId);
// release unmanaged region block if ref count is one
uint16_t refCount = ShmHeader::DecrementRefCount(fManager.GetAddressFromHandle(fMeta.fShared, fMeta.fSegmentId));
if (fShared >= 0) {
fRegionPtr = fManager.GetRegionFromCache(fRegionId);
if (!fRegionPtr) {
throw TransportError(tools::ToString("Cannot get unmanaged region with id ", fRegionId));
}
if (fRegionPtr->fRcSegmentSize > 0) {
uint16_t refCount = fRegionPtr->GetRefCountAddressFromHandle(fShared)->Decrement();
if (refCount == 1) {
fManager.Deallocate(fMeta.fShared, fMeta.fSegmentId);
fRegionPtr->RemoveRefCount(*(fRegionPtr->GetRefCountAddressFromHandle(fShared)));
ReleaseUnmanagedRegionBlock();
}
} else { // if RefCount segment size is 0, get the ref count from the managed segment
// make sure segment is initialized in this transport
fManager.GetSegment(fSegmentId);
// release unmanaged region block if ref count is one
uint16_t refCount = ShmHeader::DecrementRefCount(fManager.GetAddressFromHandle(fShared, fSegmentId));
if (refCount == 1) {
fManager.Deallocate(fShared, fSegmentId);
ReleaseUnmanagedRegionBlock();
}
}
} else {
ReleaseUnmanagedRegionBlock();
}
}
}
fMeta.fHandle = -1;
fHandle = -1;
fLocalPtr = nullptr;
fMeta.fSize = 0;
fSize = 0;
}
void ReleaseUnmanagedRegionBlock()
{
if (!fRegionPtr) {
fRegionPtr = fManager.GetRegionFromCache(fMeta.fRegionId);
fRegionPtr = fManager.GetRegionFromCache(fRegionId);
}
if (fRegionPtr) {
fRegionPtr->ReleaseBlock({fMeta.fHandle, fMeta.fSize, fMeta.fHint});
fRegionPtr->ReleaseBlock({fHandle, fSize, fHint});
} else {
LOG(warn) << "region ack queue for id " << fMeta.fRegionId << " no longer exist. Not sending ack";
LOG(warn) << "region ack queue for id " << fRegionId << " no longer exist. Not sending ack";
}
}
@@ -379,7 +426,6 @@ class Message final : public fair::mq::Message
{
try {
Deallocate();
fAlignment = 0;
fManager.DecrementMsgCounter();
} catch (SharedMemoryError& sme) {
LOG(error) << "error closing message: " << sme.what();

View File

@@ -30,6 +30,7 @@
#include <ctime>
#include <iomanip>
#include <sstream>
#include <variant>
#include <poll.h>
@@ -58,12 +59,18 @@ void signalHandler(int signal)
gSignalStatus = signal;
}
Monitor::Monitor(string shmId, bool selfDestruct, bool interactive, bool viewOnly, unsigned int timeoutInMS, unsigned int intervalInMS, bool monitor, bool cleanOnExit)
Monitor::Monitor(string shmId,
bool selfDestruct,
bool interactive,
bool viewOnly,
unsigned int timeoutInMS,
unsigned int intervalInMS,
bool monitor,
bool cleanOnExit)
: fSelfDestruct(selfDestruct)
, fInteractive(interactive)
, fViewOnly(viewOnly)
, fMonitor(monitor)
, fSeenOnce(false)
, fCleanOnExit(cleanOnExit)
, fTimeoutInMS(timeoutInMS)
, fIntervalInMS(intervalInMS)
@@ -74,7 +81,7 @@ Monitor::Monitor(string shmId, bool selfDestruct, bool interactive, bool viewOnl
{
if (!fViewOnly) {
try {
bipc::named_mutex monitorStatus(bipc::create_only, string("fmq_" + fShmId + "_ms").c_str());
bipc::named_mutex monitorStatus(bipc::create_only, MakeShmName(fShmId, "ms").c_str());
} catch (bie&) {
if (fInteractive) {
LOG(error) << "fairmq-shmmonitor for shm id " << fShmId << " is already running. Try `fairmq-shmmonitor --cleanup --shmid " << fShmId << "`, or run in view-only mode (-v)";
@@ -132,7 +139,7 @@ void Monitor::Watch()
using namespace boost::interprocess;
try {
managed_shared_memory managementSegment(open_read_only, std::string("fmq_" + fShmId + "_mng").c_str());
managed_shared_memory managementSegment(open_read_only, MakeShmName(fShmId, "mng").c_str());
fSeenOnce = true;
@@ -180,11 +187,11 @@ bool Monitor::PrintShm(const ShmId& shmId)
using namespace boost::interprocess;
try {
managed_shared_memory managementSegment(open_read_only, std::string("fmq_" + shmId.shmId + "_mng").c_str());
managed_shared_memory managementSegment(open_read_only, MakeShmName(shmId.shmId, "mng").c_str());
VoidAlloc allocInstance(managementSegment.get_segment_manager());
Uint16SegmentInfoHashMap* shmSegments = managementSegment.find<Uint16SegmentInfoHashMap>(unique_instance).first;
std::unordered_map<uint16_t, boost::variant<RBTreeBestFitSegment, SimpleSeqFitSegment>> segments;
std::unordered_map<uint16_t, std::variant<RBTreeBestFitSegment, SimpleSeqFitSegment>> segments;
Uint16RegionInfoHashMap* shmRegions = managementSegment.find<Uint16RegionInfoHashMap>(unique_instance).first;
@@ -199,9 +206,9 @@ bool Monitor::PrintShm(const ShmId& shmId)
for (const auto& s : *shmSegments) {
if (s.second.fAllocationAlgorithm == AllocationAlgorithm::rbtree_best_fit) {
segments.emplace(s.first, RBTreeBestFitSegment(open_read_only, std::string("fmq_" + shmId.shmId + "_m_" + to_string(s.first)).c_str()));
segments.emplace(s.first, RBTreeBestFitSegment(open_read_only, MakeShmName(shmId.shmId, "m", s.first).c_str()));
} else {
segments.emplace(s.first, SimpleSeqFitSegment(open_read_only, std::string("fmq_" + shmId.shmId + "_m_" + to_string(s.first)).c_str()));
segments.emplace(s.first, SimpleSeqFitSegment(open_read_only, MakeShmName(shmId.shmId, "m", s.first).c_str()));
}
}
@@ -234,8 +241,8 @@ bool Monitor::PrintShm(const ShmId& shmId)
<< ", managed segments:\n";
for (const auto& s : segments) {
size_t free = boost::apply_visitor(SegmentFreeMemory(), s.second);
size_t total = boost::apply_visitor(SegmentSize(), s.second);
size_t free = std::visit([](auto& seg){ return seg.get_free_memory(); }, s.second);
size_t total = std::visit([](auto& seg){ return seg.get_size(); }, s.second);
size_t used = total - free;
std::string msgCount;
@@ -267,12 +274,21 @@ bool Monitor::PrintShm(const ShmId& shmId)
if (shmRegions && !shmRegions->empty()) {
ss << "\n unmanaged regions:";
for (const auto& r : *shmRegions) {
ss << "\n [" << r.first << "]: " << (r.second.fDestroyed ? "destroyed" : "alive");
ss << ", size: " << r.second.fSize;
for (const auto& [id, info] : *shmRegions) {
ss << "\n [" << id << "]: " << (info.fDestroyed ? "destroyed" : "alive");
ss << ", size: " << info.fSize;
try {
managed_shared_memory rcCountSegment(open_read_only, MakeShmName(shmId.shmId, "rrc", id).c_str());
auto size = rcCountSegment.get_size();
auto free = rcCountSegment.get_free_memory();
ss << ", rcCountSegment size: " << size << ", free: " << free << ", used: " << size - free;
} catch (bie&) {
ss << ", rcCountSegment: not found";
}
// try {
// boost::interprocess::message_queue q(open_only, std::string("fmq_" + std::string(shmId) + "_rgq_" + to_string(r.first)).c_str());
// boost::interprocess::message_queue q(open_only, std::string("fmq_" + std::string(shmId) + "_rgq_" + to_string(id)).c_str());
// ss << ", ack queue: " << q.get_num_msg() << " messages";
// } catch (bie&) {
// ss << ", ack queue: not found";
@@ -325,7 +341,7 @@ void Monitor::CheckHeartbeats()
while (!fTerminating) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
try {
managed_shared_memory managementSegment(open_read_only, std::string("fmq_" + fShmId + "_mng").c_str());
managed_shared_memory managementSegment(open_read_only, MakeShmName(fShmId, "mng").c_str());
Heartbeat* hb = managementSegment.find<Heartbeat>(unique_instance).first;
if (hb) {
@@ -407,7 +423,7 @@ void Monitor::Interactive()
void Monitor::PrintDebugInfo(const ShmId& shmId __attribute__((unused)))
{
#ifdef FAIRMQ_DEBUG_MODE
string managementSegmentName("fmq_" + shmId.shmId + "_mng");
string managementSegmentName = MakeShmName(shmId.shmId, "mng");
try {
bipc::managed_shared_memory managementSegment(bipc::open_only, managementSegmentName.c_str());
bipc::interprocess_mutex* mtx(managementSegment.find_or_construct<bipc::interprocess_mutex>(bipc::unique_instance)());
@@ -459,7 +475,7 @@ unordered_map<uint16_t, std::vector<BufferDebugInfo>> Monitor::GetDebugInfo(cons
unordered_map<uint16_t, std::vector<BufferDebugInfo>> result;
#ifdef FAIRMQ_DEBUG_MODE
string managementSegmentName("fmq_" + shmId.shmId + "_mng");
string managementSegmentName = MakeShmName(shmId.shmId, "mng");
try {
bipc::managed_shared_memory managementSegment(bipc::open_only, managementSegmentName.c_str());
bipc::interprocess_mutex* mtx(managementSegment.find_or_construct<bipc::interprocess_mutex>(bipc::unique_instance)());
@@ -499,7 +515,7 @@ unsigned long Monitor::GetFreeMemory(const ShmId& shmId, uint16_t segmentId)
{
using namespace boost::interprocess;
try {
bipc::managed_shared_memory managementSegment(bipc::open_only, std::string("fmq_" + shmId.shmId + "_mng").c_str());
bipc::managed_shared_memory managementSegment(bipc::open_only, MakeShmName(shmId.shmId, "mng").c_str());
boost::interprocess::interprocess_mutex* mtx(managementSegment.find_or_construct<bipc::interprocess_mutex>(bipc::unique_instance)());
boost::interprocess::scoped_lock<bipc::interprocess_mutex> lock(*mtx);
@@ -513,10 +529,10 @@ unsigned long Monitor::GetFreeMemory(const ShmId& shmId, uint16_t segmentId)
auto it = shmSegments->find(segmentId);
if (it != shmSegments->end()) {
if (it->second.fAllocationAlgorithm == AllocationAlgorithm::rbtree_best_fit) {
RBTreeBestFitSegment segment(open_read_only, std::string("fmq_" + shmId.shmId + "_m_" + std::to_string(segmentId)).c_str());
RBTreeBestFitSegment segment(open_read_only, MakeShmName(shmId.shmId, "m", segmentId).c_str());
return segment.get_free_memory();
} else {
SimpleSeqFitSegment segment(open_read_only, std::string("fmq_" + shmId.shmId + "_m_" + std::to_string(segmentId)).c_str());
SimpleSeqFitSegment segment(open_read_only, MakeShmName(shmId.shmId, "m", segmentId).c_str());
return segment.get_free_memory();
}
} else {
@@ -538,7 +554,7 @@ bool Monitor::SegmentIsPresent(const ShmId& shmId, uint16_t segmentId)
{
using namespace boost::interprocess;
try {
bipc::managed_shared_memory managementSegment(bipc::open_read_only, std::string("fmq_" + shmId.shmId + "_mng").c_str());
bipc::managed_shared_memory managementSegment(bipc::open_read_only, MakeShmName(shmId.shmId, "mng").c_str());
Uint16SegmentInfoHashMap* shmSegments = managementSegment.find<Uint16SegmentInfoHashMap>(unique_instance).first;
if (!shmSegments) {
@@ -550,9 +566,9 @@ bool Monitor::SegmentIsPresent(const ShmId& shmId, uint16_t segmentId)
if (it != shmSegments->end()) {
try {
if (it->second.fAllocationAlgorithm == AllocationAlgorithm::rbtree_best_fit) {
RBTreeBestFitSegment segment(open_read_only, std::string("fmq_" + shmId.shmId + "_m_" + std::to_string(segmentId)).c_str());
RBTreeBestFitSegment segment(open_read_only, MakeShmName(shmId.shmId, "m", segmentId).c_str());
} else {
SimpleSeqFitSegment segment(open_read_only, std::string("fmq_" + shmId.shmId + "_m_" + std::to_string(segmentId)).c_str());
SimpleSeqFitSegment segment(open_read_only, MakeShmName(shmId.shmId, "m", segmentId).c_str());
}
} catch (bie&) {
LOG(error) << "Could not find segment with id '" << segmentId << "' for shmId '" << shmId.shmId << "'";
@@ -579,7 +595,7 @@ bool Monitor::RegionIsPresent(const ShmId& shmId, uint16_t regionId)
{
using namespace boost::interprocess;
try {
bipc::managed_shared_memory managementSegment(bipc::open_read_only, std::string("fmq_" + shmId.shmId + "_mng").c_str());
bipc::managed_shared_memory managementSegment(bipc::open_read_only, MakeShmName(shmId.shmId, "mng").c_str());
Uint16RegionInfoHashMap* shmRegions = managementSegment.find<Uint16RegionInfoHashMap>(bipc::unique_instance).first;
if (!shmRegions) {
@@ -587,7 +603,7 @@ bool Monitor::RegionIsPresent(const ShmId& shmId, uint16_t regionId)
return false;
}
std::string regionFileName("fmq_" + shmId.shmId + "_rg_" + to_string(regionId));
std::string regionFileName(MakeShmName(shmId.shmId, "rg", regionId));
auto it = shmRegions->find(regionId);
if (it != shmRegions->end()) {
@@ -655,7 +671,7 @@ std::vector<std::pair<std::string, bool>> Monitor::Cleanup(const ShmId& shmIdT,
LOG(info) << "Cleaning up for shared memory id '" << shmId << "'...";
}
string managementSegmentName("fmq_" + shmId + "_mng");
string managementSegmentName = MakeShmName(shmId, "mng");
try {
bipc::managed_shared_memory managementSegment(bipc::open_read_only, managementSegmentName.c_str());
@@ -673,11 +689,12 @@ std::vector<std::pair<std::string, bool>> Monitor::Cleanup(const ShmId& shmIdT,
LOG(info) << "Found UnmanagedRegion with id: " << id << ", path: '" << path << "', flags: " << flags << ", fDestroyed: " << info.fDestroyed << ".";
}
if (!path.empty()) {
result.emplace_back(Remove<bipc::file_mapping>(path + "fmq_" + shmId + "_rg_" + to_string(id), verbose));
result.emplace_back(Remove<bipc::file_mapping>(path + MakeShmName(shmId, "rg", id), verbose));
} else {
result.emplace_back(Remove<bipc::shared_memory_object>("fmq_" + shmId + "_rg_" + to_string(id), verbose));
result.emplace_back(Remove<bipc::shared_memory_object>(MakeShmName(shmId, "rg", id), verbose));
}
result.emplace_back(Remove<bipc::message_queue>("fmq_" + shmId + "_rgq_" + to_string(id), verbose));
result.emplace_back(Remove<bipc::message_queue>(MakeShmName(shmId, "rgq", id), verbose));
result.emplace_back(Remove<bipc::shared_memory_object>(MakeShmName(shmId, "rrc", id), verbose));
}
}
@@ -687,7 +704,7 @@ std::vector<std::pair<std::string, bool>> Monitor::Cleanup(const ShmId& shmIdT,
LOG(info) << "Found " << shmSegments->size() << " managed segments...";
}
for (const auto& segment : *shmSegments) {
result.emplace_back(Remove<bipc::shared_memory_object>("fmq_" + shmId + "_m_" + to_string(segment.first), verbose));
result.emplace_back(Remove<bipc::shared_memory_object>(MakeShmName(shmId, "m", segment.first), verbose));
}
} else {
if (verbose) {
@@ -717,7 +734,7 @@ std::vector<std::pair<std::string, bool>> Monitor::Cleanup(const SessionId& sess
std::vector<std::pair<std::string, bool>> Monitor::CleanupFull(const ShmId& shmId, bool verbose /* = true */)
{
auto result = Cleanup(shmId, verbose);
result.emplace_back(Remove<bipc::named_mutex>("fmq_" + shmId.shmId + "_ms", verbose));
result.emplace_back(Remove<bipc::named_mutex>(MakeShmName(shmId.shmId, "ms"), verbose));
return result;
}
@@ -737,7 +754,7 @@ void Monitor::ResetContent(const ShmId& shmIdT, bool verbose /* = true */)
cout << "Resetting segments content for shared memory id '" << shmId << "'..." << endl;
}
string managementSegmentName("fmq_" + shmId + "_mng");
string managementSegmentName = MakeShmName(shmId, "mng");
try {
using namespace boost::interprocess;
managed_shared_memory managementSegment(open_only, managementSegmentName.c_str());
@@ -745,18 +762,18 @@ void Monitor::ResetContent(const ShmId& shmIdT, bool verbose /* = true */)
Uint16SegmentInfoHashMap* segmentInfos = managementSegment.find<Uint16SegmentInfoHashMap>(unique_instance).first;
if (segmentInfos) {
cout << "Found info for " << segmentInfos->size() << " managed segments" << endl;
for (const auto& s : *segmentInfos) {
for (const auto& [id, info] : *segmentInfos) {
if (verbose) {
cout << "Resetting content of segment '" << "fmq_" << shmId << "_m_" << s.first << "'..." << endl;
cout << "Resetting content of segment '" << MakeShmName(shmId, "m", id) << "'..." << endl;
}
try {
if (s.second.fAllocationAlgorithm == AllocationAlgorithm::rbtree_best_fit) {
RBTreeBestFitSegment segment(open_only, std::string("fmq_" + shmId + "_m_" + to_string(s.first)).c_str());
if (info.fAllocationAlgorithm == AllocationAlgorithm::rbtree_best_fit) {
RBTreeBestFitSegment segment(open_only, MakeShmName(shmId, "m", id).c_str());
void* ptr = segment.get_segment_manager();
size_t size = segment.get_segment_manager()->get_size();
new(ptr) segment_manager<char, rbtree_best_fit<mutex_family, offset_ptr<void>>, null_index>(size);
} else {
SimpleSeqFitSegment segment(open_only, std::string("fmq_" + shmId + "_m_" + to_string(s.first)).c_str());
SimpleSeqFitSegment segment(open_only, MakeShmName(shmId, "m", id).c_str());
void* ptr = segment.get_segment_manager();
size_t size = segment.get_segment_manager()->get_size();
new(ptr) segment_manager<char, simple_seq_fit<mutex_family, offset_ptr<void>>, null_index>(size);
@@ -766,7 +783,7 @@ void Monitor::ResetContent(const ShmId& shmIdT, bool verbose /* = true */)
}
} catch (bie& e) {
if (verbose) {
cout << "Error resetting content of segment '" << std::string("fmq_" + shmId + "_m_" + to_string(s.first)) << "': " << e.what() << endl;
cout << "Error resetting content of segment '" << MakeShmName(shmId, "m", id) << "': " << e.what() << endl;
}
}
}
@@ -778,7 +795,8 @@ void Monitor::ResetContent(const ShmId& shmIdT, bool verbose /* = true */)
if (shmRegions) {
for (const auto& region : *shmRegions) {
uint16_t id = region.first;
Remove<bipc::message_queue>("fmq_" + shmId + "_rgq_" + to_string(id), verbose);
Remove<bipc::message_queue>(MakeShmName(shmId, "rgq", id), verbose);
Remove<bipc::shared_memory_object>(MakeShmName(shmId, "rrc", id), verbose);
}
}
} catch (bie& e) {
@@ -807,7 +825,7 @@ void Monitor::ResetContent(const ShmId& shmIdT, const std::vector<SegmentConfig>
using namespace boost::interprocess;
std::string shmId = shmIdT.shmId;
std::string managementSegmentName("fmq_" + shmId + "_mng");
std::string managementSegmentName = MakeShmName(shmId, "mng");
// delete management segment
cout << "deleting management segment" << endl;
Remove<bipc::shared_memory_object>(managementSegmentName, verbose);
@@ -855,7 +873,7 @@ Monitor::~Monitor()
Cleanup(ShmId{fShmId});
}
if (!fViewOnly) {
RemoveMutex("fmq_" + fShmId + "_ms");
RemoveMutex(MakeShmName(fShmId, "ms"));
}
}

View File

@@ -170,7 +170,7 @@ class Monitor
bool fInteractive; // running in interactive mode
bool fViewOnly; // view only mode
bool fMonitor;
bool fSeenOnce; // true is segment has been opened successfully at least once
bool fSeenOnce{false}; // true is segment has been opened successfully at least once
bool fCleanOnExit;
unsigned int fTimeoutInMS;
unsigned int fIntervalInMS;

View File

@@ -71,7 +71,7 @@ class Poller final : public fair::mq::Poller
try {
int offset = 0;
// calculate offsets and the total size of the poll item set
for (std::string channel : channelList) {
for (const std::string& channel : channelList) {
fOffsetMap[channel] = offset;
offset += channelsMap.at(channel).size();
fNumItems += channelsMap.at(channel).size();
@@ -80,7 +80,7 @@ class Poller final : public fair::mq::Poller
fItems = new zmq_pollitem_t[fNumItems];
int index = 0;
for (std::string channel : channelList) {
for (const std::string& channel : channelList) {
for (unsigned int i = 0; i < channelsMap.at(channel).size(); ++i) {
index = fOffsetMap[channel] + i;

View File

@@ -16,6 +16,7 @@ FairMQ Shared Memory currently uses the following names to register shared memor
| `fmq_<shmId>_mng` | management segment (management data) | one of the devices | devices |
| `fmq_<shmId>_rg_<index>` | unmanaged region(s) | one of the devices | devices with unmanaged regions |
| `fmq_<shmId>_rgq_<index>` | unmanaged region queue(s) | one of the devices | devices with unmanaged regions |
| `fmq_<shmId>_rrc_<index>` | unmanaged region reference count pool(s) | one of the devices | devices with unmanaged regions |
| `fmq_<shmId>_ms` | shmmonitor status | shmmonitor | devices, shmmonitor |
The shmId is generated out of session id and user id.

View File

@@ -10,11 +10,11 @@
#include <fairmq/shmem/Common.h>
#include <fairmq/shmem/Monitor.h>
#include <boost/variant.hpp>
#include <fairmq/Transports.h>
#include <cstdint>
#include <string>
#include <variant>
namespace fair::mq::shmem
{
@@ -29,27 +29,23 @@ struct Segment
friend class Monitor;
Segment(const std::string& shmId, uint16_t id, size_t size, SimpleSeqFit)
: fSegment(SimpleSeqFitSegment(boost::interprocess::open_or_create,
std::string("fmq_" + shmId + "_m_" + std::to_string(id)).c_str(),
size))
: fSegment(SimpleSeqFitSegment(boost::interprocess::open_or_create, MakeShmName(shmId, "m", id).c_str(), size))
{
Register(shmId, id, AllocationAlgorithm::simple_seq_fit);
}
Segment(const std::string& shmId, uint16_t id, size_t size, RBTreeBestFit)
: fSegment(RBTreeBestFitSegment(boost::interprocess::open_or_create,
std::string("fmq_" + shmId + "_m_" + std::to_string(id)).c_str(),
size))
: fSegment(RBTreeBestFitSegment(boost::interprocess::open_or_create, MakeShmName(shmId, "m", id).c_str(), size))
{
Register(shmId, id, AllocationAlgorithm::rbtree_best_fit);
}
size_t GetSize() const { return boost::apply_visitor(SegmentSize(), fSegment); }
void* GetData() { return boost::apply_visitor(SegmentAddress(), fSegment); }
size_t GetSize() const { return std::visit([](auto& s){ return s.get_size(); }, fSegment); }
void* GetData() { return std::visit([](auto& s){ return s.get_address(); }, fSegment); }
size_t GetFreeMemory() const { return boost::apply_visitor(SegmentFreeMemory(), fSegment); }
size_t GetFreeMemory() const { return std::visit([](auto& s){ return s.get_free_memory(); }, fSegment); }
void Zero() { boost::apply_visitor(SegmentMemoryZeroer(), fSegment); }
void Zero() { std::visit([](auto& s){ return s.zero_free_memory(); }, fSegment); }
void Lock()
{
if (mlock(GetData(), GetSize()) == -1) {
@@ -59,16 +55,16 @@ struct Segment
static void Remove(const std::string& shmId, uint16_t id)
{
Monitor::RemoveObject("fmq_" + shmId + "_m_" + std::to_string(id));
Monitor::RemoveObject(MakeShmName(shmId, "m", id));
}
private:
boost::variant<RBTreeBestFitSegment, SimpleSeqFitSegment> fSegment;
std::variant<RBTreeBestFitSegment, SimpleSeqFitSegment> fSegment;
static void Register(const std::string& shmId, uint16_t id, AllocationAlgorithm allocAlgo)
{
using namespace boost::interprocess;
managed_shared_memory mngSegment(open_or_create, std::string("fmq_" + shmId + "_mng").c_str(), kManagementSegmentSize);
managed_shared_memory mngSegment(open_or_create, MakeShmName(shmId, "mng").c_str(), kManagementSegmentSize);
VoidAlloc alloc(mngSegment.get_segment_manager());
Uint16SegmentInfoHashMap* shmSegments = mngSegment.find_or_construct<Uint16SegmentInfoHashMap>(unique_instance)(alloc);

View File

@@ -39,18 +39,19 @@ namespace fair::mq::shmem
class Socket final : public fair::mq::Socket
{
public:
Socket(Manager& manager, const std::string& type, const std::string& name, const std::string& id, void* context, fair::mq::TransportFactory* fac = nullptr)
Socket(Manager& manager,
const std::string& type,
const std::string& name,
const std::string& id,
void* context,
fair::mq::TransportFactory* fac = nullptr)
: fair::mq::Socket(fac)
, fManager(manager)
, fId(id + "." + name + "." + type)
, fSocket(nullptr)
, fMonitorSocket(nullptr)
, fBytesTx(0)
, fBytesRx(0)
, fMessagesTx(0)
, fMessagesRx(0)
, fTimeout(100)
, fConnectedPeersCount(0)
, fMetadataMsgSize(manager.GetMetadataMsgSize())
{
assert(context);
@@ -129,9 +130,11 @@ class Socket final : public fair::mq::Socket
}
int elapsed = 0;
MetaHeader meta{ shmMsg->fSize, shmMsg->fHint, shmMsg->fHandle, shmMsg->fShared, shmMsg->fRegionId, shmMsg->fSegmentId, shmMsg->fManaged };
// meta msg format: | MetaHeader | padded to fMetadataMsgSize |
zmq::ZMsg zmqMsg(std::max(fMetadataMsgSize, sizeof(MetaHeader)));
std::memcpy(zmqMsg.Data(), &(shmMsg->fMeta), sizeof(MetaHeader));
std::memcpy(zmqMsg.Data(), &meta, sizeof(MetaHeader));
while (true) {
int nbytes = zmq_msg_send(zmqMsg.Msg(), fSocket, flags);
@@ -167,7 +170,8 @@ class Socket final : public fair::mq::Socket
while (true) {
Message* shmMsg = static_cast<Message*>(msg.get());
int nbytes = zmq_recv(fSocket, &(shmMsg->fMeta), sizeof(MetaHeader), flags);
MetaHeader meta;
int nbytes = zmq_recv(fSocket, &meta, sizeof(MetaHeader), flags);
if (nbytes > 0) {
// check for number of received messages. must be 1
if (static_cast<std::size_t>(nbytes) < sizeof(MetaHeader)) {
@@ -177,6 +181,8 @@ class Socket final : public fair::mq::Socket
"Expected minimum size of ", sizeof(MetaHeader), " bytes, received ", nbytes));
}
shmMsg->SetMeta(meta);
size_t size = shmMsg->GetSize();
fBytesRx += size;
++fMessagesRx;
@@ -195,7 +201,7 @@ class Socket final : public fair::mq::Socket
}
}
int64_t Send(std::vector<MessagePtr>& msgVec, int timeout = -1) override
int64_t Send(Parts::container& msgVec, int timeout = -1) override
{
int flags = 0;
if (timeout == 0) {
@@ -218,7 +224,8 @@ class Socket final : public fair::mq::Socket
}
assertm(dynamic_cast<shmem::Message*>(msgPtr), "given mq::Message is a shmem::Message"); // NOLINT
auto shmMsg = static_cast<shmem::Message*>(msgPtr); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
std::memcpy(metas++, &(shmMsg->fMeta), sizeof(MetaHeader));
MetaHeader meta{ shmMsg->fSize, shmMsg->fHint, shmMsg->fHandle, shmMsg->fShared, shmMsg->fRegionId, shmMsg->fSegmentId, shmMsg->fManaged };
std::memcpy(metas++, &meta, sizeof(MetaHeader));
}
while (true) {
@@ -230,7 +237,7 @@ class Socket final : public fair::mq::Socket
for (auto& msg : msgVec) {
Message* shmMsg = static_cast<Message*>(msg.get());
shmMsg->fQueued = true;
totalSize += shmMsg->fMeta.fSize;
totalSize += shmMsg->fSize;
}
// store statistics on how many messages have been sent
@@ -254,7 +261,7 @@ class Socket final : public fair::mq::Socket
return static_cast<int>(TransferCode::error);
}
int64_t Receive(std::vector<MessagePtr>& msgVec, int timeout = -1) override
int64_t Receive(Parts::container& msgVec, int timeout = -1) override
{
int flags = 0;
if (timeout == 0) {
@@ -450,15 +457,15 @@ class Socket final : public fair::mq::Socket
private:
Manager& fManager;
std::string fId;
void* fSocket;
void* fMonitorSocket;
void* fSocket{nullptr};
void* fMonitorSocket{nullptr};
std::atomic<unsigned long> fBytesTx;
std::atomic<unsigned long> fBytesRx;
std::atomic<unsigned long> fMessagesTx;
std::atomic<unsigned long> fMessagesRx;
int fTimeout;
mutable unsigned long fConnectedPeersCount;
int fTimeout{100};
mutable unsigned long fConnectedPeersCount{0};
std::size_t fMetadataMsgSize;
};

View File

@@ -118,7 +118,10 @@ class TransportFactory final : public fair::mq::TransportFactory
return std::make_unique<Message>(*fManager, data, size, ffn, hint, this);
}
MessagePtr CreateMessage(UnmanagedRegionPtr& region, void* data, size_t size, void* hint = 0) override
MessagePtr CreateMessage(UnmanagedRegionPtr& region,
void* data,
size_t size,
void* hint = nullptr) override
{
return std::make_unique<Message>(*fManager, region, data, size, hint, this);
}
@@ -198,6 +201,8 @@ class TransportFactory final : public fair::mq::TransportFactory
void Resume() override { fManager->Resume(); }
void Reset() override { fManager->Reset(); }
char* GetDataAddressFromHandle(const MetaHeader& meta) { return fManager->GetDataAddressFromHandle(meta); }
~TransportFactory() override
{
LOG(debug) << "Destroying Shared Memory transport...";

View File

@@ -13,6 +13,7 @@
#include <fairmq/shmem/Monitor.h>
#include <fairmq/tools/Strings.h>
#include <fairmq/UnmanagedRegion.h>
#include <fairmq/Transports.h>
#include <fairlogger/Logger.h>
@@ -59,11 +60,11 @@ struct UnmanagedRegion
, fRemoveOnDestruction(cfg.removeOnDestruction)
, fLinger(cfg.linger)
, fStopAcks(false)
, fName("fmq_" + shmId + "_rg_" + std::to_string(cfg.id.value()))
, fQueueName("fmq_" + shmId + "_rgq_" + std::to_string(cfg.id.value()))
, fShmemObject()
, fName(MakeShmName(shmId, "rg", cfg.id.value()))
, fQueueName(MakeShmName(shmId, "rgq", cfg.id.value()))
, fRefCountSegmentName(MakeShmName(shmId, "rrc", cfg.id.value()))
, fFile(nullptr)
, fFileMapping()
, fRcSegmentSize(cfg.rcSegmentSize)
, fQueue(nullptr)
, fCallback(nullptr)
, fBulkCallback(nullptr)
@@ -99,7 +100,7 @@ struct UnmanagedRegion
}
fFileMapping = file_mapping(fName.c_str(), read_write);
LOG(debug) << "UnmanagedRegion(): initialized file: " << fName;
fRegion = mapped_region(fFileMapping, read_write, 0, size, 0, cfg.creationFlags);
fRegion = mapped_region(fFileMapping, read_write, 0, size, nullptr, cfg.creationFlags);
} else {
try {
// if opening fails, create
@@ -123,7 +124,7 @@ struct UnmanagedRegion
}
try {
fRegion = mapped_region(fShmemObject, read_write, 0, 0, 0, cfg.creationFlags);
fRegion = mapped_region(fShmemObject, read_write, 0, 0, nullptr, cfg.creationFlags);
if (size != 0 && size != fRegion.get_size()) {
LOG(error) << "Created/opened region size (" << fRegion.get_size() << ") does not match configured size (" << size << ")";
throw TransportError(tools::ToString("Created/opened region size (", fRegion.get_size(), ") does not match configured size (", size, ")"));
@@ -145,11 +146,13 @@ struct UnmanagedRegion
LOG(debug) << "Successfully zeroed free memory of region " << id << ".";
}
InitializeRefCountSegment(fRcSegmentSize);
if (fControlling && created) {
Register(shmId, cfg);
}
LOG(debug) << (created ? "Created" : "Opened") << " unmanaged shared memory region: " << fName << " (" << (fControlling ? "controller" : "viewer") << ")";
LOG(debug) << (created ? "Created" : "Opened") << " unmanaged shared memory region: " << fName << " (" << (fControlling ? "controller" : "viewer") << "), refCount segment size: " << fRcSegmentSize;
}
UnmanagedRegion() = delete;
@@ -186,6 +189,19 @@ struct UnmanagedRegion
bool RemoveOnDestruction() { return fRemoveOnDestruction; }
RefCount& MakeRefCount(uint16_t initialCount = 1)
{
RefCount* refCount = fRefCountPool->allocate_one().get();
new (refCount) RefCount(initialCount);
return *refCount;
}
void RemoveRefCount(RefCount& refCount)
{
refCount.~RefCount();
fRefCountPool->deallocate_one(&refCount);
}
~UnmanagedRegion()
{
LOG(debug) << "~UnmanagedRegion(): " << fName << " (" << (fControlling ? "controller" : "viewer") << ")";
@@ -208,6 +224,11 @@ struct UnmanagedRegion
if (Monitor::RemoveFileMapping(fName.c_str())) {
LOG(trace) << "File mapping '" << fName << "' destroyed.";
}
if (fRefCountSegment) {
if (Monitor::RemoveObject(fRefCountSegmentName)) {
LOG(trace) << "Ref Count Segment '" << fRefCountSegmentName << "' destroyed.";
}
}
} else {
LOG(debug) << "Skipping removal of " << fName << " unmanaged region, because RegionConfig::removeOnDestruction is false";
}
@@ -235,6 +256,7 @@ struct UnmanagedRegion
std::atomic<bool> fStopAcks;
std::string fName;
std::string fQueueName;
std::string fRefCountSegmentName;
boost::interprocess::shared_memory_object fShmemObject;
FILE* fFile;
boost::interprocess::file_mapping fFileMapping;
@@ -244,7 +266,10 @@ struct UnmanagedRegion
std::condition_variable fBlockSendCV;
std::vector<RegionBlock> fBlocksToFree;
const std::size_t fAckBunchSize = 256;
uint64_t fRcSegmentSize;
std::unique_ptr<boost::interprocess::message_queue> fQueue;
std::unique_ptr<boost::interprocess::managed_shared_memory> fRefCountSegment;
std::unique_ptr<RefCountPool> fRefCountPool;
std::thread fAcksReceiver;
std::thread fAcksSender;
@@ -262,7 +287,7 @@ struct UnmanagedRegion
{
using namespace boost::interprocess;
LOG(debug) << "Registering unmanaged shared memory region with id " << cfg.id.value();
managed_shared_memory mngSegment(open_or_create, std::string("fmq_" + shmId + "_mng").c_str(), kManagementSegmentSize);
managed_shared_memory mngSegment(open_or_create, MakeShmName(shmId, "mng").c_str(), kManagementSegmentSize);
VoidAlloc alloc(mngSegment.get_segment_manager());
Uint16RegionInfoHashMap* shmRegions = mngSegment.find_or_construct<Uint16RegionInfoHashMap>(unique_instance)(alloc);
@@ -275,7 +300,7 @@ struct UnmanagedRegion
throw TransportError(tools::ToString("Unmanaged Region with id ", cfg.id.value(), " has already been registered. Only unique IDs per session are allowed."));
}
shmRegions->emplace(cfg.id.value(), RegionInfo(cfg.path.c_str(), cfg.creationFlags, cfg.userFlags, cfg.size, alloc));
shmRegions->emplace(cfg.id.value(), RegionInfo(cfg.path.c_str(), cfg.creationFlags, cfg.userFlags, cfg.size, cfg.rcSegmentSize, alloc));
(eventCounter->fCount)++;
}
@@ -294,6 +319,29 @@ struct UnmanagedRegion
}
}
void InitializeRefCountSegment(uint64_t size)
{
using namespace boost::interprocess;
if (!fRefCountSegment && size > 0) {
fRefCountSegment = std::make_unique<managed_shared_memory>(open_or_create, fRefCountSegmentName.c_str(), size);
LOG(trace) << "shmem: initialized ref count segment: " << fRefCountSegmentName;
fRefCountPool = std::make_unique<RefCountPool>(fRefCountSegment->get_segment_manager());
}
}
RefCount* GetRefCountAddressFromHandle(const boost::interprocess::managed_shared_memory::handle_t handle)
{
if (fRefCountPool) {
return reinterpret_cast<RefCount*>(fRefCountSegment->get_address_from_handle(handle));
}
return nullptr;
};
boost::interprocess::managed_shared_memory::handle_t HandleFromAddress(const void* ptr)
{
return fRefCountSegment->get_handle_from_address(ptr);
}
void StartAckSender()
{
if (!fAcksSender.joinable()) {

View File

@@ -37,8 +37,6 @@ class UnmanagedRegionImpl final : public fair::mq::UnmanagedRegion
fair::mq::TransportFactory* factory)
: fair::mq::UnmanagedRegion(factory)
, fManager(manager)
, fRegion(nullptr)
, fRegionId(0)
{
auto [regionPtr, regionId] = fManager.CreateRegion(size, callback, bulkCallback, std::move(cfg));
fRegion = regionPtr;
@@ -62,8 +60,8 @@ class UnmanagedRegionImpl final : public fair::mq::UnmanagedRegion
private:
Manager& fManager;
shmem::UnmanagedRegion* fRegion;
uint16_t fRegionId;
shmem::UnmanagedRegion* fRegion{nullptr};
uint16_t fRegionId{0};
};
} // namespace fair::mq::shmem

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2017-2021 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2017-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
@@ -8,12 +8,12 @@
#include <fairlogger/Logger.h>
#include <fairmq/tools/Network.h>
#include <fairmq/tools/Strings.h>
#ifndef _GNU_SOURCE
#define _GNU_SOURCE // To get defns of NI_MAXSERV and NI_MAXHOST
#endif
#include <algorithm>
#include <array>
#include <boost/algorithm/string.hpp> // trim
#include <boost/asio.hpp>
@@ -99,7 +99,7 @@ string getDefaultRouteNetworkInterface()
string interfaceName;
#ifdef __APPLE__ // MacOS
unique_ptr<FILE, decltype(pclose)*> file(
unique_ptr<FILE, int (*)(FILE*)> file(
popen("route -n get default | grep interface | cut -d \":\" -f 2", "r"), pclose);
#else // Linux
ifstream is("/proc/net/route");
@@ -128,7 +128,7 @@ string getDefaultRouteNetworkInterface()
LOG(debug) << "could not get network interface of the default route from /proc/net/route, "
"going to try via 'ip route'";
unique_ptr<FILE, decltype(pclose)*> file(
unique_ptr<FILE, int (*)(FILE*)> file(
popen("ip route | grep default | cut -d \" \" -f 5 | head -n 1", "r"), pclose);
#endif
@@ -158,33 +158,22 @@ string getDefaultRouteNetworkInterface()
}
string getIpFromHostname(const string& hostname)
{
try {
boost::asio::io_context ioc;
boost::asio::ip::tcp::resolver resolver(ioc);
using namespace boost::asio::ip;
try {
tcp::resolver resolver(ioc);
tcp::resolver::query query(hostname, "");
tcp::resolver::iterator end;
auto it = find_if(static_cast<basic_resolver_iterator<tcp>>(resolver.resolve(query)),
end,
[](const tcp::endpoint& ep) { return ep.address().is_v4(); });
if (it != end) {
stringstream ss;
ss << static_cast<tcp::endpoint>(*it).address();
return ss.str();
}
auto const result = resolver.resolve(boost::asio::ip::tcp::v4(), hostname, "");
if (result.empty()) {
LOG(warn) << "could not find ipv4 address for hostname '" << hostname << "'";
return "";
} catch (exception& e) {
LOG(error) << "could not resolve hostname '" << hostname << "', reason: " << e.what();
return "";
}
return ToString(result.begin()->endpoint().address());
}
catch (std::exception const& ex)
{
LOG(error) << "could not resolve hostname '" << hostname << "', reason: " << ex.what();
return "";
}
} // namespace fair::mq::tools

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2017-2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2017-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
@@ -10,17 +10,26 @@
#include <fairmq/tools/Strings.h>
#include <boost/asio.hpp>
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
#include <boost/process/v1.hpp>
#else
#include <boost/process.hpp>
#endif
#include <chrono>
#include <csignal> // kill, signals
#include <iostream>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <thread>
#include <utility>
using namespace std;
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
namespace bp = boost::process::v1;
#else
namespace bp = boost::process;
#endif
namespace ba = boost::asio;
namespace bs = boost::system;
@@ -29,13 +38,21 @@ class LinePrinter
public:
LinePrinter(stringstream& out, string prefix)
: fOut(out)
, fPrefix(move(prefix))
, fPrefix(std::move(prefix))
{}
// prints line with prefix on both cout (thread-safe) and output stream
// prints line with prefix on both cout and output stream
void Print(const string& line)
{
// Serialize: the standard allows concurrent insertion on std::cout,
// but libstdc++ maintains the formatted-output state (e.g.
// ios_base::width) with plain reads and writes that constitute data
// races; the lock also keeps whole lines from interleaving.
static mutex sCoutMutex;
{
lock_guard<mutex> lock(sCoutMutex);
cout << fair::mq::tools::ToString(fPrefix, line, "\n") << flush;
}
fOut << fPrefix << line << endl;
}
@@ -64,22 +81,22 @@ execute_result execute(const string& cmd, const string& prefix, const string& in
p.Print(cmd);
ba::io_service ios;
ba::io_context ioc;
// containers for std_in
ba::const_buffer inputBuffer(ba::buffer(input));
bp::async_pipe inputPipe(ios);
bp::async_pipe inputPipe(ioc);
// containers for std_out
ba::streambuf outputBuffer;
bp::async_pipe outputPipe(ios);
bp::async_pipe outputPipe(ioc);
// containers for std_err
ba::streambuf errorBuffer;
bp::async_pipe errorPipe(ios);
bp::async_pipe errorPipe(ioc);
const string delimiter = "\n";
ba::steady_timer inputTimer(ios);
ba::steady_timer inputTimer(ioc);
inputTimer.expires_after(std::chrono::milliseconds(1000)); // NOLINT
ba::steady_timer signalTimer(ios);
ba::steady_timer signalTimer(ioc);
signalTimer.expires_after(std::chrono::milliseconds(2000)); // NOLINT
// child process
@@ -154,7 +171,7 @@ execute_result execute(const string& cmd, const string& prefix, const string& in
};
ba::async_read_until(errorPipe, errorBuffer, delimiter, onStdErr);
ios.run();
ioc.run();
c.wait();
result.exit_code = c.exit_code();

View File

@@ -37,7 +37,6 @@ class Context
Context(int numIoThreads)
: fZmqCtx(zmq_ctx_new())
, fInterrupted(false)
, fRegionCounter(1)
{
if (!fZmqCtx) {
throw ContextError(tools::ToString("failed creating context, reason: ", zmq_strerror(errno)));
@@ -180,7 +179,7 @@ class Context
mutable std::mutex fMtx;
std::atomic<bool> fInterrupted;
uint16_t fRegionCounter;
uint16_t fRegionCounter{1};
std::condition_variable fRegionEventsCV;
std::vector<RegionInfo> fRegionInfos;
std::queue<RegionInfo> fRegionEvents;

View File

@@ -107,7 +107,11 @@ class Message final : public fair::mq::Message
}
}
Message(UnmanagedRegionPtr& region, void* data, const size_t size, void* hint = 0, fair::mq::TransportFactory* factory = nullptr)
Message(UnmanagedRegionPtr& region,
void* data,
const size_t size,
void* hint = nullptr,
fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory)
, fMsg(std::make_unique<zmq_msg_t>())
{
@@ -210,7 +214,7 @@ class Message final : public fair::mq::Message
// destroyed. Used size is applied only once in ApplyUsedSize, which is called by the socket
// before sending. This function just updates the desired size until the actual "resizing"
// happens.
bool SetUsedSize(size_t size) override
bool SetUsedSize(size_t size, Alignment /* alignment */ = Alignment{0}) override
{
if (size == GetSize()) {
// nothing to do

View File

@@ -86,7 +86,7 @@ class Poller final : public fair::mq::Poller
fItems = new zmq_pollitem_t[fNumItems];
int index = 0;
for (std::string channel : channelList) {
for (const std::string& channel : channelList) {
for (unsigned int i = 0; i < channelsMap.at(channel).size(); ++i) {
index = fOffsetMap[channel] + i;

View File

@@ -41,8 +41,7 @@ class Socket final : public fair::mq::Socket
, fBytesRx(0)
, fMessagesTx(0)
, fMessagesRx(0)
, fTimeout(100)
, fConnectedPeersCount(0)
{
if (fSocket == nullptr) {
LOG(error) << "Failed creating socket " << fId << ", reason: " << zmq_strerror(errno);
@@ -154,7 +153,7 @@ class Socket final : public fair::mq::Socket
}
}
int64_t Send(std::vector<std::unique_ptr<fair::mq::Message>>& msgVec, int timeout = -1) override
int64_t Send(Parts::container& msgVec, int timeout = -1) override
{
int flags = 0;
if (timeout == 0) {
@@ -206,7 +205,7 @@ class Socket final : public fair::mq::Socket
}
}
int64_t Receive(std::vector<std::unique_ptr<fair::mq::Message>>& msgVec, int timeout = -1) override
int64_t Receive(Parts::container& msgVec, int timeout = -1) override
{
int flags = 0;
if (timeout == 0) {
@@ -225,7 +224,7 @@ class Socket final : public fair::mq::Socket
int nbytes = zmq_msg_recv(static_cast<Message*>(part.get())->GetMessage(), fSocket, flags);
if (nbytes >= 0) {
static_cast<Message*>(part.get())->Realign();
msgVec.push_back(move(part));
msgVec.push_back(std::move(part));
totalSize += nbytes;
} else if (zmq_errno() == EAGAIN || zmq_errno() == EINTR) {
if (fCtx.Interrupted()) {
@@ -405,8 +404,8 @@ class Socket final : public fair::mq::Socket
std::atomic<unsigned long> fMessagesTx;
std::atomic<unsigned long> fMessagesRx;
int fTimeout;
mutable unsigned long fConnectedPeersCount;
int fTimeout{100};
mutable unsigned long fConnectedPeersCount{0};
};
} // namespace fair::mq::zmq

View File

@@ -73,7 +73,10 @@ class TransportFactory final : public fair::mq::TransportFactory
return std::make_unique<Message>(data, size, ffn, hint, this);
}
MessagePtr CreateMessage(UnmanagedRegionPtr& region, void* data, size_t size, void* hint = 0) override
MessagePtr CreateMessage(UnmanagedRegionPtr& region,
void* data,
size_t size,
void* hint = nullptr) override
{
return std::make_unique<Message>(region, data, size, hint, this);
}

View File

@@ -16,18 +16,33 @@ if(FairLogger_VERSION VERSION_LESS 1.9.0 AND FairLogger_VERSION VERSION_GREATER_
LIST(APPEND definitions FAIR_MIN_SEVERITY=trace)
endif()
if(FAIRMQ_BOOST_PROCESS_V1_HEADER)
LIST(APPEND definitions FAIRMQ_BOOST_PROCESS_V1_HEADER)
endif()
if(definitions)
set(definitions DEFINITIONS ${definitions})
endif()
set(test_environment)
if(ENABLE_SANITIZER_LEAK)
get_filename_component(lsan_supps "${CMAKE_CURRENT_SOURCE_DIR}/leak_sanitizer_suppressions.txt" ABSOLUTE)
set(environment ENVIRONMENT "LSAN_OPTIONS=set:suppressions=${lsan_supps}")
list(APPEND test_environment "LSAN_OPTIONS=set:suppressions=${lsan_supps}")
endif()
# Run the tests against an alternative runtime library directory, e.g. a
# tsan-instrumented libstdc++. Set per test (not at the job level), because
# such a library must only be loaded into instrumented executables.
if(FAIRMQ_TEST_LD_LIBRARY_PATH)
list(APPEND test_environment "LD_LIBRARY_PATH=path_list_prepend:${FAIRMQ_TEST_LD_LIBRARY_PATH}")
endif()
if(test_environment)
set(environment ENVIRONMENT ${test_environment})
endif()
add_testhelper(runTestDevice
SOURCES
helper/runTestDevice.cxx
helper/LocaleWarmup.h
helper/devices/TestPairLeft.h
helper/devices/TestPairRight.h
helper/devices/TestPollIn.h
@@ -134,6 +149,7 @@ add_testsuite(Device
${CMAKE_CURRENT_SOURCE_DIR}/device
${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 20
${definitions}
${environment}
)
@@ -253,7 +269,7 @@ add_testsuite(Tools
LINKS FairMQ
INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 20
TIMEOUT 5
${environment}
)

View File

@@ -1,19 +0,0 @@
ARG VERSION=latest
FROM fedora:${VERSION}
ARG VERSION=latest
LABEL org.opencontainers.image.source "https://github.com/FairRootGroup/FairMQ"
LABEL org.opencontainers.image.description "FairMQ development environment"
RUN dnf -y update
# https://git.gsi.de/SDE/packages/builder
RUN dnf -y install https://alfa-ci.gsi.de/packages/rpm/fedora-$VERSION-x86_64/fairsoft-release-dev.rpm
RUN dnf -y install clang cli11-devel ninja-build 'dnf-command(builddep)' libasan liblsan libtsan libubsan clang-tools-extra
RUN dnf -y builddep fairmq
RUN dnf -y clean all
# buildah build --build-arg "VERSION=36" -t "ghcr.io/fairrootgroup/fairmq-dev/fedora-36:latest" -f test/ci/Containerfile.fedora .
# echo $GH_PAT | buildah login -u dennisklein --password-stdin ghcr.io
# buildah push ghcr.io/fairrootgroup/fairmq-dev/fedora-36:latest
# apptainer pull docker://ghcr.io/fairrootgroup/fairmq-dev/fedora-36:latest
# echo $GH_PAT | apptainer remote login -u dennisklein --password-stdin oras://ghcr.io
# apptainer push ./fedora-36_latest.sif oras://ghcr.io/fairrootgroup/fairmq-dev/fedora-36-sif:latest

View File

@@ -1,36 +0,0 @@
ARG VERSION=latest
FROM ubuntu:${VERSION}
ARG VERSION=latest
LABEL org.opencontainers.image.source "https://github.com/FairRootGroup/FairMQ"
LABEL org.opencontainers.image.description "FairMQ development environment"
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get -y install ca-certificates patch cmake git libboost-dev libboost-log-dev libboost-system-dev libboost-regex-dev libboost-filesystem-dev libboost-container-dev libboost-thread-dev libboost-date-time-dev libboost-program-options-dev g++ libfmt-dev ninja-build wget libczmq-dev libxml2-utils libfabric-dev libfabric-bin pkg-config
RUN apt-get -y clean
RUN cd /tmp
RUN git clone -b v1.19.2 --recurse-submodules https://github.com/FairRootGroup/asio
RUN cmake -GNinja -S asio -B asio_build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release
RUN cmake --build asio_build --target install
RUN rm -rf asio asio_build
RUN git clone -b v1.0.0 https://github.com/FairRootGroup/FairCMakeModules
RUN cmake -GNinja -S FairCMakeModules -B FairCMakeModules_build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release
RUN cmake --build FairCMakeModules_build --target install
RUN rm -rf FairCMakeModules FairCMakeModules_build
RUN git clone -b v1.11.0 https://github.com/FairRootGroup/FairLogger
RUN cmake -GNinja -S FairLogger -B FairLogger_build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DUSE_EXTERNAL_FMT=ON
RUN cmake --build FairLogger_build --target install
RUN rm -rf FairLogger FairLogger_build
# buildah build --build-arg "VERSION=22.04" -t "ghcr.io/fairrootgroup/fairmq-dev/ubuntu-22.04:latest" -f test/ci/Containerfile.ubuntu .
# echo $GH_PAT | buildah login -u dennisklein --password-stdin ghcr.io
# buildah push ghcr.io/fairrootgroup/fairmq-dev/ubuntu-22.04:latest
# apptainer pull docker://ghcr.io/fairrootgroup/fairmq-dev/ubuntu-22.04:latest
# echo $GH_PAT | apptainer remote login -u dennisklein --password-stdin oras://ghcr.io
# apptainer push ./ubuntu-22.04_latest.sif oras://ghcr.io/fairrootgroup/fairmq-dev/ubuntu-22.04-sif:latest

View File

@@ -1,42 +0,0 @@
#! /bin/bash
label="$1"
jobsh="$2"
if [ -z "$ALFACI_SLURM_CPUS" ]
then
ALFACI_SLURM_CPUS=20
fi
CPUS_PER_JOB=$(($ALFACI_SLURM_CPUS / 2))
if [ -z "$ALFACI_SLURM_EXTRA_OPTS" ]
then
ALFACI_SLURM_EXTRA_OPTS="--hint=compute_bound"
fi
if [ -z "$ALFACI_SLURM_TIMEOUT" ]
then
ALFACI_SLURM_TIMEOUT=30
fi
if [ -z "$ALFACI_SLURM_QUEUE" ]
then
ALFACI_SLURM_QUEUE=main
fi
echo "*** Slurm request options :"
echo "*** Working directory ..: $PWD"
echo "*** Queue ..............: $ALFACI_SLURM_QUEUE"
echo "*** CPUs ...............: $ALFACI_SLURM_CPUS"
echo "*** Wall Time ..........: $ALFACI_SLURM_TIMEOUT min"
echo "*** Job Name ...........: ${label}"
echo "*** Extra Options ......: ${ALFACI_SLURM_EXTRA_OPTS}"
echo "*** Submitting job at ....: $(date -R)"
(
set -x
srun -p $ALFACI_SLURM_QUEUE -c $CPUS_PER_JOB -n 1 \
-t $ALFACI_SLURM_TIMEOUT \
--job-name="${label}" \
${ALFACI_SLURM_EXTRA_OPTS} \
bash "${jobsh}"
)
retval=$?
echo "*** Exit Code ............: $retval"
exit "$retval"

View File

@@ -0,0 +1,21 @@
spack:
specs:
- "fairlogger@2.2.0 ^fmt@:11"
- "boost@1.87 +container +program_options +filesystem +date_time +regex"
- "libzmq@4.1.4:"
- ninja
- "cmake@3.15:"
concretizer:
targets:
granularity: generic
packages:
all:
require:
- target=x86_64_v3
mirrors:
ghcr-buildcache:
url: oci://ghcr.io/fairrootgroup/fairmq-spack-buildcache
signed: false

21
test/ci/spack-latest.yaml Normal file
View File

@@ -0,0 +1,21 @@
spack:
specs:
- "fairlogger@2.2.0 ^fmt@:11"
- "boost@1.66: +container +program_options +filesystem +date_time +regex"
- "libzmq@4.1.4:"
- ninja
- "cmake@3.15:"
concretizer:
targets:
granularity: generic
packages:
all:
require:
- target=x86_64_v3
mirrors:
ghcr-buildcache:
url: oci://ghcr.io/fairrootgroup/fairmq-spack-buildcache
signed: false

47
test/ci/spack-tsan.yaml Normal file
View File

@@ -0,0 +1,47 @@
spack:
specs:
# fairlogger and boost are left uninstrumented on purpose: no tsan report
# has ever implicated them. Should that change, instrument them with
# per-node flags like libzmq below.
- "fairlogger@2.2.0 ^fmt@:11"
- "boost@1.66: +container +program_options +filesystem +date_time +regex"
# libzmq (and the libsodium it links) are built with ThreadSanitizer
# instrumentation so tsan can observe the happens-before edges that
# libzmq's lock-free queues establish between user and I/O threads.
# Flags are set per node on purpose: the propagating '==' operator could
# reach the gcc node (the compiler is a dependency since spack 1.0) and
# trigger a full compiler rebuild.
- "libzmq@4.1.4: cflags='-g -fno-omit-frame-pointer -fsanitize=thread'
cxxflags='-g -fno-omit-frame-pointer -fsanitize=thread'
ldflags='-fsanitize=thread'
^libsodium cflags='-g -fno-omit-frame-pointer -fsanitize=thread'
ldflags='-fsanitize=thread'"
# ThreadSanitizer-instrumented libstdc++ (from test/ci/spack_repo), a
# drop-in runtime replacement injected per test via LD_LIBRARY_PATH (see
# FAIRMQ_TEST_LD_LIBRARY_PATH in test/CMakeLists.txt).
# Keep the major version in lockstep with the gcc used by the tsan job.
- "libstdcxx-tsan@15"
- ninja
# the per-test env injection uses ctest's ENVIRONMENT_MODIFICATION,
# which needs cmake >= 3.22
- "cmake@3.22:"
concretizer:
targets:
granularity: generic
reuse:
# always re-concretize libstdcxx-tsan from the recipe in
# test/ci/spack_repo: spec reuse would keep resurrecting previously
# built (and cached) variants after the recipe changed; unchanged
# recipes still hit the buildcache because the hash is identical
exclude: ["libstdcxx-tsan"]
packages:
all:
require:
- target=x86_64_v3
mirrors:
ghcr-buildcache:
url: oci://ghcr.io/fairrootgroup/fairmq-spack-buildcache
signed: false

View File

@@ -0,0 +1,90 @@
import os
import shutil
from spack_repo.builtin.build_systems.generic import Package
from spack.package import *
class LibstdcxxTsan(Package):
"""ThreadSanitizer-instrumented build of GCC's libstdc++.
GCC ships no supported way to produce a tsan-instrumented libstdc++ (and
spack's gcc package offers no hook into the target-library flags), so this
package builds only the libstdc++-v3 subtree from the GCC release tarball
matching the toolchain, with -fsanitize=thread. The result is a drop-in
runtime replacement for the compiler's own libstdc++ (same soname and
symbol versions) that lets tsan observe the synchronization inside the
C++ standard library (e.g. std::locale/std::regex lazy initialization).
Select it via LD_LIBRARY_PATH in the environment of the instrumented
executables only. Like any shared library built with gcc
-fsanitize=thread it carries unresolved __tsan_* symbols satisfied by the
executable's libtsan, so loading it into an uninstrumented executable
fails.
Recipe modeled on https://iree.dev/developers/debugging/sanitizers/.
"""
homepage = "https://gcc.gnu.org/onlinedocs/libstdc++/"
url = "https://ftp.gnu.org/gnu/gcc/gcc-15.2.0/gcc-15.2.0.tar.xz"
# Keep in lockstep with the gcc version used by the tsan CI job: a
# libstdc++ older than the compiler may lack GLIBCXX symbol versions
# that binaries built by that compiler reference. Checksums match the
# version() directives in spack's gcc package.
version("15.2.0", sha256="438fd996826b0c82485a29da03a72d71d6e3541a83ec702df4271f6fe025d24e")
version("14.3.0", sha256="e0dc77297625631ac8e50fa92fffefe899a4eb702592da5c32ef04e2293aca3a")
depends_on("c", type="build")
depends_on("cxx", type="build")
depends_on("gmake", type="build")
def install(self, spec, prefix):
# gthr-default.h only materializes in a configured libgcc build dir;
# on POSIX targets it is a verbatim copy of gthr-posix.h. Providing it
# up front spares building libgcc (and the in-tree compiler).
gthr_include = join_path(self.stage.source_path, "spack-gthr")
mkdirp(gthr_include)
copy(
join_path(self.stage.source_path, "libgcc", "gthr-posix.h"),
join_path(gthr_include, "gthr-default.h"),
)
flags = f"-I{gthr_include} -O2 -g -fno-omit-frame-pointer -fsanitize=thread"
build_dir = join_path(self.stage.source_path, "spack-build")
mkdirp(build_dir)
with working_dir(build_dir):
configure = Executable(
join_path(self.stage.source_path, "libstdc++-v3", "configure")
)
configure(
f"--prefix={prefix}",
f"--libdir={prefix.lib}",
"--disable-multilib",
"--disable-libstdcxx-pch",
"--enable-libstdcxx-threads=yes",
"--with-default-libstdcxx-abi=new",
f"CFLAGS={flags}",
f"CXXFLAGS={flags}",
"LDFLAGS=-fsanitize=thread",
)
make()
make("install")
# The runtime libraries land in the multilib os dir (lib64 on
# x86_64), --libdir notwithstanding (--with-toolexeclibdir only
# applies to cross builds); normalize to lib/ for the consumer.
if os.path.isdir(prefix.lib64):
mkdirp(prefix.lib)
for entry in os.listdir(prefix.lib64):
shutil.move(join_path(prefix.lib64, entry), join_path(prefix.lib, entry))
os.rmdir(prefix.lib64)
# Only the runtime library is consumed -- compilation uses the
# compiler's own (identical) headers. Drop the rest to keep the
# buildcache entry small.
for directory in ("include", "share"):
path = join_path(prefix, directory)
if os.path.isdir(path):
shutil.rmtree(path)

View File

@@ -0,0 +1,3 @@
repo:
namespace: fairmq_ci
api: v2.0

View File

@@ -9,7 +9,11 @@
#include "runner.h"
#include <gtest/gtest.h>
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
#include <boost/process/v1.hpp>
#else
#include <boost/process.hpp>
#endif
#include <fairmq/tools/Process.h>
#include <fairmq/tools/Unique.h>
#include <fairmq/Device.h>

View File

@@ -9,7 +9,11 @@
#include "runner.h"
#include <gtest/gtest.h>
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
#include <boost/process/v1.hpp>
#else
#include <boost/process.hpp>
#endif
#include <fairmq/tools/Process.h>
#include <fairmq/tools/Unique.h>

View File

@@ -9,7 +9,11 @@
#include "runner.h"
#include <gtest/gtest.h>
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
#include <boost/process/v1.hpp>
#else
#include <boost/process.hpp>
#endif
#include <fairmq/tools/Unique.h>
#include <fairmq/tools/Process.h>

View File

@@ -29,13 +29,17 @@ TEST(EventManager, Basics)
std::function<void(typename TestEvent::KeyType, int)> callback{
[&](TestEvent::KeyType key, int newValue){
++call_counter;
if (key == "test") value = newValue;
if (key == "test") {
value = newValue;
}
}
};
std::function<void(typename TestEvent::KeyType, string)> callback2{
[&](TestEvent::KeyType key, string newValue){
++call_counter2;
if (key == "test") value2 = newValue;
if (key == "test") {
value2 = newValue;
}
}
};

View File

@@ -0,0 +1,63 @@
/********************************************************************************
* Copyright (C) 2026 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" *
********************************************************************************/
#ifndef FAIR_MQ_TEST_LOCALEWARMUP_H
#define FAIR_MQ_TEST_LOCALEWARMUP_H
#include <locale>
#include <sstream>
namespace fair::mq::test {
/// libstdc++'s std::ctype<char> caches narrow()/widen() results per character
/// in plain char arrays of the global classic-locale facet -- by design with
/// unsynchronized writes, emitted from header-inlined code (locale_facets.h).
/// Whenever two threads exercise an uncached character concurrently (e.g. by
/// compiling a std::regex), ThreadSanitizer rightfully reports the write as a
/// data race. The stores are real and unsynchronized, so instrumenting
/// libstdc++ cannot help; instead, fill the caches before any thread exists,
/// which turns every later access into a pure read.
inline void WarmUpLocaleCaches()
{
auto const& ct = std::use_facet<std::ctype<char>>(std::locale::classic());
for (int c = 1; c < 256; ++c) {
auto const ch = static_cast<char>(c);
ct.narrow(ch, '\0');
ct.widen(ch);
}
// Only the range overload runs _M_narrow_init(), which sets the
// all-cached flag (_M_narrow_ok); the single-char loop above fills the
// cache without it. (widen() needs no equivalent: the single-char
// overload already runs _M_widen_init().)
char from[256];
char to[256];
for (int c = 0; c < 256; ++c) {
from[c] = static_cast<char>(c);
}
ct.narrow(from, from + 256, '?', to);
// num_put/num_get caches used by stream insertion/extraction
std::ostringstream os;
os << 4711 << ' ' << 3.14;
std::istringstream is("42 3.14");
int i = 0;
double d = 0.;
is >> i >> d;
}
/// For binaries that do not own main(): define a namespace-scope instance to
/// run the warm-up during static initialization, before main().
struct LocaleWarmup
{
LocaleWarmup() { WarmUpLocaleCaches(); }
};
} // namespace fair::mq::test
#endif /* FAIR_MQ_TEST_LOCALEWARMUP_H */

View File

@@ -6,6 +6,7 @@
* copied verbatim in the file "LICENSE" *
********************************************************************************/
#include "LocaleWarmup.h"
#include "devices/TestPairLeft.h"
#include "devices/TestPairRight.h"
#include "devices/TestPollIn.h"
@@ -30,6 +31,10 @@
namespace bpo = boost::program_options;
namespace {
[[maybe_unused]] fair::mq::test::LocaleWarmup const gLocaleWarmup{};
}
auto addCustomOptions(bpo::options_description& options) -> void
{
options.add_options()

View File

@@ -1 +1,2 @@
leak:zmq_msg_init_size
leak:zmq::msg_t::init_size

View File

@@ -16,6 +16,7 @@
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include <vector>
namespace
@@ -101,7 +102,7 @@ TEST(MemoryResources, allocator)
size_t session{tools::UuidHash()};
ProgOptions config;
config.SetProperty<string>("session", to_string(session));
config.SetProperty<std::string>("session", to_string(session));
FactoryType factoryZMQ = TransportFactory::CreateTransportFactory("zeromq", fair::mq::tools::Uuid(), &config);
@@ -129,7 +130,7 @@ TEST(MemoryResources, getMessage)
size_t session{tools::UuidHash()};
ProgOptions config;
config.SetProperty<string>("session", to_string(session));
config.SetProperty<std::string>("session", to_string(session));
config.SetProperty<bool>("shm-monitor", true);
FactoryType factoryZMQ = TransportFactory::CreateTransportFactory("zeromq", fair::mq::tools::Uuid(), &config);

View File

@@ -50,7 +50,7 @@ auto RunPushPullWithMsgResize(string const & transport, string const & _address,
Channel push{"Push", "push", factory};
Channel pull{"Pull", "pull", factory};
auto const address(tools::ToString(_address, "_", transport));
auto const address(tools::ToString(_address, "_", transport, "_", config.GetProperty<string>("session")));
push.Bind(address);
pull.Connect(address);
@@ -153,7 +153,7 @@ auto RunPushPullWithAlignment(string const& transport, string const& _address, b
Channel push{"Push", "push", factory};
Channel pull{"Pull", "pull", factory};
auto const address(tools::ToString(_address, "_", transport));
auto const address(tools::ToString(_address, "_", transport, "_", config.GetProperty<string>("session")));
push.Bind(address);
pull.Connect(address);
@@ -211,7 +211,7 @@ auto EmptyMessage(string const& transport, string const& _address, bool expanded
Channel push{"Push", "push", factory};
Channel pull{"Pull", "pull", factory};
auto const address(tools::ToString(_address, "_", transport));
auto const address(tools::ToString(_address, "_", transport, "_", config.GetProperty<string>("session")));
push.Bind(address);
pull.Connect(address);
@@ -287,8 +287,10 @@ auto ZeroCopy(bool expandedShmMetadata = false) -> void
// The "zero copy" property of the Copy() method is an implementation detail and is not guaranteed.
// Currently it holds true for the shmem (across devices) and for zeromq (within same device) transports.
auto ZeroCopyFromUnmanaged(string const& address, bool expandedShmMetadata = false) -> void
auto ZeroCopyFromUnmanaged(string const& address, bool expandedShmMetadata, uint64_t rcSegmentSize) -> void
{
fair::Logger::SetConsoleSeverity(fair::Severity::debug);
ProgOptions config1;
ProgOptions config2;
string session(tools::Uuid());
@@ -311,18 +313,20 @@ auto ZeroCopyFromUnmanaged(string const& address, bool expandedShmMetadata = fal
const size_t msgSize{100};
const size_t regionSize{1000000};
RegionConfig cfg;
cfg.rcSegmentSize = rcSegmentSize;
tools::Semaphore blocker;
auto region = factory1->CreateUnmanagedRegion(regionSize, [&blocker](void*, size_t, void*) {
blocker.Signal();
});
}, cfg);
{
Channel push("Push", "push", factory1);
Channel pull("Pull", "pull", factory2);
push.Bind(address);
pull.Connect(address);
push.Bind(address + "_" + session);
pull.Connect(address + "_" + session);
const size_t offset = 100;
auto msg1(push.NewMessage(region, static_cast<char*>(region->GetData()), msgSize, nullptr));
@@ -449,6 +453,46 @@ TEST(EmptyMessage, shmem_expanded_metadata) // NOLINT
EmptyMessage("shmem", "ipc://test_empty_message", true);
}
// GetMeta() + GetDataAddressFromHandle() round-trip: simulates a consumer device that
// receives message metadata over a side channel and resolves it to a local pointer.
// Uses a second factory on the same session to exercise the open_only segment attach path.
auto SideChannel(bool expandedShmMetadata = false) -> void
{
const string session = tools::Uuid();
ProgOptions producerConfig;
producerConfig.SetProperty<string>("session", session);
producerConfig.SetProperty<size_t>("shm-segment-size", 100000000);
producerConfig.SetProperty<bool>("shm-monitor", true);
if (expandedShmMetadata) {
producerConfig.SetProperty<size_t>("shm-metadata-msg-size", 2048);
}
auto producerFactory(TransportFactory::CreateTransportFactory("shmem", tools::Uuid(), &producerConfig));
ProgOptions consumerConfig;
consumerConfig.SetProperty<string>("session", session);
consumerConfig.SetProperty<size_t>("shm-segment-size", 100000000);
consumerConfig.SetProperty<bool>("shm-monitor", true);
auto consumerFactory(TransportFactory::CreateTransportFactory("shmem", tools::Uuid(), &consumerConfig));
const size_t size = 2;
MessagePtr msg(producerFactory->CreateMessage(size));
memcpy(msg->GetData(), "AB", size);
// producer side: extract metadata to send over the side channel
const auto meta = static_cast<const shmem::Message&>(*msg).GetMeta();
EXPECT_EQ(meta.fSize, size);
EXPECT_TRUE(meta.fManaged);
// consumer side: resolve via a different factory — exercises open_only segment attach.
// The virtual address differs between factory mappings; only the content must match.
char* ptr = shmem::GetDataAddressFromHandle(*consumerFactory, meta);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(ptr[0], 'A');
EXPECT_EQ(ptr[1], 'B');
}
TEST(ZeroCopy, shmem) // NOLINT
{
ZeroCopy();
@@ -459,14 +503,111 @@ TEST(ZeroCopy, shmem_expanded_metadata) // NOLINT
ZeroCopy(true);
}
// Uses a second factory on the same session to exercise the remote region lookup path.
auto SideChannelUnmanaged() -> void
{
const string session = tools::Uuid();
ProgOptions producerConfig;
producerConfig.SetProperty<string>("session", session);
producerConfig.SetProperty<size_t>("shm-segment-size", 100000000);
producerConfig.SetProperty<bool>("shm-monitor", true);
auto producerFactory(TransportFactory::CreateTransportFactory("shmem", tools::Uuid(), &producerConfig));
ProgOptions consumerConfig;
consumerConfig.SetProperty<string>("session", session);
consumerConfig.SetProperty<size_t>("shm-segment-size", 100000000);
consumerConfig.SetProperty<bool>("shm-monitor", true);
auto consumerFactory(TransportFactory::CreateTransportFactory("shmem", tools::Uuid(), &consumerConfig));
const size_t regionSize = 1000000;
tools::Semaphore blocker;
auto region = producerFactory->CreateUnmanagedRegion(regionSize, [&blocker](void*, size_t, void*) {
blocker.Signal();
});
const size_t size = 2;
auto msg(producerFactory->CreateMessage(region, static_cast<char*>(region->GetData()), size, nullptr));
memcpy(msg->GetData(), "AB", size);
const auto meta = static_cast<const shmem::Message&>(*msg).GetMeta();
EXPECT_EQ(meta.fSize, size);
EXPECT_FALSE(meta.fManaged);
// consumer resolves via its own factory — exercises remote region lookup.
// The virtual address differs between factory mappings; only the content must match.
char* ptr = shmem::GetDataAddressFromHandle(*consumerFactory, meta);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(ptr[0], 'A');
EXPECT_EQ(ptr[1], 'B');
msg.reset();
blocker.Wait();
}
auto SideChannelErrors() -> void
{
ProgOptions config;
config.SetProperty<string>("session", tools::Uuid());
config.SetProperty<size_t>("shm-segment-size", 100000000);
config.SetProperty<bool>("shm-monitor", true);
auto shmFactory(TransportFactory::CreateTransportFactory("shmem", tools::Uuid(), &config));
auto zmqFactory(TransportFactory::CreateTransportFactory("zeromq", tools::Uuid(), &config));
// non-shmem factory must throw
shmem::MetaHeader meta{};
meta.fManaged = true;
EXPECT_THROW(shmem::GetDataAddressFromHandle(*zmqFactory, meta), shmem::SharedMemoryError);
// bad segment id must throw
meta.fSize = 1;
meta.fSegmentId = 999;
EXPECT_THROW(shmem::GetDataAddressFromHandle(*shmFactory, meta), shmem::SharedMemoryError);
// zero-size managed message must return nullptr (mirrors Message::GetData())
meta.fSize = 0;
EXPECT_EQ(shmem::GetDataAddressFromHandle(*shmFactory, meta), nullptr);
}
TEST(SideChannel, shmem) // NOLINT
{
SideChannel();
}
TEST(SideChannel, shmem_expanded_metadata) // NOLINT
{
SideChannel(true);
}
TEST(SideChannel, shmem_unmanaged) // NOLINT
{
SideChannelUnmanaged();
}
TEST(SideChannel, errors) // NOLINT
{
SideChannelErrors();
}
TEST(ZeroCopyFromUnmanaged, shmem) // NOLINT
{
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged");
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged", false, 10000000);
}
TEST(ZeroCopyFromUnmanaged, shmem_expanded_metadata) // NOLINT
{
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged", true);
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged_expanded", true, 10000000);
}
TEST(ZeroCopyFromUnmanaged, shmem_no_rc_segment) // NOLINT
{
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged_no_rc_segment", false, 0);
}
TEST(ZeroCopyFromUnmanaged, shmem_expanded_metadata_no_rc_segment) // NOLINT
{
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged_expanded_no_rc_segment", true, 0);
}
} // namespace

View File

@@ -1,5 +1,5 @@
/********************************************************************************
* Copyright (C) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2017-2023 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
@@ -7,8 +7,12 @@
********************************************************************************/
#include "Fixture.h"
#include <array>
#include <condition_variable>
#include <fairmq/Tools.h>
#include <memory>
#include <mutex>
#include <string>
namespace
{
@@ -142,4 +146,27 @@ TEST_F(PluginServices, ControlStateTransitionConversions)
EXPECT_NO_THROW(mServices.ToStr(DeviceStateTransition::ErrorFound));
}
TEST_F(PluginServices, SubscriptionThreadSafety)
{
// obviously not a perfect test, but I could segfault fmq reliably with it (without the fix)
constexpr auto attempts = 1000;
constexpr auto subscribers = 5;
std::array<std::unique_ptr<std::thread>, subscribers> threads;
auto id = 0;
for (auto& thread : threads) {
thread = std::make_unique<std::thread>([&, id](){
auto const subscriber = fair::mq::tools::ToString("subscriber_", id);
for (auto i = 0; i < attempts; ++i) {
mServices.SubscribeToDeviceStateChange(subscriber, [](DeviceState){});
mServices.UnsubscribeFromDeviceStateChange(subscriber);
}
});
++id;
}
for (auto& thread : threads) { thread->join(); }
}
} /* namespace */

View File

@@ -35,6 +35,9 @@ auto RunSingleThreadedMultipart(string transport, string address1, string addres
config.SetProperty<size_t>("shm-metadata-msg-size", 2048);
}
address1 += "_" + config.GetProperty<string>("session");
address2 += "_" + config.GetProperty<string>("session");
auto factory = TransportFactory::CreateTransportFactory(transport, tools::Uuid(), &config);
Channel push1("Push1", "push", factory);
@@ -118,6 +121,8 @@ auto RunMultiThreadedMultipart(string transport, string address1, bool expandedS
config.SetProperty<size_t>("shm-metadata-msg-size", 2048);
}
address1 += "_" + config.GetProperty<string>("session");
auto factory = TransportFactory::CreateTransportFactory(transport, tools::Uuid(), &config);
Channel push1("Push1", "push", factory);
@@ -210,7 +215,7 @@ TEST(PushPull, Multipart_MultiThreaded_ipc_shmem) // NOLINT
TEST(PushPull, Multipart_MultiThreaded_ipc_shmem_expanded_metadata) // NOLINT
{
RunMultiThreadedMultipart("shmem", "ipc://test_Multipart_MultiThreaded_ipc_shmem_1", true);
RunMultiThreadedMultipart("shmem", "ipc://test_Multipart_MultiThreaded_ipc_shmem__expanded_metadata_1", true);
}
} // namespace

View File

@@ -10,6 +10,7 @@
#include <TestEnvironment.h>
#include <gtest/gtest.h>
#include <helper/LocaleWarmup.h>
#include <string>
@@ -24,6 +25,7 @@ string runTestDevice = "@RUN_TEST_DEVICE@";
int main(int argc, char** argv)
{
fair::mq::test::WarmUpLocaleCaches();
::testing::InitGoogleTest(&argc, argv);
::testing::FLAGS_gtest_death_test_style = "threadsafe";
setenv("FAIRMQ_PATH", FAIRMQ_TEST_ENVIRONMENT, 0);

View File

@@ -1,28 +1,37 @@
/********************************************************************************
* Copyright (C) 2018 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* Copyright (C) 2018-2025 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
* *
* This software is distributed under the terms of the *
* GNU Lesser General Public Licence (LGPL) version 3, *
* copied verbatim in the file "LICENSE" *
********************************************************************************/
#include <gtest/gtest.h>
#include <fairmq/tools/Network.h>
#include <gtest/gtest.h>
#include <string>
namespace
{
using namespace std;
using namespace fair::mq;
TEST(Tools, Network)
TEST(Tools, NetworkDefaultIP)
{
string interface = fair::mq::tools::getDefaultRouteNetworkInterface();
auto const interface = fair::mq::tools::getDefaultRouteNetworkInterface();
EXPECT_NE(interface, "");
string interfaceIP = fair::mq::tools::getInterfaceIP(interface);
auto const interfaceIP = fair::mq::tools::getInterfaceIP(interface);
EXPECT_NE(interfaceIP, "");
}
TEST(Tools, NetworkIPv4Localhost)
{
auto const ip = fair::mq::tools::getIpFromHostname("localhost");
EXPECT_FALSE(ip.empty());
EXPECT_EQ(ip, "127.0.0.1");
}
TEST(Tools, NetworkInvalidHostname)
{
auto const ip = fair::mq::tools::getIpFromHostname("non.existent.domain.invalid");
EXPECT_TRUE(ip.empty());
}
} /* namespace */