Event handling in C and C++

One of the most-occurring subpatterns I keep running into lately is an event-handling subpattern. I say subpattern because it is not a pattern in and of itself: it can be part of an observer pattern, a state machine, or of any other pattern in which objects that have neither an “is-a” nor an explicit “has-a” relationship have to send messages to each other. I run into this when I’m doing C and when I’m doing C++, and the patterns that this is part of are pretty varied, but the way I handle this subpattern in both languages is almost identical. So I thought I’d write a post about it.

[Are_PayPal_LoginPlease]
I’ll split this post up into a few sections, treating each of the important parts of event handling as I see it. These parts are: important considerations using events (the guarantees that an event handler must give to the called, thread-safety considerations for event handlers and cascading events (events generating events)); patterns in event handling (observer pattern, signals and slots, message pump).

Considerations using events

There are several caveats you need to take into account when writing an event handler, especially if you don’t know beforehand in what kind of context your event handler will be used or if you know that that context is going to be a time-critical one.

When writing event handlers, you should consider the call to your event handler as a courtesy of the caller: it is notifying you of an event so you can do something interesting – and so you don’t need to poll to see if anything has changed. As such, you should be generally well-behaved and shouldn’t burden the event source more than necessary.

Guarantees

If you’ve been reading this blog (perhaps I should start calling it a column, as some other oligo-maniacs with blogs do theirs) for a while, you’ll know what I mean by the guarantees a function can give, but I’ll briefly recap: there are three types of guarantees: the basic guarantee (application and object invariants will continue to hold despite failure); the strong guarantee (basic + on failure, there will be no visible effect to application or object state) and the no-fail guarantee (there can be no visible failure).

Any event handler should provide the no-fail guarantee.

The reason for this is simple: whichever process, function, etc. created the event, they are simply creating an event and don’t necessarily care how the event is handled. There is very likely nothing that could be done with the failure, aside from perhaps telling the wetware (humans), if any is present. What that means, though, is that although am event handler may not visibly fail, it may fail “invisibly” – i.e. without that failure being visible to the caller. That would mean, though, that the event would possibly have to create another event, indicating its own failure. We’ll get to the ramifications of that later.

In C, this means that the function should never longjmp(), nor should it return any value, so a typical prototype of an event handling function would look like this:

typedef void (*EventHandler)();

Note that I wrote this as a pointer to a function? That’s because one of the traits of an event handler is that they are usually anonymous: the code that generates the event is completely de-coupled from the code that consumes the event, which is one of the main advantages of event handling patterns, described below, and one of the main reasons we use events in the first place.

Thread-Safety Considerations

When an event handler is called, the caller (the source of the event) may be in any kind of state and may or may not be in a state that is coherent as far as the outside world is concerned. Even the most strictly re-entrant functions may put the objects they work on in an incoherent state for a few moments, and may have events to generate while those objects are in an incoherent state. Event handlers should, therefore, avoid working with such objects as may be used by the source of the events they work on.

Similarly, the source of the event may hold any number of locks and those locks may not be recursive, so trying to acquire those locks may very well result in a deadlock. Event handlers should therefore avoid acquiring any locks that are not strictly restricted to use by the event handlers in question. Even in those cases, it isn’t good manners to hijack the thread that generated the event to start doing all kinds of housekeeping tasks, so if it can be avoided at all, no locks should be acquired by the event handlers. If the event handler uses abstract data types (queues, tables, etc.) it should, as much as possible, use lock-free versions of those ADTs in order to avoid holding up the event source.

Cascading Events

The issues concerning cascading events (events generating events) is similar: the event’s generator shouldn’t be burdened with a while cascade of events when it only wanted to notify you of a specific event. Think of how you feel when you report an incident (e.g. someone took your car for a place to park) to the police and they have you fill out a whole stack of paperwork: shouldn’t that be their job? Similarly, the event generator may be under a time constraint that, when you’re writing the event handler, you don’t know about (or may not even be in effect at that time).

If you do have to generate events in your event handler, do so asynchronously: create the event object (we’ll get to that) and hand it to some asynchronous event handler.

System calls

I guess by now the theme is starting to become obvious, but as most system calls that might be of any use (e.g. sending things over the network, writing to files, reading from files or sockets, allocating memory, etc.) usually involves locks, waiting, etc., these are all things that should be avoided in event handlers, and should be done asynchronously, if at all possible.

