Compare commits

..

5 Commits

Author SHA1 Message Date
Alexey Rybalchenko
d322d97d4a f 2025-08-26 11:59:20 +02:00
Alexey Rybalchenko
4176376b21 f 2025-08-26 11:56:49 +02:00
Alexey Rybalchenko
75b1208af0 f 2025-08-26 11:12:28 +02:00
Alexey Rybalchenko
e18140e110 f 2025-08-26 11:11:30 +02:00
Alexey Rybalchenko
6f9b72a27f Add test workflow 2025-08-26 11:02:11 +02:00
63 changed files with 460 additions and 992 deletions

View File

@@ -1,3 +1,3 @@
--- ---
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' 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'
HeaderFilterRegex: '/(fairmq/)' HeaderFilterRegex: '/(fairmq/)'

View File

@@ -1,78 +0,0 @@
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::"

View File

@@ -1,82 +0,0 @@
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

View File

@@ -22,7 +22,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: Try updating metadata - name: Try updating metadata
run: python meta_update.py run: python meta_update.py
- name: Check for Updates - name: Check for Updates

View File

@@ -1,209 +0,0 @@
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: container:
image: gitlab-registry.in2p3.fr/escape2020/wp3/eossr:v1.0 image: gitlab-registry.in2p3.fr/escape2020/wp3/eossr:v1.0
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- name: validate codemeta - name: validate codemeta
run: eossr-metadata-validator codemeta.json run: eossr-metadata-validator codemeta.json

70
.github/workflows/test-macos-runner.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: Test macOS Self-Hosted Runner
on:
workflow_dispatch:
push:
branches: [ dev, master ]
pull_request:
branches: [ dev, master ]
jobs:
test-runner:
runs-on: [self-hosted, macOS-15]
timeout-minutes: 120
steps:
- name: Setup environment
run: |
echo "Setting up PATH for Homebrew..."
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
echo "PATH=$PATH" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
- name: System information
run: |
echo "Runner information:"
uname -a
sw_vers
echo "CPU info:"
sysctl -n machdep.cpu.brand_string
echo "Memory info:"
system_profiler SPHardwareDataType | grep "Memory:"
echo "Disk space:"
df -h
- name: Check development tools
run: |
echo "Xcode tools version:"
xcode-select -p
clang --version
echo "CMake version:"
cmake --version || echo "CMake not installed"
echo "Git version:"
git --version
echo "Available SDKs:"
xcodebuild -showsdks || echo "Xcode not fully installed"
- name: Test basic compilation
run: |
echo "Testing basic C++ compilation:"
cat > test.cpp << 'EOF'
#include <iostream>
int main() {
std::cout << "Hello from macOS 15 UTM runner!" << std::endl;
return 0;
}
EOF
clang++ -o test_cpp test.cpp
./test_cpp
- name: Check FairMQ dependencies
run: |
echo "Checking potential FairMQ build dependencies:"
brew --version || echo "Homebrew not installed"
pkg-config --version || echo "pkg-config not available"
echo "Looking for common HEP libraries..."
find /usr/local /opt -name "*root*" -type d 2>/dev/null | head -5 || echo "No ROOT installation found"

View File

@@ -46,13 +46,6 @@ include(FairMQDependencies)
# Targets ###################################################################### # 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) if(BUILD_FAIRMQ)
add_subdirectory(fairmq) add_subdirectory(fairmq)
endif() endif()

93
FairMQTest.cmake Normal file
View File

@@ -0,0 +1,93 @@
################################################################################
# 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()

118
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,118 @@
#!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 = "--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: 'ubuntu', ver: '24.04', arch: 'x86_64', compiler: 'gcc-13'],
[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: 'fedora', ver: '39', arch: 'x86_64', compiler: 'gcc-13'],
[os: 'fedora', ver: '40', arch: 'x86_64', compiler: 'gcc-14'],
[os: 'macos', ver: '14', arch: 'x86_64', compiler: 'apple-clang-16'],
[os: 'macos', ver: '15', arch: 'x86_64', compiler: 'apple-clang-16'],
[os: 'macos', ver: '15', arch: 'arm64', compiler: 'apple-clang-16'],
])
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

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

View File

