Boost logo

Boost :

Subject: Re: [boost] [signals2][review] The review of the signals2 library(formerly thread_safe_signals) begins today, Nov 1st
From: Frank Mori Hess (frank.hess_at_[hidden])
Date: 2008-11-04 17:47:00


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Tuesday 04 November 2008 10:14 am, vicente.botet wrote:
> - The number of parameters of the signal class starts to be too high.

I agree. My inclination is to remove the SlotFunction and
ExtendedSlotFunction parameters, but I'm not going to do it based only on my
personal inclination unless something like a consensus arises that it is a
good idea.

> The use of the Boost.Parameter library could simplify this.

I've never used Boost.Parameter. It does seem useful for this kind of
situation, I guess my question would be: is there any reason _not_ to use
Boost.Parameter? It doesn't seem to currently be used by other boost
libraries except for some test programs. Is that just because it is
relatively new?

> - I don't like too much the Mutex template parameter, I would prefer a more
> declarative template parameter as single_threaded|multi_threaded<>.

It seems like mostly a matter of taste, so I don't expect to convince you of
anything, but I'll just summarize why I prefer Mutex. 1) It is a very simple
concept. 2) Users of Boost.Thread are already familiar with it. 3) It is in
the same spirit as the SlotFunction parameter from Boost.Signals. 4) There
is precendence in at least one other Boost library, pool_alloc does something
very similar:

http://www.boost.org/doc/libs/1_36_0/libs/pool/doc/implementation/pool_alloc.html

Maybe I'm missing your point though, as I see now below your multi_threaded<>
parameter itself takes a Mutex parameter in turn.

> The fact that the library adds a new mutex class for performance issues
> must documented.

Are you talking about signals2::dummy_mutex or signals2::mutex? And can you
be more specific about the holes in their documentation?

> - Well the implementation is based on the monitor pattern. The signal class
> protects itself from multiple threads access locking each operation with an
> internal mutex. This means that we need to lock/unlock a mutex each time a
> signal must be invoked, connected, disconnected, ....
> This coudl be expensive for some applications. I would like to be able to
> use an externally locked mutex allowing to lock only once for several
> signals. This can be obtained by adding a LockingStrategy template
> parameter to the multi_threaded (internally_locked | externally_locked).
>
> template <typename Mutex=mutex, typename LockingStrategy
> =internally_locked> struct multi_threaded {
> // ...
> };
>
> When externally_locked is choosen the user needs to get a signal handle
> providing it has locked the mutex (maybe using a strict lock as parameter)
>
> typedef signal<void (), ..., multi_threaded<mutex,externally_locked> >
> my_signal1;
> typedef signal<void (), ..., multi_threaded<mutex,externally_locked> >
> my_signal2;
>
> mutex mtx;
> my_signal1 sig1(mtx);
> my_signa12 sig2(mtx);
>
> {
> scoped_guard lock(mtx); // locked only once
> my_signal1::handle hsig1 = sig1.get_handle(lock)
> my_signal2::handle hsig2 = sig2.get_handle(lock)
> // use hsig1/2 as you will use sig1/2
> // ...
> } // // unlocked only once

I've thought a little about some kind of external locking to handle the
particular situation of an atomic get/modify/set combiner operation. I don't
personally find what you're describing above compelling enough for me to
implement though, plus there is the added interface complexity. Couldn't you
implement the scheme you describe above on top of signals2, using a signal
with a dummy_mutex? You should also consider that holding the mutex while
invoking slots opens the possibility of deadlock, since that establishes a
locking order between the signal's mutex and whatever the slot may acquire.

> - For compatibility purposes the signal class must have as default
> parameter a dummy_mutex.
> The library can define a basic_signal template class and two
> specializations, signal (single_threaded) and ts_signal (multi_threaded).
>
> - For compatibility purposes, the optional_last_value should replace
> last_value as the default combiner for signals on multi_threaded
> environements, but not on single_threaded environements.
>
> - For compatibility purposes, preserve the connection block() and
> unblock() methods. Note that this follows the same pattern as lock/unlock
> and scoped_guard. You can state that these are depreceated and remove them
> two or three releases after.
>
> - For compatibility purposes the tractable and visit_each classes must be
> preserved with a clear notice that this are not thread-safe. You can state
> that these are depreceated and remove them two or three releases after.

I don't believe compatibility should drive the default parameters for
signals2. At the extreme, a complete compatibility layer could be provided
on top of signals2 (but sequestered in seperate headers, maybe under
boost/signals2/compat), which provides exactly the Boost.Signals interface,
and uses dummy_mutex underneath.

Doug suggested some time ago a staged introduction (which seems like a good
idea to me), where the original Boost.Signals would stay around for a few
releases after Signals2 is introduced. That would seem to cover the
two "deprecate/remove later" cases you suggest above.

It seems to me the big issue for backwards compatibility is what do we need to
keep long-term in Signals2, even after people have had a transition period.
It seems to me losing boost::trackable completely would cause people the most
headaches.