Patterns Using Events

There are several patterns that use events at their core, some of which are more evidently event-based than others. I will list a few of them here and show how those patterns could be implemented.

The Observer Pattern

The Observer pattern is doubtlessly one of the better known design patterns, period. I once based the entire architecture of an agent in a distributed system on the Observer pattern because of this. It is also doubtlessly one of the most-used patterns when it comes to event handling. A generic implementation of an Observer class might look like this (untested code):

#ifndef vlinder_observers_observer_h
#define vlinder_observers_observer_h
 
namespace Vlinder { namespace Observers {
	template < typename Event >
	class Observer
	{
	public :
		Observer()
		{ /* no-op */ }
 
		virtual ~Observer()
		{ /* no-op */ }
 
		void update(const Event &event) throw()
		{
			try
			{
				update_(event);
			}
			catch (...)
			{ /* observer must provide the no-fail guarantee,
			   * so we enforce the guarantee, without resorting
			   * to std::unexpected, which would be bad manners */ }
		}
	protected :
		virtual void update_(const Event &event) = 0;
 
	private :
	};
}}
 
#endif

As you can see, this implementation takes care to make sure that no unexpected exceptions get through (and std::unexpected doesn’t get called either). As the comment indicates, I consider it to be bad manners to resort to std::unexpected when a derived class is ill-behaved and throws an exception of its own: there should be no reason for it to do so, but there should certainly be no reason for my code to cause an abort if the derived class is ill-behaved.

Aside from the Observer, the pattern consists of another participant object: the Subject. A generic implementation of a Subject class might look like this (also untested code):

#ifndef vlinder_observers_subject_h
#define vlinder_observers_subject_h
 
#include "Observer.h"
 
namespace Vlinder { namespace Observers {
	template < typename Event, typename ObserverType_ = Observer< Event > >
	class Subject
	{
	public :
		typedef ObserverType_ ObserverType;
 
		Subject()
		{ /* no-op */ }
 
		virtual ~Subject()
		{ /* no-op */ }
 
		virtual void attach(ObserverType *observer) = 0;
		virtual void detach(ObserverType *observer) = 0;
	};
}}
 
#endif

The subject of observation is the generator of the events observed by the observers. Observers can be attached to, or detached from, the Subject. Of course, the detach method, like any deleter/destructor/eraser, should give the no-fail guarantee. The attach method, on the other hand, only needs to give the strong guarantee: it should either attach the observer, or not; and if not, fail ostensibly in doing so. We pass a pointer to the observer so the subject should not take ownership of it.

We can implement both an observer and a subject by implementing a useful container of observers, called Observers, as follows (untested code):

#ifndef vlinder_observers_observers_h
#define vlinder_observers_observers_h
 
#include "Observer.h"
#include "Subject.h"
#include "Details/Lock.h"
#include <list>
 
namespace Vlinder { namespace Observers {
	namespace Details {
		template < typename T >
		struct List
			: std::list< T >
		{
		};
	}
	template <
		typename Event,
		bool mt__,
		template < class > class Container = Details::List >
	class Observers
	: public Observer< Event >
	, public Subject< Event >
	{
	public :
		typedef typename Subject< Event >::ObserverType ObserverType;
 
		Observers()
		{ /* no-op */ }
 
		~Observers()
		{ /* no-op */ }
 
		virtual void attach(ObserverType *observer)/* = 0*/
		{
			ScopedLockType lock(lock_);
			(void)lock;
			observers_.push_back(observer);
		}
 
		virtual void detach(ObserverType *observer)/* = 0*/
		{
			using std::find;
			ScopedLockType lock(lock_);
			(void)lock;
			Iterator which(
				find(
					observers_.begin(),
					observers_.end(),
					observer));
			if (which != observers_.end())
			{
				observers_.erase(which);
			}
			else
			{ /* not found - not a problem */ }
		}
 
	protected :
		virtual void update_(const Event &event)/* = 0*/
		{
			ScopedLockType lock(lock_);
			(void)lock;
			for (
				Iterator curr(observers_.begin());
				curr != observers_.end();
				++curr)
			{
				(*curr)->update(event);
			}
		}
 
	private :
		typedef Container< ObserverType* > ContainerType;
		typedef typename ContainerType::iterator Iterator;
		typedef typename Details::GetLockType< mt__ >::type LockType;
		typedef typename Details::GetLockType< mt__ >::scoped_type ScopedLockType;
 
		ContainerType observers_;
		LockType lock_;
	};
}}
 
