initial import

This commit is contained in:
Tom 2019-09-14 22:41:56 +02:00
commit 076e77ff53
11 changed files with 396 additions and 0 deletions

44
CMakeLists.txt Normal file
View File

@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(signal-handler VERSION 0.1.0)
# Add the top-level cmake module directory to CMAKE_MODULE_PATH
list(INSERT CMAKE_MODULE_PATH 0 ${PROJECT_SOURCE_DIR}/cmake)
find_package(Threads REQUIRED)
if( NOT CMAKE_USE_PTHREADS_INIT )
message(FATAL_ERROR "pthreads required")
endif()
add_library(sgnl INTERFACE)
add_library(sgnl::sgnl ALIAS sgnl)
target_include_directories(
sgnl INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)
target_link_libraries(sgnl INTERFACE Threads::Threads)
target_compile_features(sgnl INTERFACE cxx_std_17)
include(EnableWarnings)
enable_warnings(sgnl INTERFACE)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/SgnlConfigVersion.cmake"
COMPATIBILITY SameMajorVersion)
include(GNUInstallDirs)
install(TARGETS sgnl
EXPORT SgnlTargets)
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/sgnl"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES "${PROJECT_SOURCE_DIR}/cmake/SgnlConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/SgnlConfigVersion.cmake"
DESTINATION lib/cmake/sgnl)
install(EXPORT SgnlTargets
FILE SgnlTargets.cmake
NAMESPACE sgnl::
DESTINATION lib/cmake/sgnl)
enable_testing()
add_subdirectory("test")

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2019 Thomas Trapp
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

49
README.md Normal file
View File

@ -0,0 +1,49 @@
Signal handler for multithreaded C++ applications on Linux
==========================================================
Example usage:
```
bool worker(sgnl::AtomicCondition<bool>& exit_condition)
{
while( true )
{
exit_condition.wait_for(std::chrono::milliseconds(1000), true);
if( exit_condition.get() )
return true;
}
}
sgnl::AtomicCondition exit_condition(false);
sgnl::SignalHandler signal_handler(
{{SIGINT, true}, {SIGTERM, true}},
exit_condition);
std::promise<pthread_t> signal_handler_thread_id;
std::future<int> ft_sig_handler =
std::async(std::launch::async, [&]() {
signal_handler_thread_id.set_value(pthread_self());
return signal_handler();
});
std::vector<std::future<bool>> futures;
for(int i = 0; i < 10; ++i)
futures.push_back(
std::async(
std::launch::async,
&worker,
std::ref(exit_condition)));
// simulate [ctrl]+[c], which sends SIGINT
std::this_thread::sleep_for(std::chrono::milliseconds(100));
pthread_kill(
signal_handler_thread_id.get_future().get(),
SIGINT);
for(auto& future : futures)
future.get();
int signal = ft_sig_handler.get();
```

1
build/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*

View File

@ -0,0 +1,6 @@
# If build type was not specified, build Release.
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release")
endif()

View File

@ -0,0 +1,67 @@
function(enable_warnings_gnu)
target_compile_options(
${ARGN}
"-Wall"
"-Wcast-align"
"-Wcast-qual"
"-Wconversion"
"-Wctor-dtor-privacy"
"-Wdisabled-optimization"
"-Weffc++"
"-Wextra"
"-Wfloat-equal"
"-Wformat=2"
"-Wimport"
"-Winvalid-pch"
"-Wlogical-op"
"-Wmissing-format-attribute"
"-Wmissing-include-dirs"
"-Wmissing-noreturn"
"-Woverloaded-virtual"
"-Wpacked"
"-Wpointer-arith"
"-Wredundant-decls"
"-Wshadow"
"-Wsign-conversion"
"-Wsign-promo"
"-Wstack-protector"
"-Wstrict-aliasing=2"
"-Wstrict-null-sentinel"
"-Wstrict-overflow"
"-Wswitch"
"-Wundef"
"-Wunreachable-code"
"-Wunused"
"-Wvariadic-macros"
"-Wwrite-strings"
"-pedantic"
"-pedantic-errors"
)
endfunction()
function(enable_warnings_clang)
target_compile_options(
${ARGN}
"-Weverything"
"-Wno-c++98-compat"
"-Wno-documentation"
"-Wno-documentation-html"
"-Wno-documentation-unknown-command"
"-Wno-exit-time-destructors"
"-Wno-global-constructors"
"-Wno-padded"
"-Wno-switch-enum"
"-Wno-covered-switch-default"
"-Wno-weak-vtables")
endfunction()
function(enable_warnings)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
enable_warnings_clang(${ARGN})
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
enable_warnings_gnu(${ARGN})
endif()
endfunction()

3
cmake/SgnlConfig.cmake Normal file
View File

@ -0,0 +1,3 @@
include(CMakeFindDependencyMacro)
find_dependency(Threads)
include("${CMAKE_CURRENT_LIST_DIR}/SgnlTargets.cmake")

