Odds and ends: the Observer pattern
Welcome back.
This week, the first of a series of three posts on the implementation of design patterns in QuantLib.
Also, it’s still possible to register for my next course. For info, click on the links in the banner above. Unless you’re reading a month or two from now and the banner is no longer there. But in that case, the course would be done and there would be no point in clicking, so we’re good either way.
Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.
Design patterns
A few design patterns were implemented in QuantLib. You can refer to the Gang of Four book [1] for a description of such patterns; so why do I write about them? Well, as once noted by G. K. Chesterton,
[p]oets have been mysteriously silent on the subject of cheese
and the Gang was just as silent on a number of issues that come up when you write actual implementations—through no fault of them, mind you. The variations are almost limitless, and they were only four.
Thus, I will use these posts to point out a few ways in which our implementations were tailored to the requirements of the library.
The Observer pattern
The use of the Observer pattern in the QuantLib library is widespread; you’ve seen it used in past series of posts to let financial instruments and term structures keep track of changes and recalculate when needed.
Our version of the pattern (sketched in the next listing) is close enough to that described in the Gang of Four book; but as I mentioned, there are questions and problems that weren’t discussed there.
For instance: what information should we include in the notification?
In our implementation, we went for minimalism—all that an observer
gets to know is that something changed. It would have been possible to
provide more information (e.g., by having the update
method
take the notifying observable as an argument) so that observers could
select what to recalculate and save a few cycles; but I don’t think
that this feature was worth the added complexity.
Another question: what happens if an observer raises an exception from
its update
method? This would happen when an observable is
sending a notification, i.e., while the observable is iterating over
its observers, calling update
on each one. If the exception
were to propagate, the loop would be aborted and a number of observers
would not receive the notification—bad. Our solution was to catch
any such exception, complete the loop, and raise an exception at the
end if anything went wrong. This causes the original exceptions to be
lost, which is not good either; however, we felt this to be the lesser
of the two evils.
Onwards to the third issue: that is, copy behavior. It is not very clear what should happen when an observer or an observable are copied. Currently, what seemed a sensible choice is implemented: on the one hand, copying an observable results in the copy not having any observer; on the other hand, copying an observer results in the copy being registered with the same observables as the original. However, other behaviors might be considered; as a matter of fact, the right choice might be to inhibit copying altogether.
The big problems, however, were two. First: we obviously had to make sure that the lifetimes of the observer and observables were managed properly, meaning that no notification should be sent to an already deleted object. To do so, we had observers store shared pointers to their observables, which ensures that no observable is deleted before an observer is done with it. The observers will unregister with any observable before being deleted, which in turn makes it safe for observables to store a list of raw pointers to their observers.
This, however, is only guaranteed to work in a single-threaded
setting; and we are exporting QuantLib bindings to C# and Java,
where unfortunately there is always another thread where the garbage
collector is busy deleting stuff. Every once in a while, this caused
random crashes as a notification was sent to a half-deleted object.
Once the problem was understood, it was fixed (hi, Klaus) by means of
an undocumented hook in the implementation of
boost::shared_ptr
; I’m not sure how this will work out when we
switch to std::shared_ptr
in the future. Also, the fix slows
down the code, so it’s inactive by default and can be enabled by a
compilation switch. Use it if you need the C# or Java bindings.
The second big problem is (notice that I didn’t say was) that, like
in the Jerry Lee Lewis’ song, there’s a whole lotta notifyin’ going
on. A change of date can easily trigger tens or hundreds of
notifications; and even if most of the update
methods only set a
flag and forward the call, the time adds up.
People using QuantLib in applications where calculation time is
paramount, such as CVA/XVA (hi, Peter) have worked
around the problem by disabling notifications and recalculating
explicitly. A step towards reducing notification time would be to
remove the middlemen, and shorten the chains of notifications;
however, this is not possible due to the ubiquitous presence of the
Handle
class in the chains. Handles can be relinked, and thus
chains of dependencies can change even after objects are built.
In short, the problem is still not solved. You know where to find us if you have any bright ideas.
Bibliography
[1] E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns: Element of Reusable Object-Oriented Software. Addison-Wesley, 1995.