initial import
This commit is contained in:
commit
076e77ff53
44
CMakeLists.txt
Normal file
44
CMakeLists.txt
Normal 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
7
LICENSE
Normal 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
49
README.md
Normal 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
1
build/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*
|
6
cmake/DefaultBuildRelease.cmake
Normal file
6
cmake/DefaultBuildRelease.cmake
Normal 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()
|
||||
|
67
cmake/EnableWarnings.cmake
Normal file
67
cmake/EnableWarnings.cmake
Normal 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
3
cmake/SgnlConfig.cmake
Normal file
@ -0,0 +1,3 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(Threads)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/SgnlTargets.cmake")
|
63
include/sgnl/AtomicCondition.h
Normal file
63
include/sgnl/AtomicCondition.h
Normal 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
|
||||
|
75
include/sgnl/SignalHandler.h
Normal file
75
include/sgnl/SignalHandler.h
Normal 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
18
test/CMakeLists.txt
Normal 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
63
test/test.cpp
Normal 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 );
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user