diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b9c7200f491911d63973488067bb545a238daebe..3a03bf281f4856f6ae217f6a4bb9c86841a4159a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,8 @@ image: "flowdalic/debian-dev:1.13" before_script: + - apt update && apt install -y pkg-config + - apt install -y -t testing liburcu-dev - | readarray TOOLS <<EOF c++ diff --git a/emper/Emper.hpp b/emper/Emper.hpp index 76fa35e31ccb4ed0defe0e1511cba81f1b05e5d1..d5c3dc57b40a468a3361fb40c1e6f0fdcc48395a 100644 --- a/emper/Emper.hpp +++ b/emper/Emper.hpp @@ -22,4 +22,12 @@ static const bool WORKER_SLEEP = #endif ; +static const bool LIBURCU = +#ifdef EMPER_LIBURCU + true +#else + false +#endif + ; + } // namespace emper diff --git a/emper/Runtime.cpp b/emper/Runtime.cpp index 041bcc4bc7c52b6f634a0ed39ae513d119aa6889..2cc9e480b524f777e94f152fdda963cef656b601 100644 --- a/emper/Runtime.cpp +++ b/emper/Runtime.cpp @@ -8,6 +8,7 @@ // Non portable. #include <sched.h> // for cpu_set_t, CPU_SET, CPU_ZERO #include <sys/sysinfo.h> // for get_nprocs +#include <urcu.h> // for rcu_register_thread #include <cstdlib> // for rand, srand, abort #include <cstring> @@ -78,6 +79,10 @@ Runtime::Runtime(workerid_t workerCount, RuntimeStrategy& strategy, unsigned int workerIds[i] = i; auto thread_function = [](void* voidWorkerId) -> void* { + if constexpr (emper::LIBURCU) { + rcu_register_thread(); + } + return currentRuntime->workerLoop(voidWorkerId); }; errno = pthread_create(&threads[i], &attr, thread_function, &workerIds[i]); diff --git a/emper/meson.build b/emper/meson.build index eeee83e0fcdc45d4072b09519c49684b85ddba25..f73891a7543d0266bdeea336542a7a009957264d 100644 --- a/emper/meson.build +++ b/emper/meson.build @@ -45,7 +45,7 @@ emper = library( [emper_cpp_sources, emper_generated_files], emper_asm_objects, include_directories: emper_all_include, - dependencies: thread_dep, + dependencies: emper_dependencies, install: true, ) diff --git a/iwyu-mappings.imp b/iwyu-mappings.imp index 8a64523b20e91b6a76971ec7398b9dbbcd5bfa9f..3b0d99bef9cad915b5f885595e236530608d3c27 100644 --- a/iwyu-mappings.imp +++ b/iwyu-mappings.imp @@ -1,4 +1,5 @@ [ { include: ["<bits/getopt_core.h>", "private", "<unistd.h>", "public"] }, { include: ["@<gtest/.*>", "private", "<gtest/gtest.h>", "public"] }, + { include: ["<urcu/map/urcu-memb.h>", "private", "<urcu.h>", "public"] }, ] diff --git a/meson.build b/meson.build index 325105c849c06f727a5fa8c03dfff3561e7ee435..6c2c21e593783963abaa0561fc907a93b5a9502d 100644 --- a/meson.build +++ b/meson.build @@ -12,12 +12,14 @@ project('EMPER', 'c', 'cpp', add_project_arguments('-Wno-non-virtual-dtor', language: 'cpp') thread_dep = dependency('threads') -emper_dependencies = [thread_dep] +liburcu_dep = dependency('liburcu') +emper_dependencies = [thread_dep, liburcu_dep] run_target('iwyu', command: 'tools/check-iwyu') conf_data = configuration_data() +conf_data.set('EMPER_LIBURCU', get_option('userspace-rcu')) conf_data.set('EMPER_WORKER_SLEEP', get_option('worker_sleep')) conf_data.set('EMPER_LOCKED_WS_QUEUE', get_option('locked_ws_queue')) conf_data.set('EMPER_OVERFLOW_QUEUE', get_option('overflow_queue')) diff --git a/meson_options.txt b/meson_options.txt index 98a9ca1502ba1b7aa5fe3a03bd7d85a587771147..8032e30c80192ddd7463a3d70e288570472e3bbd 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,9 @@ +option( + 'userspace-rcu', + type: 'boolean', + value: true, + description: 'Allow EMPER fibers to use userspace RCU', +) option( 'worker_sleep', type: 'boolean', diff --git a/tests/SimpleURCUTest.cpp b/tests/SimpleURCUTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dfd9f813b93e3060b37c8e3d66ce4a802dbca721 --- /dev/null +++ b/tests/SimpleURCUTest.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright © 2020 Florian Fischer +#include <urcu.h> // for rcu_read_lock, rcu_read_unlock +#include <urcu/rculfhash.h> // for RCU lock-free hash table + +#include <algorithm> // for find +#include <cstdlib> // for exit, EXIT_FAILURE, EXIT_SUC... +#include <functional> // for hash +#include <iostream> // for hash +#include <vector> // for vector + +#include "Common.hpp" // for die +#include "CountingPrivateSemaphore.hpp" // for CPS +#include "Fiber.hpp" // for Fiber +#include "Runtime.hpp" // for Runtime +#include "emper.hpp" // for spawn + +struct node { + int value; + struct cds_lfht_node node; /* Chaining in hash table */ +}; + +using node_t = struct node; + +auto main() -> int { + Runtime runtime; + struct cds_lfht* ht; + std::vector<int> values = { + -5, 42, 42, 36, 24, + }; /* 42 is duplicated */ + + // Allocate new hash table. + ht = cds_lfht_new(1, 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, nullptr); + if (!ht) { + die("Error allocating hash table", false); + } + + Fiber* verifier = Fiber::from([&]() { + CPS cps; + for (auto& value : values) { + // add each value to the hash table + spawn( + [&ht, value] { + auto* node = reinterpret_cast<node_t*>(malloc(sizeof(node_t))); + if (!node) { + die("allocating node failed", true); + } + + cds_lfht_node_init(&node->node); + node->value = value; + size_t hash = std::hash<int>{}(value); + + rcu_read_lock(); + cds_lfht_add(ht, hash, &node->node); + rcu_read_unlock(); + }, + cps); + } + + // Wait for the adders to finish + cps.wait(); + + // Verify the content of the hash table. + // Iterate over each hash table node. + // Iteration needs to be performed within RCU read-side critical section. + struct cds_lfht_iter iter; + node_t* node; + rcu_read_lock(); + size_t i = 0; + cds_lfht_for_each_entry(ht, &iter, node, node) { + ++i; + auto it = std::find(values.begin(), values.end(), node->value); + if (it == values.end()) { + std::cerr << "value: " << node->value << " not found in cds_lfht" << std::endl; + exit(EXIT_FAILURE); + } + } + rcu_read_unlock(); + + if (i != values.size()) { + std::cerr << "number of values in cds_lfht: " << i + << " differ number of added ones: " << values.size() << std::endl; + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + }); + + runtime.schedule(*verifier); + runtime.waitUntilFinished(); + + return EXIT_FAILURE; +} diff --git a/tests/meson.build b/tests/meson.build index 2ac875c0ee7d3dc4c9f4b46f8437b9a815ec26b3..e1c2f709cb7c25948cb176c905825b400a8ca4d2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,3 +1,7 @@ +cc = meson.get_compiler('c') +liburcu_memb = cc.find_library('urcu-memb') +liburcu_cds = cc.find_library('urcu-cds') + tests = { 'SimpleFibTest.cpp': { @@ -29,10 +33,14 @@ tests = { { 'description': 'Simple LAWS scheduling strategy test', }, + 'SimpleURCUTest.cpp': + { + 'description': 'Simple userspace-rcu hash table test', + 'dependencies': [liburcu_memb, liburcu_cds] + }, } undef_ndebug = '-UNDEBUG' -test_dep = [thread_dep] test_env = environment( { # Set glibc's MALLOC_PERTURB to 1. This means that newly allocated @@ -50,12 +58,17 @@ foreach source, test_dict : tests # The test_name is the name of the source file without the file suffix. test_name = source.split('.')[0] + test_dep = [thread_dep] + if test_dict.has_key('dependencies') + test_dep += test_dict['dependencies'] + endif + test_exe = executable(test_name, source, include_directories: emper_all_include, c_args: undef_ndebug, cpp_args: undef_ndebug, - dependencies: test_dep, + dependencies: emper_dependencies + test_dep, link_with: [emper, emper_c], )