@@ -23,18 +23,6 @@ if(BUILD_FAIRMQ OR BUILD_TIDY_TOOL)
find_package2(PUBLIC Boost REQUIRED VERSION 1.66 find_package2(PUBLIC Boost REQUIRED VERSION 1.66
COMPONENTS container program_options filesystem date_time regex 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() endif()
if(BUILD_FAIRMQ) if(BUILD_FAIRMQ)

View File

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

View File

@@ -12,22 +12,6 @@ set(test_script_prefix "test-ex")
set(testsuite "Example") set(testsuite "Example")
set(transports "zeromq" "shmem") 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) function(add_example)
cmake_parse_arguments(PARSE_ARGV 0 ARG cmake_parse_arguments(PARSE_ARGV 0 ARG
"CONFIG;NO_TRANSPORT;NO_TEST" "CONFIG;NO_TRANSPORT;NO_TEST"
@@ -45,6 +29,11 @@ function(add_example)
message(FATAL_ERROR "NAME arg is required") message(FATAL_ERROR "NAME arg is required")
endif() 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) if(ARG_DEVICE)
set(exe_targets) set(exe_targets)
foreach(device IN LISTS ARG_DEVICE) foreach(device IN LISTS ARG_DEVICE)
@@ -89,8 +78,8 @@ function(add_example)
set(test "${testsuite}.${name}.${transport}") set(test "${testsuite}.${name}.${transport}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport}) add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport})
set_tests_properties(${test} PROPERTIES TIMEOUT "30") set_tests_properties(${test} PROPERTIES TIMEOUT "30")
if(env_mods) if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}") set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
endif() endif()
else() else()
foreach(transport IN LISTS transports) foreach(transport IN LISTS transports)
@@ -99,16 +88,16 @@ function(add_example)
set(test "${testsuite}.${name}.${variant}.${transport}") set(test "${testsuite}.${name}.${variant}.${transport}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport} ${variant}) add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport} ${variant})
set_tests_properties(${test} PROPERTIES TIMEOUT "30") set_tests_properties(${test} PROPERTIES TIMEOUT "30")
if(env_mods) if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}") set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
endif() endif()
endforeach() endforeach()
else() else()
set(test "${testsuite}.${name}.${transport}") set(test "${testsuite}.${name}.${transport}")
add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport}) add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${test_script} ${transport})
set_tests_properties(${test} PROPERTIES TIMEOUT "30") set_tests_properties(${test} PROPERTIES TIMEOUT "30")
if(env_mods) if(lsan_options)
set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION "${env_mods}") set_tests_properties(${test} PROPERTIES ENVIRONMENT_MODIFICATION ${lsan_options})
endif() endif()
endif() endif()
endforeach() endforeach()

View File

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

View File

@@ -176,9 +176,6 @@ if(BUILD_FAIRMQ)
FAIRMQ_HAS_STD_FILESYSTEM=${FAIRMQ_HAS_STD_FILESYSTEM} FAIRMQ_HAS_STD_FILESYSTEM=${FAIRMQ_HAS_STD_FILESYSTEM}
FAIRMQ_HAS_STD_PMR=${FAIRMQ_HAS_STD_PMR} 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) if(DEFINED FAIRMQ_CHANNEL_DEFAULT_AUTOBIND)
# translate CMake boolean (TRUE, FALSE, 0, 1, OFF, ON) into C++ boolean literal (true, false) # translate CMake boolean (TRUE, FALSE, 0, 1, OFF, ON) into C++ boolean literal (true, false)
if(FAIRMQ_CHANNEL_DEFAULT_AUTOBIND) if(FAIRMQ_CHANNEL_DEFAULT_AUTOBIND)

View File

