Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Object-level signal delivery in C++

4 views
Skip to first unread message

lpw

unread,
Nov 1, 2005, 6:11:17 PM11/1/05
to
I've been racking my brain on how to incorporate signal handling into my C++
programs whilst remaining one with the OO nature. One common suggestion on
delivering a signal to an object is to do it via a global variable (a
pointer to the object) and a wrapper function, a technique that is generally
a Bad Idea (due to the usage of a global variable). I would like to propose
a different solution, one that, IMHO, is a little cleaner and fits better
with the OO paradigm than simply using global object pointers.

Suppose that we have a program that does a lot of I/O (sockets, pipes,
files, etc.). For each I/O "stream", we have a dedicated handler object.
When the user becomes bored and sends SIGINT to our program, we would like
all our handlers to cleanly close their respective TCP connections, IPC
pipes, etc. With things like connection-oriented sockets, this is often
more involved than merely calling close(). The responsibility to cleanly
shut down a particular I/O "stream" should be delegated to that stream's
handler object. Thus, we need object-level signal delivery to a number of
heterogeneous objects. This can be accomplished by having all those objects
derive from a common base class. The responsibility of that class (let's
call it Interruptible) is to maintain a list of all live interruptible
objects and invoke the signal handlers of those objects whenever SIGINT is
caught. The code below illustrates this paradigm using a silly Runner class
instead of an I/O handler. But the idea remains the same.

#include <iostream>
#include <string>
#include <list>
#include <cstdlib>
#include <ctime>
#include <signal.h>
#include <unistd.h>

using namespace std;

class Interruptible {

// "global" list of live interruptible objects
static list<Interruptible*> instances;

protected:

// all interruptible classes must implement this method
virtual void sigint_handler() = 0;

Interruptible() {
// add me to global list of live interruptible objects
instances.push_back(this);
}

virtual ~Interruptible() {
// remove me from global list of live interruptible objects
instances.remove(this);
}

public:

// the "global" signal handler
static void sigint_handler(int s) {
// call all interruptible objects
list<Interruptible*>::const_iterator i;
for (i=instances.begin(); i!=instances.end(); i++)
(*i)->sigint_handler();
// goodbye
exit(0);
}
};

list<Interruptible*> Interruptible::instances;

class Runner : public Interruptible {

string name;

virtual void sigint_handler() {
cout << name << " got SIGINT " << endl;
//
// do some cleanup here
//
}

public:

Runner(const string& str) : Interruptible(), name(str) { }

void run() {
cout << name << " is running" << endl;
//
// do something smart and useful here
//
sleep(1+rand()&3);
}
};

int main(void) {

signal(SIGINT, Interruptible::sigint_handler);

srand(time(NULL));

Runner obj1("Runner 1");
Runner obj2("Runner 2");

while (true) {
obj1.run();
obj2.run();
}
}

I have posted a similar question to comp.lang.c++, but I'm also posting here
to get some responses from a UNIX programmer's perspective. Any suggestions
are appreciated. I would like to hear of how other people are dealing with
this issue. Oh yes -- and let's forget about thread safety for now. Just
assume that all this is running in a single-threaded process.


Eric Sosman

unread,
Nov 1, 2005, 6:40:47 PM11/1/05
to

lpw wrote On 11/01/05 18:11,:
> [call object methods from a signal handler -- see up-thread]


>
> I have posted a similar question to comp.lang.c++, but I'm also posting here
> to get some responses from a UNIX programmer's perspective. Any suggestions
> are appreciated. I would like to hear of how other people are dealing with
> this issue. Oh yes -- and let's forget about thread safety for now. Just
> assume that all this is running in a single-threaded process.

While the signal handler is active, your program has
a second thread running. It's not a true "thread" in the
POSIX sense, but it is a second instruction stream. In
fact, it's worse than a second thread because most (all?)
of the pthread_xxx functions are strictly off-limits to
signal handlers. You daren't call malloc or free, and I
imagine this means you can't use new and delete. Nor can
you do I/O on the C FILE* streams, and I imagine this means
you can't use the C++ I/O gadgetry either. Semaphores are
OK and so is low-level I/O via read and write, but there's
not a whole lot else.

