Skip to content

Commit

Permalink
Feature: Wait for Signals (#15)
Browse files Browse the repository at this point in the history
* added wait functions
  • Loading branch information
johnpatek authored Jan 5, 2025
1 parent 30b29a1 commit 2a3ebc2
Show file tree
Hide file tree
Showing 10 changed files with 481 additions and 116 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ cmake_minimum_required(VERSION 3.15)

project(sigfn)

find_package(Threads REQUIRED)

include(FetchContent)

option(SIGFN_TESTS "Build test suite" OFF)
option(SIGFN_COVER "Add code coverage" OFF)
option(SIGFN_EXAMPLES "Build SigFn examples" OFF)
Expand Down
54 changes: 8 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,64 +55,26 @@ Basic SigFn usage in C. It is copied from `pause.c` in the
```c
#include <sigfn.h>
#include <stdio.h>
// to implement sleep_for()
#ifdef _WIN32
#include <Windows.h>
#else
#include <time.h>
#endif

// signal handler
static void handle_signal(int signum, void *userdata);

// helper function to abstract platform dependent sleep functions
static void sleep_for(int ms);

int main()
int main(int argc, const char **argv)
{
// used to store signum
int flag;
// signal number to wait for
const int signum = SIGINT;

// initialize to what should be an impossible value for SIGINT
flag = -1;
// used to store signum
int received;

// set callback for SIGINT
sigfn_handle(SIGINT, handle_signal, &flag);

// notify user that the program is "paused"
puts("Paused. Press Ctrl+C to exit.");

// sleep loop until we receive the signal
while (flag < 0)
{
// this should stop the CPU from being overworked
sleep_for(100);
}
// pause main thread execution until SIGINT is received
sigfn_wait(&signum, 1, &received);

// notify user that flag was set and exit cleanly
printf("\nReceived signal: %d\n", flag);
printf("\nReceived signal: %d\n", received);

return 0;
}

void handle_signal(int signum, void *userdata)
{
// set int that was passed by address
*(int*)userdata = signum;
}

void sleep_for(int ms)
{
#ifdef _WIN32
Sleep(ms);
#else
const struct timespec duration = {
.tv_sec = ms / 1000,
.tv_nsec = (ms % 1000) * 1000000,
};
nanosleep(&duration, NULL);
#endif
}
```
### C++
Expand Down
52 changes: 7 additions & 45 deletions examples/pause.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,61 +22,23 @@

#include <sigfn.h>
#include <stdio.h>
// to implement sleep_for()
#ifdef _WIN32
#include <Windows.h>
#else
#include <time.h>
#endif

// signal handler
static void handle_signal(int signum, void *userdata);

// helper function to abstract platform dependent sleep functions
static void sleep_for(int ms);

int main(int argc, const char **argv)
{
// used to store signum
int flag;
// signal number to wait for
const int signum = SIGINT;

// initialize to what should be an impossible value for SIGINT
flag = -1;
// used to store signum
int received;

// set callback for SIGINT
sigfn_handle(SIGINT, handle_signal, &flag);

// notify user that the program is "paused"
puts("Paused. Press Ctrl+C to exit.");

// sleep loop until we receive the signal
while (flag < 0)
{
// this should stop the CPU from being overworked
sleep_for(100);
}
// pause main thread execution until SIGINT is received
sigfn_wait(&signum, 1, &received);

// notify user that flag was set and exit cleanly
printf("\nReceived signal: %d\n", flag);
printf("\nReceived signal: %d\n", received);

return 0;
}

void handle_signal(int signum, void *userdata)
{
// set int that was passed by address
*(int*)userdata = signum;
}

void sleep_for(int ms)
{
#ifdef _WIN32
Sleep(ms);
#else
const struct timespec duration = {
.tv_sec = ms / 1000,
.tv_nsec = (ms % 1000) * 1000000,
};
nanosleep(&duration, NULL);
#endif
}
52 changes: 43 additions & 9 deletions include/sigfn.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,24 @@
* @author John R. Patek Sr.
*/

#define SIGFN_SUCCESS 0
#define SIGFN_INVALID_SIGNUM 1
#define SIGFN_INVALID_HANDLER 2

#include <stdlib.h>
#include <signal.h>

#ifdef __cplusplus
extern "C"
{
#endif

#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
struct timeval
{
uint64_t tv_sec;
uint64_t tv_usec;
};
#else
#define DLL_EXPORT
#endif

#ifdef __cplusplus
extern "C"
{
#include <sys/time.h>
#endif

/**
Expand Down Expand Up @@ -81,6 +83,38 @@ extern "C"
*/
DLL_EXPORT int sigfn_reset(int signum);

/**
* @brief wait for any of the specified signals
*
* @param signums array of signal numbers
* @param count number of signals in the array
* @param received signal number that was received, can be NULL
* @returns 0 on success, -1 on error
*/
DLL_EXPORT int sigfn_wait(const int *signums, size_t count, int *received);

/**
* @brief wait for any of the specified signals with a timeout
*
* @param signums array of signal numbers
* @param count number of signals in the array
* @param received signal number that was received, can be NULL
* @param timeout maximum time to wait
* @returns 0 on success, -1 on error, 1 if timed out
*/
DLL_EXPORT int sigfn_wait_for(const int *signums, size_t count, int *received, const struct timeval *timeout);

/**
* @brief wait for any of the specified signals until a deadline
*
* @param signums array of signal numbers
* @param count number of signals in the array
* @param received signal number that was received, can be NULL
* @param deadline time to stop waiting
* @returns 0 on success, -1 on error, 1 if timed out
*/
DLL_EXPORT int sigfn_wait_until(const int *signums, size_t count, int *received, const struct timeval *deadline);

/**
* @brief get the last error message
*
Expand Down
33 changes: 32 additions & 1 deletion include/sigfn.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@
*/

#include <csignal>
#include <stdexcept>
#include <algorithm>
#include <chrono>
#include <functional>
#include <initializer_list>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <thread>

#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
Expand Down Expand Up @@ -79,6 +84,32 @@ namespace sigfn
* @param signum signal to be reset
*/
DLL_EXPORT void reset(int signum);

/**
* @brief wait for any signal in the list
*
* @param signums list of signals to wait for
* @return signal number
*/
DLL_EXPORT int wait(std::initializer_list<int> signums);

/**
* @brief wait for any signal in the list with a timeout
*
* @param signums list of signals to wait for
* @param timeout duration to wait
* @return signal number if received before timeout
*/
DLL_EXPORT std::optional<int> wait_for(std::initializer_list<int> signums, const std::chrono::system_clock::duration &timeout);

/**
* @brief wait for any signal in the list until a deadline
*
* @param signums list of signals to wait for
* @param deadline time point to wait until
* @return signal number if received before deadline
*/
DLL_EXPORT std::optional<int> wait_until(std::initializer_list<int> signums, const std::chrono::system_clock::time_point &deadline);
}

#endif
15 changes: 13 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,24 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

FetchContent_Declare(
Channels
GIT_REPOSITORY https://github.com/johnpatek/channels.git
GIT_TAG master
)
FetchContent_MakeAvailable(Channels)

add_library(sigfn SHARED ${SIGFN_SOURCES})

add_library(sigfn_a STATIC ${SIGFN_SOURCES})

target_include_directories(sigfn PUBLIC ${SIGFN_INCLUDE})
target_include_directories(sigfn PUBLIC ${SIGFN_INCLUDE} ${CMAKE_BINARY_DIR}/_deps/channels-src)

target_include_directories(sigfn_a PUBLIC ${SIGFN_INCLUDE} ${CMAKE_BINARY_DIR}/_deps/channels-src)

target_link_libraries(sigfn PUBLIC Threads::Threads)

target_include_directories(sigfn_a PUBLIC ${SIGFN_INCLUDE})
target_link_libraries(sigfn_a PUBLIC Threads::Threads)

add_library(SigFn::SharedLibrary ALIAS sigfn)

Expand Down
60 changes: 59 additions & 1 deletion src/internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <sigfn.h>
#include <sigfn.hpp>

#include <channels.hpp>

#ifdef _WIN32
typedef void (*__sighandler_t)(int);
#endif
Expand All @@ -36,6 +38,8 @@ namespace sigfn
{
const std::string invalid_syscall = "sigfn: signal() failed";
const std::string invalid_handler = "sigfn: invalid handler";
const std::string empty_sigset = "sigfn: empty signum set";
const std::string invalid_timeval = "sigfn: invalid timeval";

struct state
{
Expand All @@ -48,7 +52,33 @@ namespace sigfn
// adding because the C++ interface is not cooperating with the template
void handle(int signum, sigfn_handler_func handler, void *userdata);

template<class F,class ...Args> int try_catch_return(F&& f, Args&&... args)
template <class IteratorType>
void handle_sigset(IteratorType begin, IteratorType end, channels::buffered_channel<int> &channel)
{
const std::size_t size = std::distance(begin, end);
if (size > 0)
{
std::for_each(
begin,
end,
[&](int signum)
{
sigfn::handle(
signum,
[&](int value)
{
channel.write(value);
});
});
}
else
{
throw std::runtime_error(empty_sigset);
}
}

template <class F, class... Args>
int try_catch_return(F &&f, Args &&...args)
{
int result(0);
try
Expand All @@ -63,6 +93,34 @@ namespace sigfn
}
return result;
}

template <class IteratorType>
void wait(IteratorType begin, IteratorType end, int &signum)
{
channels::buffered_channel<int> channel(std::max<std::size_t>(std::distance(begin, end), 1));
handle_sigset(begin, end, channel);
channel.read(signum);
}

template <class IteratorType>
bool wait_for(IteratorType begin, IteratorType end, int &signum, const std::chrono::system_clock::duration &timeout)
{
channels::buffered_channel<int> channel(std::max<std::size_t>(std::distance(begin, end), 1));
handle_sigset(begin, end, channel);
return channel.read_for(signum, timeout) == channels::read_status::success;
}

template <class IteratorType>
bool wait_until(IteratorType begin, IteratorType end, int &signum, const std::chrono::system_clock::time_point &deadline)
{
channels::buffered_channel<int> channel(std::max<std::size_t>(std::distance(begin, end), 1));
handle_sigset(begin, end, channel);
return channel.read_until(signum, deadline) == channels::read_status::success;
}

std::chrono::system_clock::duration make_duration(const struct timeval *timeval);

std::chrono::system_clock::time_point make_time_point(const struct timeval *timeval);
}
}

Expand Down
Loading

0 comments on commit 2a3ebc2

Please sign in to comment.