#endif

As you can see, I went to the trouble of allowing the user of the class to override my choice of containers, and in doing so, worked around the fact that the container classes in the STL take an allocator type as well as an element type as template parameters. That is not the important part of this code, however: the important thing to see is that we have a container of observers – or rather: pointers to observers, seeing as the subject does not take ownership and as Observer is polymorphic – and that we call all of the observers in that container when an event occurs. Note that this relies rather heavily on update giving a no-fail guarantee: if it did fail, some of the observers would not be updated.

This implementation also acquires a lock on three different occasions. The alternative would have been to use a non-blocking list, which would certainly have been better taking into account all I said above about the caveats of event handling. Using one here, however, is beyond the scope of this article and left as another exercise to the reader.

For completeness, I will include the meta-function that’s called in this code to get the lock’s type:

#ifndef vlinder_observers_details_lock_h
#define vlinder_observers_details_lock_h
 
#include <boost/thread/mutex.hpp>
 
namespace Vlinder { namespace Observers { namespace Details {
	template < bool mt__ >
	struct GetLockType
	{
		typedef boost::mutex type;
		typedef boost::mutex::scoped_lock scoped_type;
	};
 
	template <>
	struct GetLockType< false >
	{
		typedef int type;
		typedef int scoped_type;
	};
}}}
 
#endif

Signals and Slots

A well-known variant on observers, signals and slots offer different semantics and slightly different usage patterns for the same design pattern: signals and slots are connected together such that when an event is generated, it is signalled and the signal is brought to all of the connected slots. Perhaps the best-known implementation in the world of C++ is the one found in Qt, but Boost provides an arguably better implementation.

I won’t go into the details of either implementation. Neither of them is ideal for all purposes – but then, no implementation of anything ever is. Both of them run into at least one of my caveats in that they, like the implementation of the Observer pattern presented above, use locks for mutual exclusion for internally managed containers of slots (observers) (Boost actually has two versions of its signals library, one of which is thread-safe and uses locks, the other one isn’t thread-safe and doesn’t use locks).

There is a legitimate reason why you might want to use signals and slots rather than observers and subjects: while subjects are already far de-coupled from their observers and vice-versa, there is still some relationship between them in that their types are often related, albeit loosely, as described above. This last remnant of coupling is gone when you use signals and slots. This has the advantage of being able to implement the one without any knowledge whatsoever of the other, but has the disadvantage of being more difficult to maintain in many cases.

The Message Pump

Any Windows programmer will know this particular pattern: any window in Windows has a message queue associated with it, to which messages can be posted with SendMessage and PostMessage, the two differing only slightly in their semantics. Messages are consumed from the queue in a loop until one of the messages indicates that the loop should break. The messages, in the Windows implementation, are possibly inter-process and contain a set of integers, which can be used to carry pointers as well. One of the integers in question is the type of the message, which is one of the WM_* values and a few of those values are reserved for use by the user.

This pattern is surprisingly flexible: because you can associate a message queue with any number of windows (but only one message queue per window) and because every window must have a message queue associated with it, the handle to the window can be used as a handle to the message queue, and the whole thing becomes pretty close to transparent. Anything the user does in the interface (moving the mouse, clicking, etc.) becomes a message that is filtered at the entry of the queue, so messages that aren’t interesting to the message queue aren’t put in it, and at its exit, where they are handled. This automatically means that, by default, the thread that “pumps” the messages (i.e. takes them from the queue and handles them) is the thread that handles almost anything related to the window; and because messages are only a bunch of integers, they can be copied around without any worries of failing, exception-throwing copy constructors and some such. Messages are also fixed in size, so the queue itself is pretty easy to implement. The Windows message queue has another feature that may or may not be interesting, depending on the context it is used it (but usually is a good feature): messages have priorities that are based on the numeric value of the message type. If judiciously chosen (which the ones in Windows are, more or less), a scheme like this means that more important messages arrive before less important ones. This may or may not actually be what you want: sometimes, you might want the messages to arrive in the order they were sent (FIFO order) rather than according to some more or less arbitrary sort…