Also, what do you think will happen when the main program
is cranking away and the signal handler makes an asynchronous
call to some object's methods at a completely unpredictable
instant? Nothing pleasant, I'll warrant.

You might be able to rescue some parts of your scheme
by using sigwait instead of old-style asynchronous signals.
This, of course, would put you firmly in the multi-threaded
world, and you'd need to code accordingly.

--
Eric....@sun.com

Paul Pluzhnikov

unread,
Nov 1, 2005, 10:50:15 PM11/1/05
to
"lpw" <lwawr...@hotmail.com> writes:

> OO nature. One common suggestion on
> delivering a signal to an object is to do it via a global variable (a
> pointer to the object) and a wrapper function, a technique that is generally
> a Bad Idea (due to the usage of a global variable).

What makes you believe that "static list<Interruptible*> instances;"
is not a global variable?

> I would like to propose
> a different solution, one that, IMHO, is a little cleaner and fits better
> with the OO paradigm than simply using global object pointers.

It *is* simply using a global list of object pointers, and as Eric
has already told you, you are extremely restricted in what the
(overloaded) Interruptible::sigint_handler() may call.

> I would like to hear of how other people are dealing with
> this issue.

Just about the only thing you can safely do in a single-threaded
signal handler is set a flag variable. Pretending otherwise will
simply produce a chain of programs that *sometimes* work, and that
resist all attempts to make them *always* work :(

Cheers,
--
In order to understand recursion you must first understand recursion.
Remove /-nsp/ for email.

Ulrich Eckhardt

unread,
Nov 2, 2005, 1:53:01 PM11/2/05
to
lpw wrote:
> I've been racking my brain on how to incorporate signal handling into
> my C++ programs whilst remaining one with the OO nature.

OO is a tool, not a goal.

> One common suggestion on delivering a signal to an object is to do it
> via a global variable (a pointer to the object) and a wrapper function,
> a technique that is generally a Bad Idea (due to the usage of a global
> variable).

OK, let's think about it: a signal handler is a function that takes no
parameters except the signal. So, the only other context it has is globals
(in the sense of universally present and accessible) and function-static
variables.

> I would like to propose a different solution, one that, IMHO, is a
> little cleaner and fits better with the OO paradigm than simply using
> global object pointers.

> class Interruptible {


>
> // "global" list of live interruptible objects
> static list<Interruptible*> instances;
>
> protected:
>
> // all interruptible classes must implement this method
> virtual void sigint_handler() = 0;
>
> Interruptible() {
> // add me to global list of live interruptible objects
> instances.push_back(this);
> }
>
> virtual ~Interruptible() {
> // remove me from global list of live interruptible objects
> instances.remove(this);
> }
>
> public:
>
> // the "global" signal handler
> static void sigint_handler(int s) {
> // call all interruptible objects
> list<Interruptible*>::const_iterator i;
> for (i=instances.begin(); i!=instances.end(); i++)
> (*i)->sigint_handler();
> // goodbye
> exit(0);
> }
> };

Some notes here:
- 'instances' is a global that is just put into the namespace of a class.
- "Law of Three"
- Thread safety.
- prefix/postfix increment for iterator, std::for_each.

> Oh yes -- and let's forget about thread safety for now. Just
> assume that all this is running in a single-threaded process.

Hmmm-humm. Due to the difficult handling of signals and possible deadlocks,
I'd rather avoid giving them a 'nice' interface that invites people to
treat them lightly. Already in your example, invoking cout in the handler
might cause problems, what if cout was just in the process of reshuffling
some buffers for some other output when the signal arrives and now you add
another call on top of that?

Set a flag in the signal handler and return, checking that flag regularly.
Optionally, if the flag is set already, exit().

Uli

--
http://www.erlenstar.demon.co.uk/unix/

0 new messages