LibEventCpp is a lightweight and portable C++14 library designed for handling events efficiently. It is implemented in a single header file, making it easy to integrate into projects. The library supports an unlimited number of arguments for event handlers, providing flexibility in event management. LibEventCpp is designed to be simple to use while offering powerful features for event-driven programming.
MIT License
GCC (GNU Compiler Collection): Version 5.0 and later.
Clang: Version 3.4 and later.
To ensure compatibility, you should use the appropriate compiler flags to enable C++14. For example:
GCC/Clang: -std=c++14
LibEventCpp primarily supports Linux environments, making it highly suitable for most automotive projects where Linux-based embedded systems are commonly used. The library leverages POSIX APIs for features such as timers, signals, file descriptors, and file system monitoring, ensuring optimal performance and integration in Linux-based automotive ECUs and embedded platforms.
To use LibEventCpp to handle event messages, you can create an object of derived class of event_handler::event_handler that contains the member functions, which are considered the reaction for the event messages. Internally, event_handler::event_handler uses a event_handler::event_looper to continously and asynchronously poll and execute messages from a event_handler::event_queue.
Checkout the sample message event handler source code HERE.
Include necessary headers
#include "libevent.h"
Create a derived event_handler::event_handler class
class TestHandler : public event_handler::event_handler
{
public:
TestHandler()
: event_handler::event_handler()
{
}
void print_message(std::string s)
{
std::cout << s << std::endl;
}
void do_heavy_work(int ith)
{
for (int i = 0; i < 1000; i++)
{
// do something
int val = i & 1;
(void)(val);
}
std::cout << "Done a heavy work ith = " << ith << std::endl;
}
void repated_work()
{
static std::size_t repeated_times = 0U;
repeated_times++;
std::cout << "This is a job for repeated message, " << repeated_times << " times" << std::endl;
}
void tick()
{
std::cout << "Tick every 1 second..." << std::endl;
this->post_delayed_event(1000U, &TestHandler::tick);
}
};
Here, we created a TestHandler class that inherits from event_handler::event_handler. We defined several methods (print_message, do_heavy_work, repated_work, and tick) to handle different types of events.
Declare the object of derived event_handler::event_handler class
std::shared_ptr<TestHandler> handler = std::make_shared<TestHandler>();
Start posting event messages
From this moment, you can post messages to the handler. These messages will be processed asynchronously:
// Post a delayed message
handler->post_delayed_event(2000U, &TestHandler::print_message, std::string("The delayed message is printed after 2 seconds"));
// Post a repated message (number of repeating times and duration (ms) between each couple)
handler->post_repeated_event(5, 1000U, &TestHandler::repated_work);
// Post a regular message
handler->post_event(&TestHandler::print_message, std::string("Hello event handler"));
// Call a member function of the handler that trigger an internal event
handler->post_event(&TestHandler::tick);
// Post multiple heavy work messages
for (int i = 0; i < 10; i++)
{
handler->post_event(&TestHandler::do_heavy_work, i);
}
To use LibEventCpp to create signal-slot connections just as an alternative of Qt framework, follow these steps:
Include neccessary headers
#include "libevent.h"
Declare the signal
class Sender
{
public:
sigslot::signal<std::string> message_notification;
void boardcast_message(std::string mess)
{
message_notification(mess);
}
};
The sigslot::signal variable can be declared as member or outside of a class.
Define a class with slot functions
class Listener : public sigslot::base_slot
{
public:
void on_boardcast_received(std::string mess)
{
std::cout << "Received boardcast message: " << mess << std::endl;
}
};
The created class must inherits from sigslot::base_slot.
Create instances of the classes
Sender sender;
Listener listener;
Connect signal to slot
sender.message_notification.connect(&listener, &Listener::on_boardcast_received);
Or using sigslot::connect function:
sigslot::signal<std::string> global_broadcast;
sigslot::connect(global_broadcast, &listener, &Listener::on_boardcast_received);
Emit the signal
sender.boardcast_message("Hello from Sender");
Close the connection when needed
sender.message_notification.disconnect(&listener);
or
sender.message_notification.disconnect_all();
The connection can also be closed from Listener side
listener.disconnect(&sender);
or
listener.disconnect_all();
Advanced signals and slots usage:
Sometimes, it is helpful to connect a signal to a lambda function or a callable object
sigslot::signal<> test_sig_lambda;
test_sig_lambda.connect([]() {
std::cout << "The signal connected to this lambda is activated" << std::endl;
});
test_sig_lambda();
test_sig_lambda.disconnect_all_callable();
Checkout the sample signal and slot source code HERE.
The time_event::timer class provides a thread-safe mechanism for creating and managing timers in a multithreaded environment. It supports ont-shot and periodic timers, with the ability to invoke user-defined callback functions upon expiration.
Checkout the sample message event handler source code HERE.
Note that the time_event::timer uses POSIX APIs (timer_create, timer_settime, etc...) so it is only supported on platforms that provide these APIs.
Include necessary headers
#include "libevent.h"
Create timer instance
time_event::timer my_timer;
Set duration
my_timer.set_duration(1000); // 1 second
Adding callbacks
my_timer.add_callback([]() {
std::cout << "Timer expired!" << std::endl;
});
Start the timer
try
{
my_timer.start(5); // repeat 5 times
my_timer.start(0); // one-shot
my_timer.start(); // repeat forever
}
catch (const std::runtime_error& e)
{
// It is possible that timer creation is failed.
// In that case, exception runtime_error is thrown
}
Actively stop the timer
my_timer.stop();
The once_event namespace provides several classes for controlling function execution based on various conditions. These classes help prevent duplicate operations and control when code should run.
Checkout the sample once event source code HERE.
Include necessary headers
#include "libevent.h"
once_per_life
Ensures a function is called only once during the lifetime of the object, similar to std::call_once.
once_event::once_per_life init_flag;
// This will be called only once, no matter how many times you try
init_flag.call_once([]() {
std::cout << "Initialize resources" << std::endl;
});
// Subsequent calls are ignored
init_flag.call_once([]() {
std::cout << "This will not print" << std::endl;
});
once_per_n_times
Executes a function once every N calls.
once_event::once_per_n_times every_third(3);
for (int i = 1; i <= 10; ++i) {
// This callback is executed on calls 3, 6, 9
every_third.call_if_due([]() {
std::cout << "Executed!" << std::endl;
});
}
// Reset the counter
every_third.reset();
once_per_value
Executes a function only when a value changes.
once_event::once_per_value<int> value_monitor;
value_monitor.on_value_change(5, [](int val) {
std::cout << "New value: " << val << std::endl;
}); // Prints: New value: 5
value_monitor.on_value_change(5, [](int val) {
std::cout << "New value: " << val << std::endl;
}); // Does nothing (value unchanged)
value_monitor.on_value_change(10, [](int val) {
std::cout << "New value: " << val << std::endl;
}); // Prints: New value: 10
// Reset to monitor from scratch
value_monitor.reset();
once_per_interval
Executes a function at most once per time interval.
once_event::once_per_interval rate_limiter(1000); // 1 second interval
for (int i = 0; i < 10; ++i) {
bool executed = rate_limiter.call([]() {
std::cout << "Rate-limited action" << std::endl;
});
if (!executed) {
std::cout << "Skipped due to rate limit" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
once_at_least
Ensures a callback is invoked at least once per interval with the latest data, even if no new data arrives. Useful for guaranteeing periodic updates.
once_event::once_at_least<std::string> data_stream(
1000U, // 1 second interval
[](const std::string& data) {
std::cout << "Processing data: " << data << std::endl;
}
);
for (int i = 0; i < 5; ++i)
{
data_stream.update("Data packet " + std::to_string(i));
// sleep 1s with jiter randomly +/- 20ms
auto sleep_duration = 1000 + (rand() % 41 - 20);
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_duration));
std::cout << "Slept for " << sleep_duration << " ms" << std::endl;
}
data_stream.stop();
The toggle_event namespace provides a synchronization mechanism that triggers a callback only once when a condition becomes true, and can be reset when the condition becomes false. This is useful for state-based event handling.
Checkout the sample toggle event source code HERE.
Include necessary headers
#include "libevent.h"
Basic usage
toggle_event::toggle_event toggle;
// Trigger callback only on first call
toggle.trigger_if_not_set([]() {
std::cout << "Event triggered!" << std::endl;
}); // Prints: Event triggered!
// Subsequent calls are ignored
toggle.trigger_if_not_set([]() {
std::cout << "This won't print" << std::endl;
}); // Does nothing
// Reset to allow next trigger
toggle.reset();
toggle.trigger_if_not_set([]() {
std::cout << "Triggered again!" << std::endl;
}); // Prints: Triggered again!
State management
toggle_event::toggle_event connection_state;
// Check state
if (!connection_state.is_triggered()) {
std::cout << "Not yet triggered" << std::endl;
}
// Set without triggering callback
connection_state.set();
// Check state again
if (connection_state.is_triggered()) {
std::cout << "Now triggered" << std::endl;
}
Blocking wait
toggle_event::toggle_event event;
// Thread 1: Wait for event
std::thread waiter([&event]() {
std::cout << "Waiting for event..." << std::endl;
event.wait(); // Blocks until event is triggered
std::cout << "Event received!" << std::endl;
});
// Thread 2: Trigger event
std::thread trigger([&event]() {
std::this_thread::sleep_for(std::chrono::seconds(2));
event.trigger_if_not_set([]() {
std::cout << "Triggering event" << std::endl;
});
});
waiter.join();
trigger.join();
Wait with timeout
toggle_event::toggle_event event;
// Wait for event with 2 second timeout
bool result = event.wait_for(2000);
if (result) {
std::cout << "Event triggered within timeout" << std::endl;
} else {
std::cout << "Timeout waiting for event" << std::endl;
}
Wait then execute
toggle_event::toggle_event ready_signal;
// Wait for signal, then execute callback
std::thread worker([&ready_signal]() {
ready_signal.wait_then([]() {
std::cout << "Ready! Starting work..." << std::endl;
});
});
// Trigger signal from another thread
std::this_thread::sleep_for(std::chrono::seconds(1));
ready_signal.trigger_if_not_set([]() {
std::cout << "Signaling ready" << std::endl;
});
worker.join();
Practical example - Connection state monitoring
class ConnectionMonitor {
public:
ConnectionMonitor() : connected_(false) {}
void check_connection() {
if (is_connected()) {
disconnected_event_.reset(); // Reset disconnect event
connected_event_.trigger_if_not_set([this]() {
std::cout << "Connection established" << std::endl;
on_connected();
});
} else {
connected_event_.reset(); // Reset connect event
disconnected_event_.trigger_if_not_set([this]() {
std::cout << "Connection lost" << std::endl;
on_disconnected();
});
}
}
private:
bool is_connected() { return connected_; }
void on_connected() { /* Handle connection */ }
void on_disconnected() { /* Handle disconnection */ }
bool connected_;
toggle_event::toggle_event connected_event_;
toggle_event::toggle_event disconnected_event_;
};
Features
All methods are protected with a mutex, making the fd_event_manager thread-safe for:
Note: Callbacks are invoked from the thread that calls wait() or wait_and_process().
Examples:
#include "libevent.h"
fd_event::fd_event_manager manager;
// Add file descriptor with callback
manager.add_fd(
socket_fd,
static_cast<short>(fd_event::event_type::READ),
[](int fd, short revents, void* user_data) {
if (revents & POLLIN) {
char buffer[1024];
ssize_t n = read(fd, buffer, sizeof(buffer));
// Process data...
}
},
nullptr,
"my_socket"
);
// Main loop
while (running) {
int ret = manager.wait_and_process(1000); // 1 second timeout
if (ret < 0) {
std::cerr << "Error: " << manager.get_last_error() << std::endl;
}
}
struct Context {
int counter;
std::string name;
};
Context my_context = {0, "MyContext"};
manager.add_fd(
fd,
POLLIN,
[](int fd, short revents, void* user_data) {
Context* ctx = static_cast<Context*>(user_data);
ctx->counter++;
std::cout << ctx->name << " event #" << ctx->counter << std::endl;
},
&my_context,
"context_fd"
);
manager.set_error_handler([](const std::string& error) {
std::cerr << "fd_event_manager Error: " << error << std::endl;
});
manager.add_fd(
fd,
POLLIN,
[](int fd, short revents, void* user_data) {
if (revents & POLLERR) {
std::cerr << "Error on fd " << fd << std::endl;
}
if (revents & POLLHUP) {
std::cerr << "Hangup on fd " << fd << std::endl;
}
if (revents & POLLIN) {
// Handle data...
}
}
);
fd_event::fd_event_manager fd_manager;
// Context for callback
struct GnssContext {
GnssCommander* commander;
CParserBuffer* parser;
};
GnssContext ctx = {pCommander, &parser};
// Add GNSS receiver FD
fd_manager.add_fd(
pCommander->get_receiver()->get_fd(),
static_cast<short>(fd_event::event_type::READ),
[](int fd, short revents, void* user_data) {
GnssContext* ctx = static_cast<GnssContext*>(user_data);
if (revents & POLLHUP) {
LOG_ERROR("GNSS receiver hang up");
return;
}
if (revents & POLLERR) {
LOG_ERROR("GNSS receiver error");
return;
}
if (revents & POLLIN) {
ctx->commander->lock_operation();
// Read and process data...
ctx->commander->unlock_operation();
}
},
&ctx,
"gnss_receiver"
);
// Main loop
while (pCommander->is_running()) {
int ret = fd_manager.wait_and_process(timeout);
if (ret < 0) {
LOG_ERROR("Event manager error: %s", fd_manager.get_last_error().c_str());
}
}
The signal_event namespace provides a C++ wrapper around POSIX signal handling APIs. It offers a clean and safe interface for managing Unix/Linux signals in your application.
Checkout the sample signal event source code HERE.
Include necessary headers
#include "libevent.h"
Setting up signal handlers
void handle_interrupt(int signum) {
std::cout << "Caught signal " << signum << std::endl;
std::cout << "Gracefully shutting down..." << std::endl;
// Cleanup and exit
}
// Set handler for SIGINT (Ctrl+C)
if (signal_event::set_signal_handler(SIGINT, handle_interrupt)) {
std::cout << "Signal handler set successfully" << std::endl;
}
// Reset to default behavior
signal_event::reset_signal_handler(SIGINT);
// Ignore a signal
signal_event::ignore_signal(SIGUSR1);
Extended signal handler with info
Get additional information about the signal (sender PID, UID, etc.):
void extended_handler(int signum, siginfo_t* info, void* context) {
std::cout << "Signal: " << signal_event::get_signal_name(signum) << std::endl;
std::cout << "Signal code: " << info->si_code << std::endl;
std::cout << "Sender PID: " << info->si_pid << std::endl;
std::cout << "Sender UID: " << info->si_uid << std::endl;
}
signal_event::set_signal_handler_ex(SIGUSR1, extended_handler);
Blocking and unblocking signals
// Block a signal (it will be pending)
signal_event::block_signal(SIGUSR1);
// Send signal - it will be pending, not delivered yet
signal_event::raise_signal(SIGUSR1);
// Check if signal is pending
if (signal_event::is_signal_pending(SIGUSR1)) {
std::cout << "SIGUSR1 is pending" << std::endl;
}
// Unblock signal - now it will be delivered
signal_event::unblock_signal(SIGUSR1);
// Block multiple signals at once
std::vector<int> signals = {SIGUSR1, SIGUSR2, SIGTERM};
signal_event::block_signals(signals);
// Check if a signal is blocked
if (signal_event::is_signal_blocked(SIGUSR1)) {
std::cout << "SIGUSR1 is blocked" << std::endl;
}
// Unblock multiple signals
signal_event::unblock_signals(signals);
Sending signals
// Send signal to current process
signal_event::raise_signal(SIGUSR1);
// Send signal to another process
pid_t target_pid = 1234;
signal_event::send_signal(target_pid, SIGUSR1);
Waiting for signals
// Block signals first (required for sigwait)
signal_event::block_signals({SIGUSR1, SIGUSR2});
// Wait for any of these signals (infinite timeout)
int received = signal_event::wait_for_signal({SIGUSR1, SIGUSR2});
if (received > 0) {
std::cout << "Received signal: " << signal_event::get_signal_name(received) << std::endl;
}
// Wait with timeout (milliseconds)
received = signal_event::wait_for_signal({SIGUSR1, SIGUSR2}, 5000);
if (received > 0) {
std::cout << "Received: " << signal_event::get_signal_name(received) << std::endl;
} else {
std::cout << "Timeout or error" << std::endl;
}
Getting signal names
std::cout << signal_event::get_signal_name(SIGINT) << std::endl; // Output: SIGINT
std::cout << signal_event::get_signal_name(SIGTERM) << std::endl; // Output: SIGTERM
std::cout << signal_event::get_signal_name(SIGUSR1) << std::endl; // Output: SIGUSR1
Practical example - Graceful shutdown
#include "libevent.h"
std::atomic<bool> running(true);
void handle_shutdown(int signum) {
std::cout << "\nReceived " << signal_event::get_signal_name(signum) << std::endl;
std::cout << "Shutting down gracefully..." << std::endl;
running = false;
}
int main() {
// Set handlers for shutdown signals
signal_event::set_signal_handler(SIGINT, handle_shutdown);
signal_event::set_signal_handler(SIGTERM, handle_shutdown);
std::cout << "Process ID: " << getpid() << std::endl;
std::cout << "Running... Press Ctrl+C to stop" << std::endl;
// Main loop
while (running) {
// Do work...
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Cleanup completed" << std::endl;
return 0;
}
Practical example - Inter-process communication
void handle_custom_signal(int signum) {
static int count = 0;
count++;
std::cout << "Received custom signal " << count << " times" << std::endl;
}
int main() {
signal_event::set_signal_handler(SIGUSR1, handle_custom_signal);
std::cout << "Process ID: " << getpid() << std::endl;
std::cout << "Send SIGUSR1 to this process using: kill -USR1 " << getpid() << std::endl;
// Wait indefinitely
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
Available signals
Common signals you can handle:
SIGHUP: Hangup detected on controlling terminalSIGINT: Interrupt from keyboard (Ctrl+C)SIGQUIT: Quit from keyboardSIGTERM: Termination signalSIGUSR1, SIGUSR2: User-defined signalsSIGALRM: Timer signal from alarm()SIGCHLD: Child process stopped or terminatedSIGPIPE: Broken pipeImportant notes:
SIGKILL and SIGSTOPwait_for_signal()The fs_event namespace provides a C++ implementation for monitoring file system events, similar to Python's pyinotify library. It uses Linux's inotify API to efficiently track changes to files and directories.
The fs_event namespace consists of several key components that work together to provide efficient file system monitoring:
fs_event_type (Enum)
Defines the types of file system events to monitor:
enum class fs_event_type : uint32_t
{
ACCESS, // File was accessed (read)
MODIFY, // File was modified
CREATE, // File/directory created
DELETE, // File/directory deleted
MOVED_FROM, // File moved out
MOVED_TO, // File moved in
OPEN, // File was opened
CLOSE_WRITE, // Writable file closed
// ... and more
};
Events can be combined using bitwise OR:
auto mask = fs_event_type::CREATE | fs_event_type::MODIFY | fs_event_type::DELETE;
fs_event_info (Structure)
Contains information about a file system event:
struct fs_event_info
{
int wd; // Watch descriptor
uint32_t mask; // Event type mask
uint32_t cookie; // Cookie for event synchronization
std::string pathname; // Parent directory path
std::string name; // File/directory name
bool is_dir; // True if event is for a directory
std::string get_full_path() const; // Get pathname + name
bool is_event(fs_event_type type) const;
std::string event_to_string() const;
};
process_event (Base Class)
Abstract base class for event handlers (similar to pyinotify.ProcessEvent):
class process_event
{
public:
// Override these methods to handle specific events
virtual void process_IN_CREATE(const fs_event_info& event);
virtual void process_IN_MODIFY(const fs_event_info& event);
virtual void process_IN_DELETE(const fs_event_info& event);
virtual void process_IN_OPEN(const fs_event_info& event);
virtual void process_IN_CLOSE_WRITE(const fs_event_info& event);
virtual void process_IN_MOVED_FROM(const fs_event_info& event);
virtual void process_IN_MOVED_TO(const fs_event_info& event);
// ... and more
virtual void process_default(const fs_event_info& event);
};
watch_manager (Class)
Manages file system watches using inotify (similar to pyinotify.WatchManager):
class watch_manager
{
public:
// Add a watch for a path
int add_watch(const std::string& path, fs_event_type mask, bool recursive = false);
// Remove a watch
bool remove_watch(int wd);
bool remove_watch(const std::string& path);
// Query watches
std::string get_path(int wd) const;
int get_wd(const std::string& path) const;
size_t get_watch_count() const;
};
notifier (Class)
Reads events and dispatches them to event handlers (similar to pyinotify.Notifier):
class notifier
{
public:
notifier(std::shared_ptr<watch_manager> wm,
std::shared_ptr<process_event> handler);
// Blocking: continuously process events
void loop();
// Non-blocking: process available events once
int process_events(int timeout_ms = 0);
// Stop the event loop
void stop();
};
Usage Examples
Basic Usage (Blocking Mode)
#include "libevent.h"
#include <iostream>
// Custom event handler
class my_event_handler : public fs_event::process_event
{
public:
void process_IN_CREATE(const fs_event::fs_event_info& event) override
{
std::cout << "Created: " << event.get_full_path() << std::endl;
}
void process_IN_MODIFY(const fs_event::fs_event_info& event) override
{
std::cout << "Modified: " << event.get_full_path() << std::endl;
}
void process_IN_DELETE(const fs_event::fs_event_info& event) override
{
std::cout << "Deleted: " << event.get_full_path() << std::endl;
}
};
int main()
{
// Create watch manager
auto wm = std::make_shared<fs_event::watch_manager>();
// Create event handler
auto handler = std::make_shared<my_event_handler>();
// Define events to monitor
auto mask = fs_event::fs_event_type::CREATE |
fs_event::fs_event_type::MODIFY |
fs_event::fs_event_type::DELETE;
// Add watch
wm->add_watch("/tmp/watch_dir", mask);
// Create notifier and start event loop
auto notifier = std::make_shared<fs_event::notifier>(wm, handler);
notifier->loop(); // Blocking
return 0;
}
Non-Blocking Mode
auto notifier = std::make_shared<fs_event::notifier>(wm, handler);
bool running = true;
while (running)
{
// Process events with 1 second timeout
int count = notifier->process_events(1000);
if (count > 0)
{
std::cout << "Processed " << count << " events" << std::endl;
}
// Do other work here...
}
Recursive Monitoring
// Watch directory and all subdirectories
wm->add_watch("/path/to/dir", mask, true); // recursive=true
std::cout << "Total watches: " << wm->get_watch_count() << std::endl;
Specific Event Monitoring
// Monitor only specific events
auto mask = fs_event::fs_event_type::MODIFY |
fs_event::fs_event_type::CLOSE_WRITE;
wm->add_watch("/path/to/file", mask);
Each method of event handling/processing has its own advantages and disadvantages. It is important to carefully evaluate the specific requirements and constraints of your application before choosing the appropriate technique (such as performance, complexity, flexibility, scalability...).
Pros:
event_handler acts as a centralized handler for all event messages, easy to manage and organize source codes.Cons:
Pros:
Cons:
Pros:
Cons:
Pros:
Cons:
Use cases:
Pros:
trigger_if_not_set is atomicCons:
reset() to allow next triggerwait() can cause deadlocksUse cases:
Pros:
poll()Cons:
epoll/kqueue for high-performance)Pros:
siginfo_t to get sender detailsCons:
SIGKILL and SIGSTOP cannot be caughtUse cases:
Pros:
Cons:
/proc/sys/fs/inotify/max_user_watches)https://github.com/endurodave/StdWorkerThread
https://stackoverflow.com/questions/9711414/what-is-the-proper-way-of-doing-event-handling-in-c
https://gameprogrammingpatterns.com/event-queue.html