There are, of course, diverse other implementations of message queues and message pumps: a very different implementation of a message queue, which provides for enterprise-bus communications (guaranteed delivery etc.) if the well-known MQ, implemented, among others, in IBM’s WebSphere platform. Several SOAs are based on the availability of MQ to report events, which can be stored and treated in batches. This, however, is event handling (and reporting) on a wildly larger scale than the kind of event handling we’ve been talking about so far. The operating principles are still the same, however: the generator of the events simply puts the event in a queue and whoever consumes the event will do something with it, without bothering the source about it. What MQ adds, however, is the guaranteed delivery aspect, meaning the generator of the event might be interested in knowing that the transaction that was to deliver the event actually successfully took place.

An API for a generic message queue could look like this one:

#ifndef vlinder_messagequeue_messagequeue_h
#define vlinder_messagequeue_messagequeue_h
 
#include "Message.h"
 
#define VMQ_S_OK	0
#define VMQ_S_EMPTY	1
#define VMQ_E_FAIL	0x80000001
/* ... */
 
#define VMQ_F_MT	0x00000001	// multi-threaded
#define VMQ_F_PERSIST	0x00000002
#define VMQ_F_VOLATILE	0x00000004	// incompatible with Persist
#define VMQ_F_IPC	0x00000008	// if volatile flag is not set, persist is assumed
#define VMQ_F_FIFO	0x00000010	// if not set, the queue is a priority queue sorted by type (lowest type == higher priority)
#define VMQ_F_LOCKFREE	0x00000021	// assumes multi-threaded
 
typedef struct VMQ_struct VMQ;
typedef void*(*VMQAllocator)(void*,size_t);
typedef void (*VMQDeallocator)(void*,void*);
 
int VMQ_alloc(VMQ **queue, VMQAllocator alloc, VMQDeallocator dealloc, size_t size, int flags, ...);
void VMQ_free(VMQ *queue);
 
int VMQ_push(VMQ *queue, VMQMessage *message, size_t size);
int VMQ_peek(VMQ *queue, VMQMessage *message, size_t *size);
int VMQ_pop(VMQ *queue, VMQMessage *message, size_t *size);
 
#endif

This is actually the header file of one of my generic implementations, though I’ve stripped it down a bit for brevity. The message looks like this

#ifndef vlinder_messagequeue_message_h
#define vlinder_messagequeue_message_h
 
#define VMQ_MESSAGE_MAXSIZE	128
#define VMQ_MESSAGE_ALIGNSIZE	(sizeof(uint64_t))
#define VMQ_MESSAGE_PAD1SIZE	(VMQ_MESSAGE_ALIGNSIZE - sizeof(unsigned long))
#define VMQ_MESSAGE_PAD2SIZE	(VMQ_MESSAGE_MAXSIZE - (sizeof(unsigned long) + VMQ_MESSAGE_PAD1SIZE + VMQ_MESSAGE_ALIGNSIZE))
 
typedef struct VMQMessage_struct VMQMessage;
struct VMQMessage_struct
{
	unsigned long type_;
	unsigned char pad1_[VMQ_MESSAGE_PAD1SIZE];
	uint64_t align_;
	unsigned char pad2_[VMQ_NESSAGE_PAD2SIZE];
};
 
#endif

and is designed to be able to hold any message structure that has an unsigned long at its start and is at most 128 bytes in length. As you can see, I use basically the same scheme as POSIX sockaddr_storage to do that.

Conclusion

Event handling is a widely-used practice in C and C++. There are some caveats to take into account when writing event handlers, and there are a few interesting, well-documented and widely-used patterns for event handling. Events can be sent within a thread, to other threads or to other processes, and those other processes may well be on distant machines. In any of those cases, the way event handling works in general remains largely the same, which makes it one of those fundamentals you really should know about and be able to work with, regardless of what kind of programming you do.
[/Are_PayPal_LoginPlease]

About rlc

Software Analyst in embedded systems and C++, C and VHDL developer, I specialize in security, communications protocols and time synchronization, and am interested in concurrency, generic meta-programming and functional programming and their practical applications. I take a pragmatic approach to project management, focusing on the management of risk and scope. I have over two decades of experience as a software professional and a background in science.
This entry was posted in C & C++, Software Development. Bookmark the permalink.