> - Be coherent with the library name and the header protecting macro
> #ifndef BOOST_TS_
> #ifndef BOOST_TSS_
> #ifndef BOOST_SIGNALS2_
> #ifndef BOOST_SIGNALS_COMMON_MACROS_HEADER
> #ifndef BOOST_DECONSTRUCT_PTR_HEADER
> #ifndef BOOST_LAST_VALUE_HPP
> #ifndef BOOST_SHARED_CONNECTION_BLOCK_HEADER
> #ifndef _THREAD_SAFE_SIGNAL_HPP
>
> BOOST_SIGNALS2_ seems the best prefix.

Yes, I actually fixed this recently. It seems the change even slipped into
the second signals2 zip file I put in the vault, in spite of my claim there
that the only change was fixing compile errors on windows.

> - Use specific macros instead of
> #define BOOST_SIGNALS_NUM_ARGS BOOST_PP_ITERATION()
> use
> #define BOOST_SIGNALS2_NUM_ARGS BOOST_PP_ITERATION()

Unfortunately I didn't get around to making the names consistent on the macros
provided for the boost preprocessor stuff. But yes, it should be cleaned up.

> * What is your evaluation of the documentation?
> I was surprised that the documentation do not differs too much from the
> initial Boost.Signal. IMO the new features of the library are not enough
> documented on the tutorial.
>
> The fact that this library plans to replace the Boost.Signal library must
> be documented on the introduction.
>
> The section Q&A: How has the API changed from the original Boost.Signals?
> must be placed at the begining of the docummentation and should include the
> best practice on how to migrate to the new version.
>
> Tutorial
> - I expected to have some examples showing how the new library is used in a
> multi-threaded application.
> - could you add a tutorial showing?
> "Also, if your slots depend on objects which may be destroyed concurrently
> with signal invocation, you will need to use automatic connection
> management.
> That is, the objects will need to be owned by
> <classname>shared_ptr</classname> and passed to the slot's
> <methodname alt="slotN::track">track</methodname>() method before
> the slot is connected."
>
> - could you add a tutorial showing?
> "Support for postconstructors (and predestructors) on objects managed by
> shared_ptr has been added with deconstruct_ptr, postconstructible, and
> predestructible. This was motivated by the importance of shared_ptr for the
> new connection tracking scheme, and the inability to obtain a shared_ptr to
> an object in its constructor. "
>
> Design Overivew & Rationale
> - could you explain why do you have removed some sections as Type erasure,
> connection class and Choice of Slot Definitions?
>
> - could you add an explanation of why "Automatic connection management is
> achieved through the use of shared_ptr/weak_ptr and slot::track(), as
> opposed to the old boost::trackable base class. "?
>
> - could you add an explanation of why "
> The signal::combiner() method, which formerly returned a reference to the
> signal's combiner has been replaced by signal::combiner (which now returns
> the combiner by value) and signal::set_combiner. "?
>
> - could you add an explanation of why "boost::trackable objects is gone. "?
> Maybe on a depreceated section.
>
> - could you add an explanation of how the Boost.Signal2 library will
> replace the Boost.Signal?
> Which will be the namespace used when the replacement will take place?

The above suggestions on improving the documentation generally seem good. I'm
willing to make another pass over the docs and try to flesh them out and
address the above issues.

To answer some of the questions above here:

I removed the "Type erasure", etc sections because they described the
Boost.Signals implementation, not Signals2. Signals2 doesn't use boost::any
at all, for example. I could have edited them down more carefully, but in my
defense they were pretty short sections.

The shared_ptr/weak_ptr connection management is used because boost::trackable
can't be made thread-safe. It disconnects in the boost::trackable
destructor, but since boost::trackable is a base class, its destructor is not
called until the derived class destructor has already run. Thus, it leaves
open the possibility of a signal being invoked and using a partially
destructed object.

signal::combiner returned a reference, so modification through the returned
reference could not be protected by a mutex internal to the signal.
Getting/setting the combiner by value at least insure the combiner will
always be in a well-defined state when used, although they don't protect
against multiple threads doing get/modify/set on a signals combiner
concurrently.

I intend to leave everything in Signals2 where it is now (in boost::signals2).
Although if signals2 needs to use boost::visit_each, I don't think there is
any reason it needs a separate version of that. I compiled my benchmark
program using both Boost.Signal and Signals2 together with no conflicts.
Well, except for the BOOST_SIGNALS_NUM_ARGS macro which I've since renamed in
svn.

> Test
> For compatibility purposes you should preserv all the Boost.Signal tests.
> Does the regression_test contains bugs from your library or of
> Boost.Signal? This is a good practice, but why to create a separated file?
> If these bugs have a trac it will be interesting to trac them.

I have all the Boost.Signal tests except for the random_signal_system.cpp
test, which had problems sometimes hanging with both Boost.Signals and my
code the last time I tried it (circa boost 1.33 I believe). The
regression_test is only bugs from my library. I created it for convenience
for myself, since it just fits the current workflow better: bug reported,
write test that exposes bug, fix bug. The bugs aren't in trac, I actually
never considered telling people to put bug reports in trac due to the library
only being in the sandbox.

>
> No. The library should NOT be accepted until all these requirements are
> satisfied. :-(
>

Ok, in any case thanks for taking the time to write a review and provide
feedback.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQFJENDl5vihyNWuA4URAsQgAJ4wVX9F+jfqsNIxF6vE0eoAeShecACglevR
4lMuDp2aYVKIuIeKjHjTa7E=
=Wb/m
-----END PGP SIGNATURE-----


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk