C++ RAII at the process-level

C++ RAII at the process-level

Problem: Your application is a webserver with an associated ODBC connection pool (per-process pool), you want to intercept process termination/interruption signals and use a Lambda function as a callback, in order to release resources and stop the webserver properly. You can't, std::signal won't accept a Lambda, and you want a Lambda because the webserver is a local variable in main() and you don't want to use global variables and an external signalHandler() function, you want everything solved within the scope of main() and using the capture features of a C++ Lambda.

The solution? a simple C++ struct than can be encapsulated in a header file, serves as an indirect way to allow the use of a Lambda defined in main(). Here it is:

No alt text provided for this image

We confront another limitation, a struct's member function cannot be used as a function pointer for std::signal, not even with the help of std::mem_fn, but a struct's static function can be used for std::signal, that's why have to play with "static" in all cases for our simple struct.

This limitation is very well explained here, by Bjarne Stroustrup (my guess):

Back to our design, a static init(...) function replaces a normal constructor because we need to access a static variable signalHandlerFunc, which will contain a reference to a Lambda that represents a std::function<void(int)>. In turn, std::signal will use our static callback signalHandler(), which is acceptable as a function pointer for this system call, and this function wraps the reference to the Lambda (which cannot be passed as a pointer to std::signal), that's all the generic mechanism. The program using this code does not know about the signals intercepted, it only cares about the tasks to be executed inside the Lambda, and this struct does not know about the Lambda, it only serves the purpose of encapsulation, registering the signals and invoking the lambda if a signal gets intercepted.

The client code using this simple component

No alt text provided for this image

At startup, the ODBC pool is initialized with odbcInit() as well as the HTTPS web server, right after that, we want to register the signal handler using a Lambda, we can do it using our struct SignalManager and its static function init(), which receives the Lambda function, we will release the ODBC pool and stop the webserver and its threads, after that, a clean exit.

SignalManager registers 2 commonly used signals on Windows and Linux, SIGINT and SIGTERM, which is enough for CTRL-C, Windows service stop, and Linux kill (with default value or SIGTERM). If we decide to intercept more signals in the future, the main() client code won't be affected, the same for the SignalManager component, it does not care about the contents of the Lambda, it's only task is to execute it on a signal interception.

It's (according to me!) a process-level RAAI, we acquire process-level resources at startup, like the webserver and ODBC pool, and we release them on process termination, and we do it with a clean, simple mechanism, no global variables, just plain modern C++ with a reasonable separation of concerns between the parties involved.








Charaf E.ChatBi

Exploring Equations 𝑦 = 𝑓(𝜙(𝑦))

2y

Brilliant.

To view or add a comment, sign in

Insights from the community

Others also viewed

Explore topics