View File

@ -0,0 +1,63 @@
// Author: Thomas Trapp - https://thomastrapp.com/
// License: MIT
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <stdexcept>
namespace sgnl {
template<typename ValueType>
class AtomicCondition
{
public:
explicit AtomicCondition(ValueType val)
: value_(val)
, condvar_mutex_()
, condvar_()
{
// requirement of std::signal
if( !this->value_.is_lock_free() )
throw std::runtime_error("atomic<ValueType> is not lock-free");
}
ValueType get() const
{
return this->value_.load();
}
void set(ValueType val)
{
this->value_.store(val);
}
void wait_for(const std::chrono::milliseconds& time, ValueType val) const
{
std::unique_lock<std::mutex> lock(this->condvar_mutex_);
// This while-loop takes care of "spurious wakeups"
while( this->value_.load() != val )
if( this->condvar_.wait_for(lock, time) == std::cv_status::timeout )
return;
}
void notify_all()
{
this->condvar_.notify_all();
}
private:
std::atomic<ValueType> value_;
mutable std::mutex condvar_mutex_;
mutable std::condition_variable condvar_;
};
} // namespace sgnl

View File

@ -0,0 +1,75 @@
// Author: Thomas Trapp - https://thomastrapp.com/
// License: MIT
#pragma once
#include <sgnl/AtomicCondition.h>
#include <csignal>
#include <cstring>
#include <map>
#include <stdexcept>
#include <utility>
namespace sgnl {
class SignalHandlerException : public std::runtime_error
{
using std::runtime_error::runtime_error;
};
template<typename ValueType>
class SignalHandler
{
public:
SignalHandler(std::map<int, ValueType> signal_map,
AtomicCondition<ValueType>& condition)
: signal_map_(std::move(signal_map))
, set_()
, condition_(condition)
{
if( sigemptyset(&this->set_) != 0 )
throw SignalHandlerException("sigemptyset error");
for( const auto& p : this->signal_map_ )
if( sigaddset(&this->set_, p.first) != 0 )
throw SignalHandlerException("sigaddset error");
int s = pthread_sigmask(SIG_BLOCK, &this->set_, nullptr);
if( s != 0 )
throw SignalHandlerException(
std::string("pthread_sigmask: ") + std::strerror(s));
}
int operator()()
{
while( true )
{
int signum = 0;
int ret = sigwait(&this->set_, &signum);
if( ret != 0 )
throw SignalHandlerException(
std::string("sigwait: ") + std::strerror(ret));
if( auto it = this->signal_map_.find(signum);
it != this->signal_map_.end() )
{
this->condition_.set(it->second);
this->condition_.notify_all();
return it->first;
}
}
}
private:
std::map<int, ValueType> signal_map_;
sigset_t set_;
AtomicCondition<ValueType>& condition_;
};
} // namespace sgnl

18
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
project(signal-handler-test)
include(EnableWarnings)
include(DefaultBuildRelease)
find_package(Catch2 REQUIRED)
add_executable(
sgnl-test
"${PROJECT_SOURCE_DIR}/test.cpp")
enable_warnings(sgnl-test PRIVATE)
target_link_libraries(sgnl-test sgnl::sgnl Catch2::Catch2 "-fsanitize=thread")
target_compile_features(sgnl-test PUBLIC cxx_std_17)
target_compile_options(sgnl-test PUBLIC "-fsanitize=thread")
add_test("signal-handler-test" sgnl-test)

63
test/test.cpp Normal file
View File

@ -0,0 +1,63 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include <sgnl/SignalHandler.h>
#include <pthread.h>
#include <chrono>
#include <future>
#include <vector>
namespace {
bool worker(sgnl::AtomicCondition<bool>& exit_condition)
{
exit_condition.wait_for(std::chrono::milliseconds(1000), true);
return exit_condition.get();
}
} // namespace
TEST_CASE("main")
{
std::vector<int> signals({SIGINT, SIGTERM, SIGUSR1, SIGUSR2});
for( auto test_signal : signals )
{
sgnl::AtomicCondition exit_condition(false);
sgnl::SignalHandler signal_handler({{test_signal, true}}, exit_condition);
std::promise<pthread_t> signal_handler_thread_id;
std::future<int> ft_sig_handler =
std::async(std::launch::async, [&]() {
signal_handler_thread_id.set_value(pthread_self());
return signal_handler();
});
std::vector<std::future<bool>> futures;
for(int i = 0; i < 10; ++i)
futures.push_back(
std::async(
std::launch::async,
&worker,
std::ref(exit_condition)));
std::this_thread::sleep_for(std::chrono::milliseconds(100));
REQUIRE(
pthread_kill(
signal_handler_thread_id.get_future().get(),
test_signal) == 0 );
for(auto& future : futures)
REQUIRE(future.get() == true);
int signal = ft_sig_handler.get();
REQUIRE( signal == test_signal );
}
}