@@ -181,8 +181,7 @@ try {
// validate channel name // validate channel name
smatch m; smatch m;
static regex const invalidName(R"([^a-zA-Z0-9\-_\[\]#])"); if (regex_search(fName, m, regex("[^a-zA-Z0-9\\-_\\[\\]#]"))) {
if (regex_search(fName, m, invalidName)) {
ss << "INVALID"; ss << "INVALID";
LOG(debug) << ss.str(); LOG(debug) << ss.str();
LOG(error) << "channel name contains illegal character: '" << m.str(0) << "', allowed characters are: a-z, A-Z, 0-9, -, _, [, ], #"; LOG(error) << "channel name contains illegal character: '" << m.str(0) << "', allowed characters are: a-z, A-Z, 0-9, -, _, [, ], #";

View File

@@ -513,8 +513,7 @@ void Device::HandleMultipleTransportInput()
fMultitransportProceed = true; fMultitransportProceed = true;
for (const auto& i : fMultitransportInputs) { for (const auto& i : fMultitransportInputs) {
threads.emplace_back( threads.emplace_back(thread(&Device::PollForTransport, this, fTransports.at(i.first).get(), i.second));
&Device::PollForTransport, this, fTransports.at(i.first).get(), i.second);
} }
for (thread& t : threads) { for (thread& t : threads) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -189,7 +189,7 @@ vector<string> ProgOptions::GetPropertyKeys() const
vector<string> keys; vector<string> keys;
for (const auto& it : fVarMap) { for (const auto& it : fVarMap) {
keys.emplace_back(it.first.c_str()); keys.push_back(it.first.c_str());
} }
return keys; return keys;

View File

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

View File

@@ -135,7 +135,7 @@ struct RegionConfig
int64_t userFlags = 0; /// custom flags that have no effect on the transport, but can be retrieved from the region by the user 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 uint64_t size = 0; /// region size
uint64_t rcSegmentSize = 100000000; /// size of the segment that stores reference counts when "soft"-copying the messages 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::string path = ""; /// file path, if the region is backed by a file
std::optional<uint16_t> id = std::nullopt; /// region id std::optional<uint16_t> id = std::nullopt; /// region id
uint32_t linger = 100; /// delay in ms before region destruction to collect outstanding events uint32_t linger = 100; /// delay in ms before region destruction to collect outstanding events
}; };

View File

@@ -180,6 +180,7 @@ struct RegionInfo
, fUserFlags(userFlags) , fUserFlags(userFlags)
, fSize(size) , fSize(size)
, fRCSegmentSize(rcSegmentSize) , fRCSegmentSize(rcSegmentSize)
, fDestroyed(false)
{} {}
Str fPath; Str fPath;
@@ -187,7 +188,7 @@ struct RegionInfo
uint64_t fUserFlags; uint64_t fUserFlags;
uint64_t fSize; uint64_t fSize;
uint64_t fRCSegmentSize; uint64_t fRCSegmentSize;
bool fDestroyed{false}; bool fDestroyed;
}; };
using Uint16RegionInfoPairAlloc = boost::interprocess::allocator<std::pair<const uint16_t, RegionInfo>, SegmentManager>; using Uint16RegionInfoPairAlloc = boost::interprocess::allocator<std::pair<const uint16_t, RegionInfo>, SegmentManager>;
@@ -339,13 +340,4 @@ struct SegmentBufferShrink
} // namespace fair::mq::shmem } // namespace fair::mq::shmem
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_ */ #endif /* FAIR_MQ_SHMEM_COMMON_H_ */

View File

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

View File

@@ -390,10 +390,8 @@ class Manager
} }
auto* lRegion = GetRegion(id); auto* lRegion = GetRegion(id);
if (lRegion) { fTlRegionCache.fRegionsTLCache.emplace_back(std::make_tuple(lRegion, id, fShmId64));
fTlRegionCache.fRegionsTLCache.emplace_back(lRegion, id, fShmId64);
fTlRegionCache.fRegionsTLCacheGen = fRegionsGen; fTlRegionCache.fRegionsTLCacheGen = fRegionsGen;
}
return lRegion; return lRegion;
} }
@@ -778,30 +776,6 @@ class Manager
auto GetMetadataMsgSize() const noexcept { return fMetadataMsgSize; } 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() ~Manager()
{ {
fRegionsGen += 1; // signal TL cache invalidation fRegionsGen += 1; // signal TL cache invalidation

View File

@@ -77,12 +77,7 @@ class Message final : public fair::mq::Message
fManager.IncrementMsgCounter(); fManager.IncrementMsgCounter();
} }
Message(Manager& manager, Message(Manager& manager, UnmanagedRegionPtr& region, void* data, const size_t size, void* hint = 0, fair::mq::TransportFactory* factory = nullptr)
UnmanagedRegionPtr& region,
void* data,
const size_t size,
void* hint = nullptr,
fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory) : fair::mq::Message(factory)
, fManager(manager) , fManager(manager)
, fLocalPtr(static_cast<char*>(data)) , fLocalPtr(static_cast<char*>(data))
@@ -167,11 +162,6 @@ class Message final : public fair::mq::Message
} }
} }
MetaHeader GetMeta() const
{
return {fSize, fHint, fHandle, fShared, fRegionId, fSegmentId, fManaged};
}
void* GetData() const override void* GetData() const override
{ {
if (!fLocalPtr) { if (!fLocalPtr) {

View File

@@ -59,18 +59,12 @@ void signalHandler(int signal)
gSignalStatus = signal; gSignalStatus = signal;
} }
Monitor::Monitor(string shmId, Monitor::Monitor(string shmId, bool selfDestruct, bool interactive, bool viewOnly, unsigned int timeoutInMS, unsigned int intervalInMS, bool monitor, bool cleanOnExit)
bool selfDestruct,
bool interactive,
bool viewOnly,
unsigned int timeoutInMS,
unsigned int intervalInMS,
bool monitor,
bool cleanOnExit)
: fSelfDestruct(selfDestruct) : fSelfDestruct(selfDestruct)
, fInteractive(interactive) , fInteractive(interactive)
, fViewOnly(viewOnly) , fViewOnly(viewOnly)
, fMonitor(monitor) , fMonitor(monitor)
, fSeenOnce(false)
, fCleanOnExit(cleanOnExit) , fCleanOnExit(cleanOnExit)
, fTimeoutInMS(timeoutInMS) , fTimeoutInMS(timeoutInMS)
, fIntervalInMS(intervalInMS) , fIntervalInMS(intervalInMS)

View File

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

View File

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

View File

@@ -39,19 +39,18 @@ namespace fair::mq::shmem
class Socket final : public fair::mq::Socket class Socket final : public fair::mq::Socket
{ {
public: public:
Socket(Manager& manager, Socket(Manager& manager, const std::string& type, const std::string& name, const std::string& id, void* context, fair::mq::TransportFactory* fac = nullptr)
const std::string& type,
const std::string& name,
const std::string& id,
void* context,
fair::mq::TransportFactory* fac = nullptr)
: fair::mq::Socket(fac) : fair::mq::Socket(fac)
, fManager(manager) , fManager(manager)
, fId(id + "." + name + "." + type) , fId(id + "." + name + "." + type)
, fSocket(nullptr)
, fMonitorSocket(nullptr)
, fBytesTx(0) , fBytesTx(0)
, fBytesRx(0) , fBytesRx(0)
, fMessagesTx(0) , fMessagesTx(0)
, fMessagesRx(0) , fMessagesRx(0)
, fTimeout(100)
, fConnectedPeersCount(0)
, fMetadataMsgSize(manager.GetMetadataMsgSize()) , fMetadataMsgSize(manager.GetMetadataMsgSize())
{ {
assert(context); assert(context);
@@ -457,15 +456,15 @@ class Socket final : public fair::mq::Socket
private: private:
Manager& fManager; Manager& fManager;
std::string fId; std::string fId;
void* fSocket{nullptr}; void* fSocket;
void* fMonitorSocket{nullptr}; void* fMonitorSocket;
std::atomic<unsigned long> fBytesTx; std::atomic<unsigned long> fBytesTx;
std::atomic<unsigned long> fBytesRx; std::atomic<unsigned long> fBytesRx;
std::atomic<unsigned long> fMessagesTx; std::atomic<unsigned long> fMessagesTx;
std::atomic<unsigned long> fMessagesRx; std::atomic<unsigned long> fMessagesRx;
int fTimeout{100}; int fTimeout;
mutable unsigned long fConnectedPeersCount{0}; mutable unsigned long fConnectedPeersCount;
std::size_t fMetadataMsgSize; std::size_t fMetadataMsgSize;
}; };

View File

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

View File

@@ -63,7 +63,9 @@ struct UnmanagedRegion
, fName(MakeShmName(shmId, "rg", cfg.id.value())) , fName(MakeShmName(shmId, "rg", cfg.id.value()))
, fQueueName(MakeShmName(shmId, "rgq", cfg.id.value())) , fQueueName(MakeShmName(shmId, "rgq", cfg.id.value()))
, fRefCountSegmentName(MakeShmName(shmId, "rrc", cfg.id.value())) , fRefCountSegmentName(MakeShmName(shmId, "rrc", cfg.id.value()))
, fShmemObject()
, fFile(nullptr) , fFile(nullptr)
, fFileMapping()
, fRcSegmentSize(cfg.rcSegmentSize) , fRcSegmentSize(cfg.rcSegmentSize)
, fQueue(nullptr) , fQueue(nullptr)
, fCallback(nullptr) , fCallback(nullptr)
@@ -100,7 +102,7 @@ struct UnmanagedRegion
} }
fFileMapping = file_mapping(fName.c_str(), read_write); fFileMapping = file_mapping(fName.c_str(), read_write);
LOG(debug) << "UnmanagedRegion(): initialized file: " << fName; LOG(debug) << "UnmanagedRegion(): initialized file: " << fName;
fRegion = mapped_region(fFileMapping, read_write, 0, size, nullptr, cfg.creationFlags); fRegion = mapped_region(fFileMapping, read_write, 0, size, 0, cfg.creationFlags);
} else { } else {
try { try {
// if opening fails, create // if opening fails, create
@@ -124,7 +126,7 @@ struct UnmanagedRegion
} }
try { try {
fRegion = mapped_region(fShmemObject, read_write, 0, 0, nullptr, cfg.creationFlags); fRegion = mapped_region(fShmemObject, read_write, 0, 0, 0, cfg.creationFlags);
if (size != 0 && size != fRegion.get_size()) { if (size != 0 && size != fRegion.get_size()) {
LOG(error) << "Created/opened region size (" << fRegion.get_size() << ") does not match configured size (" << 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, ")")); throw TransportError(tools::ToString("Created/opened region size (", fRegion.get_size(), ") does not match configured size (", size, ")"));

View File

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

View File

@@ -99,7 +99,7 @@ string getDefaultRouteNetworkInterface()
string interfaceName; string interfaceName;
#ifdef __APPLE__ // MacOS #ifdef __APPLE__ // MacOS
unique_ptr<FILE, int (*)(FILE*)> file( unique_ptr<FILE, decltype(pclose)*> file(
popen("route -n get default | grep interface | cut -d \":\" -f 2", "r"), pclose); popen("route -n get default | grep interface | cut -d \":\" -f 2", "r"), pclose);
#else // Linux #else // Linux
ifstream is("/proc/net/route"); 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, " LOG(debug) << "could not get network interface of the default route from /proc/net/route, "
"going to try via 'ip route'"; "going to try via 'ip route'";
unique_ptr<FILE, int (*)(FILE*)> file( unique_ptr<FILE, decltype(pclose)*> file(
popen("ip route | grep default | cut -d \" \" -f 5 | head -n 1", "r"), pclose); popen("ip route | grep default | cut -d \" \" -f 5 | head -n 1", "r"), pclose);
#endif #endif

View File

@@ -10,26 +10,17 @@
#include <fairmq/tools/Strings.h> #include <fairmq/tools/Strings.h>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
#include <boost/process/v1.hpp>
#else
#include <boost/process.hpp> #include <boost/process.hpp>
#endif
#include <chrono> #include <chrono>
#include <csignal> // kill, signals #include <csignal> // kill, signals
#include <iostream> #include <iostream>
#include <mutex>
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <thread> #include <thread>
#include <utility> #include <utility>
using namespace std; using namespace std;
#ifdef FAIRMQ_BOOST_PROCESS_V1_HEADER
namespace bp = boost::process::v1;
#else
namespace bp = boost::process; namespace bp = boost::process;
#endif
namespace ba = boost::asio; namespace ba = boost::asio;
namespace bs = boost::system; namespace bs = boost::system;
@@ -41,18 +32,10 @@ class LinePrinter
, fPrefix(std::move(prefix)) , fPrefix(std::move(prefix))
{} {}
// prints line with prefix on both cout and output stream // prints line with prefix on both cout (thread-safe) and output stream
void Print(const string& line) 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; cout << fair::mq::tools::ToString(fPrefix, line, "\n") << flush;
}
fOut << fPrefix << line << endl; fOut << fPrefix << line << endl;
} }

View File

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

View File

@@ -107,11 +107,7 @@ class Message final : public fair::mq::Message
} }
} }
Message(UnmanagedRegionPtr& region, Message(UnmanagedRegionPtr& region, void* data, const size_t size, void* hint = 0, fair::mq::TransportFactory* factory = nullptr)
void* data,
const size_t size,
void* hint = nullptr,
fair::mq::TransportFactory* factory = nullptr)
: fair::mq::Message(factory) : fair::mq::Message(factory)
, fMsg(std::make_unique<zmq_msg_t>()) , fMsg(std::make_unique<zmq_msg_t>())
{ {

View File

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

View File

@@ -41,7 +41,8 @@ class Socket final : public fair::mq::Socket
, fBytesRx(0) , fBytesRx(0)
, fMessagesTx(0) , fMessagesTx(0)
, fMessagesRx(0) , fMessagesRx(0)
, fTimeout(100)
, fConnectedPeersCount(0)
{ {
if (fSocket == nullptr) { if (fSocket == nullptr) {
LOG(error) << "Failed creating socket " << fId << ", reason: " << zmq_strerror(errno); LOG(error) << "Failed creating socket " << fId << ", reason: " << zmq_strerror(errno);
@@ -404,8 +405,8 @@ class Socket final : public fair::mq::Socket
std::atomic<unsigned long> fMessagesTx; std::atomic<unsigned long> fMessagesTx;
std::atomic<unsigned long> fMessagesRx; std::atomic<unsigned long> fMessagesRx;
int fTimeout{100}; int fTimeout;
mutable unsigned long fConnectedPeersCount{0}; mutable unsigned long fConnectedPeersCount;
}; };
} // namespace fair::mq::zmq } // namespace fair::mq::zmq

View File

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

View File

@@ -16,33 +16,18 @@ if(FairLogger_VERSION VERSION_LESS 1.9.0 AND FairLogger_VERSION VERSION_GREATER_
LIST(APPEND definitions FAIR_MIN_SEVERITY=trace) LIST(APPEND definitions FAIR_MIN_SEVERITY=trace)
endif() endif()
if(FAIRMQ_BOOST_PROCESS_V1_HEADER)
LIST(APPEND definitions FAIRMQ_BOOST_PROCESS_V1_HEADER)
endif()
if(definitions) if(definitions)
set(definitions DEFINITIONS ${definitions}) set(definitions DEFINITIONS ${definitions})
endif() endif()
set(test_environment)
if(ENABLE_SANITIZER_LEAK) if(ENABLE_SANITIZER_LEAK)
get_filename_component(lsan_supps "${CMAKE_CURRENT_SOURCE_DIR}/leak_sanitizer_suppressions.txt" ABSOLUTE) get_filename_component(lsan_supps "${CMAKE_CURRENT_SOURCE_DIR}/leak_sanitizer_suppressions.txt" ABSOLUTE)
list(APPEND test_environment "LSAN_OPTIONS=set:suppressions=${lsan_supps}") set(environment 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() endif()
add_testhelper(runTestDevice add_testhelper(runTestDevice
SOURCES SOURCES
helper/runTestDevice.cxx helper/runTestDevice.cxx
helper/LocaleWarmup.h
helper/devices/TestPairLeft.h helper/devices/TestPairLeft.h
helper/devices/TestPairRight.h helper/devices/TestPairRight.h
helper/devices/TestPollIn.h helper/devices/TestPollIn.h
@@ -149,7 +134,6 @@ add_testsuite(Device
${CMAKE_CURRENT_SOURCE_DIR}/device ${CMAKE_CURRENT_SOURCE_DIR}/device
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 20 TIMEOUT 20
${definitions}
${environment} ${environment}
) )

View File

@@ -0,0 +1,19 @@
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

@@ -0,0 +1,36 @@
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

42
test/ci/slurm-submit.sh Executable file
View File

@@ -0,0 +1,42 @@
#! /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

@@ -1,21 +0,0 @@
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

View File

@@ -1,21 +0,0 @@
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

View File

@@ -1,47 +0,0 @@
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

@@ -1,90 +0,0 @@
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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -453,46 +453,6 @@ TEST(EmptyMessage, shmem_expanded_metadata) // NOLINT
EmptyMessage("shmem", "ipc://test_empty_message", true); 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 TEST(ZeroCopy, shmem) // NOLINT
{ {
ZeroCopy(); ZeroCopy();
@@ -503,93 +463,6 @@ TEST(ZeroCopy, shmem_expanded_metadata) // NOLINT
ZeroCopy(true); 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 TEST(ZeroCopyFromUnmanaged, shmem) // NOLINT
{ {
ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged", false, 10000000); ZeroCopyFromUnmanaged("ipc://test_zerocopy_unmanaged", false, 10000000);

View File

@@ -156,7 +156,7 @@ TEST_F(PluginServices, SubscriptionThreadSafety)
std::array<std::unique_ptr<std::thread>, subscribers> threads; std::array<std::unique_ptr<std::thread>, subscribers> threads;
auto id = 0; auto id = 0;
for (auto& thread : threads) { for (auto& thread : threads) {
thread = std::make_unique<std::thread>([&, id](){ thread = std::make_unique<std::thread>([&](){
auto const subscriber = fair::mq::tools::ToString("subscriber_", id); auto const subscriber = fair::mq::tools::ToString("subscriber_", id);
for (auto i = 0; i < attempts; ++i) { for (auto i = 0; i < attempts; ++i) {
mServices.SubscribeToDeviceStateChange(subscriber, [](DeviceState){}); mServices.SubscribeToDeviceStateChange(subscriber, [](DeviceState){});

View File

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