[{"id":3540,"web_url":"https://patchwork.libcamera.org/comment/3540/","msgid":"<20200120140006.552a7lmfmwlxuqnv@uno.localdomain>","date":"2020-01-20T14:00:06","subject":"Re: [libcamera-devel] [PATCH 09/19] libcamera: signal: Make slots\n\tlist private","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n\n  nit: I would move this before the patch that adds locking to this\nfunctions.\n\nThis change alone does not change how things work today\n\nOn Mon, Jan 20, 2020 at 02:24:27AM +0200, Laurent Pinchart wrote:\n> The slots list is touched from most of the Signal template functions. In\n> order to prepare for thread-safety, move handling of the lists list to a\n\ns/list list/list\n\n> small number of non-template functions in the SignalBase class.\n>\n> This incidently fixes a bug in signal disconnection handling where the\n> signal wasn't removed from the object's signals list, as pointed out by\n> the signals unit test.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  include/libcamera/object.h |  4 +-\n>  include/libcamera/signal.h | 88 ++++++++++++++++----------------------\n>  src/libcamera/object.cpp   |  7 ++-\n>  src/libcamera/signal.cpp   | 36 ++++++++++++++++\n>  4 files changed, 81 insertions(+), 54 deletions(-)\n>\n> diff --git a/include/libcamera/object.h b/include/libcamera/object.h\n> index 9344af30bc79..4d16f3f2b610 100644\n> --- a/include/libcamera/object.h\n> +++ b/include/libcamera/object.h\n> @@ -48,9 +48,7 @@ protected:\n>  \tvirtual void message(Message *msg);\n>\n>  private:\n> -\ttemplate<typename... Args>\n> -\tfriend class Signal;\n> -\tfriend class BoundMethodBase;\n> +\tfriend class SignalBase;\n>  \tfriend class Thread;\n>\n>  \tvoid notifyThreadMove();\n> diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h\n> index 432d95d0ed1c..c13bb30f0636 100644\n> --- a/include/libcamera/signal.h\n> +++ b/include/libcamera/signal.h\n> @@ -7,6 +7,7 @@\n>  #ifndef __LIBCAMERA_SIGNAL_H__\n>  #define __LIBCAMERA_SIGNAL_H__\n>\n> +#include <functional>\n>  #include <list>\n>  #include <type_traits>\n>  #include <vector>\n> @@ -19,23 +20,18 @@ namespace libcamera {\n>  class SignalBase\n>  {\n>  public:\n> -\ttemplate<typename T>\n> -\tvoid disconnect(T *obj)\n> -\t{\n> -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> -\t\t\tBoundMethodBase *slot = *iter;\n> -\t\t\tif (slot->match(obj)) {\n> -\t\t\t\titer = slots_.erase(iter);\n> -\t\t\t\tdelete slot;\n> -\t\t\t} else {\n> -\t\t\t\t++iter;\n> -\t\t\t}\n> -\t\t}\n> -\t}\n> +\tvoid disconnect(Object *object);\n>\n>  protected:\n> -\tfriend class Object;\n> -\tstd::list<BoundMethodBase *> slots_;\n> +\tusing SlotList = std::list<BoundMethodBase *>;\n> +\n> +\tvoid connect(BoundMethodBase *slot);\n> +\tvoid disconnect(std::function<bool(SlotList::iterator &)> match);\n> +\n> +\tSlotList slots();\n> +\n> +private:\n> +\tSlotList slots_;\n>  };\n>\n>  template<typename... Args>\n> @@ -45,12 +41,7 @@ public:\n>  \tSignal() {}\n>  \t~Signal()\n>  \t{\n> -\t\tfor (BoundMethodBase *slot : slots_) {\n> -\t\t\tObject *object = slot->object();\n> -\t\t\tif (object)\n> -\t\t\t\tobject->disconnect(this);\n> -\t\t\tdelete slot;\n> -\t\t}\n> +\t\tdisconnect();\n>  \t}\n>\n>  #ifndef __DOXYGEN__\n> @@ -59,8 +50,7 @@ public:\n>  \t\t     ConnectionType type = ConnectionTypeAuto)\n>  \t{\n>  \t\tObject *object = static_cast<Object *>(obj);\n> -\t\tobject->connect(this);\n> -\t\tslots_.push_back(new BoundMethodMember<T, void, Args...>(obj, object, func, type));\n> +\t\tSignalBase::connect(new BoundMethodMember<T, void, Args...>(obj, object, func, type));\n>  \t}\n>\n>  \ttemplate<typename T, typename R, typename std::enable_if<!std::is_base_of<Object, T>::value>::type * = nullptr>\n> @@ -69,63 +59,62 @@ public:\n>  #endif\n>  \tvoid connect(T *obj, R (T::*func)(Args...))\n>  \t{\n> -\t\tslots_.push_back(new BoundMethodMember<T, R, Args...>(obj, nullptr, func));\n> +\t\tSignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func));\n>  \t}\n>\n>  \ttemplate<typename R>\n>  \tvoid connect(R (*func)(Args...))\n>  \t{\n> -\t\tslots_.push_back(new BoundMethodStatic<R, Args...>(func));\n> +\t\tSignalBase::connect(new BoundMethodStatic<R, Args...>(func));\n>  \t}\n>\n>  \tvoid disconnect()\n>  \t{\n> -\t\tfor (BoundMethodBase *slot : slots_)\n> -\t\t\tdelete slot;\n> -\t\tslots_.clear();\n> +\t\tSignalBase::disconnect([](SlotList::iterator &iter) {\n> +\t\t\treturn true;\n> +\t\t});\n>  \t}\n>\n>  \ttemplate<typename T>\n>  \tvoid disconnect(T *obj)\n>  \t{\n> -\t\tSignalBase::disconnect(obj);\n> +\t\tSignalBase::disconnect([obj](SlotList::iterator &iter) {\n> +\t\t\treturn (*iter)->match(obj);\n> +\t\t});\n>  \t}\n>\n>  \ttemplate<typename T, typename R>\n>  \tvoid disconnect(T *obj, R (T::*func)(Args...))\n>  \t{\n> -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\tSignalBase::disconnect([obj, func](SlotList::iterator &iter) {\n>  \t\t\tBoundMethodArgs<R, Args...> *slot =\n>  \t\t\t\tstatic_cast<BoundMethodArgs<R, Args...> *>(*iter);\n> +\n> +\t\t\tif (!slot->match(obj))\n> +\t\t\t\treturn false;\n> +\n>  \t\t\t/*\n>  \t\t\t * If the object matches the slot, the slot is\n>  \t\t\t * guaranteed to be a member slot, so we can safely\n>  \t\t\t * cast it to BoundMethodMember<T, Args...> to match\n>  \t\t\t * func.\n>  \t\t\t */\n> -\t\t\tif (slot->match(obj) &&\n> -\t\t\t    static_cast<BoundMethodMember<T, R, Args...> *>(slot)->match(func)) {\n> -\t\t\t\titer = slots_.erase(iter);\n> -\t\t\t\tdelete slot;\n> -\t\t\t} else {\n> -\t\t\t\t++iter;\n> -\t\t\t}\n> -\t\t}\n> +\t\t\treturn static_cast<BoundMethodMember<T, R, Args...> *>(slot)->match(func);\n> +\t\t});\n>  \t}\n>\n>  \ttemplate<typename R>\n>  \tvoid disconnect(R (*func)(Args...))\n>  \t{\n> -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> -\t\t\tBoundMethodArgs<R, Args...> *slot = *iter;\n> -\t\t\tif (slot->match(nullptr) &&\n> -\t\t\t    static_cast<BoundMethodStatic<R, Args...> *>(slot)->match(func)) {\n> -\t\t\t\titer = slots_.erase(iter);\n> -\t\t\t\tdelete slot;\n> -\t\t\t} else {\n> -\t\t\t\t++iter;\n> -\t\t\t}\n> -\t\t}\n> +\t\tSignalBase::disconnect([func](SlotList::iterator &iter) {\n> +\t\t\tBoundMethodArgs<R, Args...> *slot =\n> +\t\t\t\tstatic_cast<BoundMethodArgs<R, Args...> *>(*iter);\n> +\n> +\t\t\tif (!slot->match(nullptr))\n> +\t\t\t\treturn false;\n> +\n> +\t\t\treturn static_cast<BoundMethodStatic<R, Args...> *>(slot)->match(func);\n> +\t\t});\n>  \t}\n>\n>  \tvoid emit(Args... args)\n> @@ -134,8 +123,7 @@ public:\n>  \t\t * Make a copy of the slots list as the slot could call the\n>  \t\t * disconnect operation, invalidating the iterator.\n>  \t\t */\n> -\t\tstd::vector<BoundMethodBase *> slots{ slots_.begin(), slots_.end() };\n> -\t\tfor (BoundMethodBase *slot : slots)\n> +\t\tfor (BoundMethodBase *slot : slots())\n>  \t\t\tstatic_cast<BoundMethodArgs<void, Args...> *>(slot)->activate(args...);\n>  \t}\n>  };\n> diff --git a/src/libcamera/object.cpp b/src/libcamera/object.cpp\n> index 21aad5652b38..f2a8be172547 100644\n> --- a/src/libcamera/object.cpp\n> +++ b/src/libcamera/object.cpp\n> @@ -76,7 +76,12 @@ Object::Object(Object *parent)\n>   */\n>  Object::~Object()\n>  {\n> -\tfor (SignalBase *signal : signals_)\n> +\t/*\n> +\t * Move signals to a private list to avoid concurrent iteration and\n> +\t * deletion of items from Signal::disconnect().\n> +\t */\n\nShouldn't we lock as well here ? Isn't there a small window during the move of\nsignals_ ?\n\n> +\tstd::list<SignalBase *> signals(std::move(signals_));\n> +\tfor (SignalBase *signal : signals)\n>  \t\tsignal->disconnect(this);\n>\n>  \tif (pendingMessages_)\n> diff --git a/src/libcamera/signal.cpp b/src/libcamera/signal.cpp\n> index 190033317c72..9bb7eca8ed6e 100644\n> --- a/src/libcamera/signal.cpp\n> +++ b/src/libcamera/signal.cpp\n> @@ -14,6 +14,42 @@\n>\n>  namespace libcamera {\n>\n> +void SignalBase::connect(BoundMethodBase *slot)\n> +{\n> +\tObject *object = slot->object();\n> +\tif (object)\n> +\t\tobject->connect(this);\n> +\tslots_.push_back(slot);\n> +}\n> +\n\nI assume this will all get locked later\n\nThe patch itself is fine, with or without locking squashed in\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nThanks\n  j\n\n> +void SignalBase::disconnect(Object *object)\n> +{\n> +\tdisconnect([object](SlotList::iterator &iter) {\n> +\t\treturn (*iter)->match(object);\n> +\t});\n> +}\n> +\n> +void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match)\n> +{\n> +\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\tif (match(iter)) {\n> +\t\t\tObject *object = (*iter)->object();\n> +\t\t\tif (object)\n> +\t\t\t\tobject->disconnect(this);\n> +\n> +\t\t\tdelete *iter;\n> +\t\t\titer = slots_.erase(iter);\n> +\t\t} else {\n> +\t\t\t++iter;\n> +\t\t}\n> +\t}\n> +}\n> +\n> +SignalBase::SlotList SignalBase::slots()\n> +{\n> +\treturn slots_;\n> +}\n> +\n>  /**\n>   * \\class Signal\n>   * \\brief Generic signal and slot communication mechanism\n> --\n> Regards,\n>\n> Laurent Pinchart\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay7-d.mail.gandi.net (relay7-d.mail.gandi.net\n\t[217.70.183.200])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6600260804\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Jan 2020 14:57:35 +0100 (CET)","from uno.localdomain (93-34-114-233.ip49.fastwebnet.it\n\t[93.34.114.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay7-d.mail.gandi.net (Postfix) with ESMTPSA id EC74020002;\n\tMon, 20 Jan 2020 13:57:34 +0000 (UTC)"],"X-Originating-IP":"93.34.114.233","Date":"Mon, 20 Jan 2020 15:00:06 +0100","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20200120140006.552a7lmfmwlxuqnv@uno.localdomain>","References":"<20200120002437.6633-1-laurent.pinchart@ideasonboard.com>\n\t<20200120002437.6633-10-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"e53lhr43h7b6yg4v\"","Content-Disposition":"inline","In-Reply-To":"<20200120002437.6633-10-laurent.pinchart@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH 09/19] libcamera: signal: Make slots\n\tlist private","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Mon, 20 Jan 2020 13:57:35 -0000"}},{"id":3554,"web_url":"https://patchwork.libcamera.org/comment/3554/","msgid":"<20200120174811.GN20122@pendragon.ideasonboard.com>","date":"2020-01-20T17:48:11","subject":"Re: [libcamera-devel] [PATCH 09/19] libcamera: signal: Make slots\n\tlist private","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Mon, Jan 20, 2020 at 03:00:06PM +0100, Jacopo Mondi wrote:\n> Hi Laurent,\n> \n>   nit: I would move this before the patch that adds locking to this\n> functions.\n\nI'm confused, isn't it already the case ?\n\n> This change alone does not change how things work today\n\nIt fixes the bug pointed out by patch 08/19, doesn't it ?\n\n> On Mon, Jan 20, 2020 at 02:24:27AM +0200, Laurent Pinchart wrote:\n> > The slots list is touched from most of the Signal template functions. In\n> > order to prepare for thread-safety, move handling of the lists list to a\n> \n> s/list list/list\n> \n> > small number of non-template functions in the SignalBase class.\n> >\n> > This incidently fixes a bug in signal disconnection handling where the\n> > signal wasn't removed from the object's signals list, as pointed out by\n> > the signals unit test.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  include/libcamera/object.h |  4 +-\n> >  include/libcamera/signal.h | 88 ++++++++++++++++----------------------\n> >  src/libcamera/object.cpp   |  7 ++-\n> >  src/libcamera/signal.cpp   | 36 ++++++++++++++++\n> >  4 files changed, 81 insertions(+), 54 deletions(-)\n> >\n> > diff --git a/include/libcamera/object.h b/include/libcamera/object.h\n> > index 9344af30bc79..4d16f3f2b610 100644\n> > --- a/include/libcamera/object.h\n> > +++ b/include/libcamera/object.h\n> > @@ -48,9 +48,7 @@ protected:\n> >  \tvirtual void message(Message *msg);\n> >\n> >  private:\n> > -\ttemplate<typename... Args>\n> > -\tfriend class Signal;\n> > -\tfriend class BoundMethodBase;\n> > +\tfriend class SignalBase;\n> >  \tfriend class Thread;\n> >\n> >  \tvoid notifyThreadMove();\n> > diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h\n> > index 432d95d0ed1c..c13bb30f0636 100644\n> > --- a/include/libcamera/signal.h\n> > +++ b/include/libcamera/signal.h\n> > @@ -7,6 +7,7 @@\n> >  #ifndef __LIBCAMERA_SIGNAL_H__\n> >  #define __LIBCAMERA_SIGNAL_H__\n> >\n> > +#include <functional>\n> >  #include <list>\n> >  #include <type_traits>\n> >  #include <vector>\n> > @@ -19,23 +20,18 @@ namespace libcamera {\n> >  class SignalBase\n> >  {\n> >  public:\n> > -\ttemplate<typename T>\n> > -\tvoid disconnect(T *obj)\n> > -\t{\n> > -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> > -\t\t\tBoundMethodBase *slot = *iter;\n> > -\t\t\tif (slot->match(obj)) {\n> > -\t\t\t\titer = slots_.erase(iter);\n> > -\t\t\t\tdelete slot;\n> > -\t\t\t} else {\n> > -\t\t\t\t++iter;\n> > -\t\t\t}\n> > -\t\t}\n> > -\t}\n> > +\tvoid disconnect(Object *object);\n> >\n> >  protected:\n> > -\tfriend class Object;\n> > -\tstd::list<BoundMethodBase *> slots_;\n> > +\tusing SlotList = std::list<BoundMethodBase *>;\n> > +\n> > +\tvoid connect(BoundMethodBase *slot);\n> > +\tvoid disconnect(std::function<bool(SlotList::iterator &)> match);\n> > +\n> > +\tSlotList slots();\n> > +\n> > +private:\n> > +\tSlotList slots_;\n> >  };\n> >\n> >  template<typename... Args>\n> > @@ -45,12 +41,7 @@ public:\n> >  \tSignal() {}\n> >  \t~Signal()\n> >  \t{\n> > -\t\tfor (BoundMethodBase *slot : slots_) {\n> > -\t\t\tObject *object = slot->object();\n> > -\t\t\tif (object)\n> > -\t\t\t\tobject->disconnect(this);\n> > -\t\t\tdelete slot;\n> > -\t\t}\n> > +\t\tdisconnect();\n> >  \t}\n> >\n> >  #ifndef __DOXYGEN__\n> > @@ -59,8 +50,7 @@ public:\n> >  \t\t     ConnectionType type = ConnectionTypeAuto)\n> >  \t{\n> >  \t\tObject *object = static_cast<Object *>(obj);\n> > -\t\tobject->connect(this);\n> > -\t\tslots_.push_back(new BoundMethodMember<T, void, Args...>(obj, object, func, type));\n> > +\t\tSignalBase::connect(new BoundMethodMember<T, void, Args...>(obj, object, func, type));\n> >  \t}\n> >\n> >  \ttemplate<typename T, typename R, typename std::enable_if<!std::is_base_of<Object, T>::value>::type * = nullptr>\n> > @@ -69,63 +59,62 @@ public:\n> >  #endif\n> >  \tvoid connect(T *obj, R (T::*func)(Args...))\n> >  \t{\n> > -\t\tslots_.push_back(new BoundMethodMember<T, R, Args...>(obj, nullptr, func));\n> > +\t\tSignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func));\n> >  \t}\n> >\n> >  \ttemplate<typename R>\n> >  \tvoid connect(R (*func)(Args...))\n> >  \t{\n> > -\t\tslots_.push_back(new BoundMethodStatic<R, Args...>(func));\n> > +\t\tSignalBase::connect(new BoundMethodStatic<R, Args...>(func));\n> >  \t}\n> >\n> >  \tvoid disconnect()\n> >  \t{\n> > -\t\tfor (BoundMethodBase *slot : slots_)\n> > -\t\t\tdelete slot;\n> > -\t\tslots_.clear();\n> > +\t\tSignalBase::disconnect([](SlotList::iterator &iter) {\n> > +\t\t\treturn true;\n> > +\t\t});\n> >  \t}\n> >\n> >  \ttemplate<typename T>\n> >  \tvoid disconnect(T *obj)\n> >  \t{\n> > -\t\tSignalBase::disconnect(obj);\n> > +\t\tSignalBase::disconnect([obj](SlotList::iterator &iter) {\n> > +\t\t\treturn (*iter)->match(obj);\n> > +\t\t});\n> >  \t}\n> >\n> >  \ttemplate<typename T, typename R>\n> >  \tvoid disconnect(T *obj, R (T::*func)(Args...))\n> >  \t{\n> > -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> > +\t\tSignalBase::disconnect([obj, func](SlotList::iterator &iter) {\n> >  \t\t\tBoundMethodArgs<R, Args...> *slot =\n> >  \t\t\t\tstatic_cast<BoundMethodArgs<R, Args...> *>(*iter);\n> > +\n> > +\t\t\tif (!slot->match(obj))\n> > +\t\t\t\treturn false;\n> > +\n> >  \t\t\t/*\n> >  \t\t\t * If the object matches the slot, the slot is\n> >  \t\t\t * guaranteed to be a member slot, so we can safely\n> >  \t\t\t * cast it to BoundMethodMember<T, Args...> to match\n> >  \t\t\t * func.\n> >  \t\t\t */\n> > -\t\t\tif (slot->match(obj) &&\n> > -\t\t\t    static_cast<BoundMethodMember<T, R, Args...> *>(slot)->match(func)) {\n> > -\t\t\t\titer = slots_.erase(iter);\n> > -\t\t\t\tdelete slot;\n> > -\t\t\t} else {\n> > -\t\t\t\t++iter;\n> > -\t\t\t}\n> > -\t\t}\n> > +\t\t\treturn static_cast<BoundMethodMember<T, R, Args...> *>(slot)->match(func);\n> > +\t\t});\n> >  \t}\n> >\n> >  \ttemplate<typename R>\n> >  \tvoid disconnect(R (*func)(Args...))\n> >  \t{\n> > -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> > -\t\t\tBoundMethodArgs<R, Args...> *slot = *iter;\n> > -\t\t\tif (slot->match(nullptr) &&\n> > -\t\t\t    static_cast<BoundMethodStatic<R, Args...> *>(slot)->match(func)) {\n> > -\t\t\t\titer = slots_.erase(iter);\n> > -\t\t\t\tdelete slot;\n> > -\t\t\t} else {\n> > -\t\t\t\t++iter;\n> > -\t\t\t}\n> > -\t\t}\n> > +\t\tSignalBase::disconnect([func](SlotList::iterator &iter) {\n> > +\t\t\tBoundMethodArgs<R, Args...> *slot =\n> > +\t\t\t\tstatic_cast<BoundMethodArgs<R, Args...> *>(*iter);\n> > +\n> > +\t\t\tif (!slot->match(nullptr))\n> > +\t\t\t\treturn false;\n> > +\n> > +\t\t\treturn static_cast<BoundMethodStatic<R, Args...> *>(slot)->match(func);\n> > +\t\t});\n> >  \t}\n> >\n> >  \tvoid emit(Args... args)\n> > @@ -134,8 +123,7 @@ public:\n> >  \t\t * Make a copy of the slots list as the slot could call the\n> >  \t\t * disconnect operation, invalidating the iterator.\n> >  \t\t */\n> > -\t\tstd::vector<BoundMethodBase *> slots{ slots_.begin(), slots_.end() };\n> > -\t\tfor (BoundMethodBase *slot : slots)\n> > +\t\tfor (BoundMethodBase *slot : slots())\n> >  \t\t\tstatic_cast<BoundMethodArgs<void, Args...> *>(slot)->activate(args...);\n> >  \t}\n> >  };\n> > diff --git a/src/libcamera/object.cpp b/src/libcamera/object.cpp\n> > index 21aad5652b38..f2a8be172547 100644\n> > --- a/src/libcamera/object.cpp\n> > +++ b/src/libcamera/object.cpp\n> > @@ -76,7 +76,12 @@ Object::Object(Object *parent)\n> >   */\n> >  Object::~Object()\n> >  {\n> > -\tfor (SignalBase *signal : signals_)\n> > +\t/*\n> > +\t * Move signals to a private list to avoid concurrent iteration and\n> > +\t * deletion of items from Signal::disconnect().\n> > +\t */\n> \n> Shouldn't we lock as well here ? Isn't there a small window during the move of\n> signals_ ?\n\nThere is, but the only other place that touches signals_ is\nObject::connect() and Object::disconnect(). If you call those methods\n(through Signal::connect() or Signal::disconnect()) from another thread\nwhile the Object instance is being deleted, you're in bigger trouble\nanyway :-) No locking is needed here in my opinion as the caller should\nguarantee that the Object instance won't be deleted while the instance\nis still being accessed. The only exception is the call to\nObject::disconnect() from SignalBase::disconnect(), called below, but\nthat runs in the same thread.\n\n> > +\tstd::list<SignalBase *> signals(std::move(signals_));\n> > +\tfor (SignalBase *signal : signals)\n> >  \t\tsignal->disconnect(this);\n> >\n> >  \tif (pendingMessages_)\n> > diff --git a/src/libcamera/signal.cpp b/src/libcamera/signal.cpp\n> > index 190033317c72..9bb7eca8ed6e 100644\n> > --- a/src/libcamera/signal.cpp\n> > +++ b/src/libcamera/signal.cpp\n> > @@ -14,6 +14,42 @@\n> >\n> >  namespace libcamera {\n> >\n> > +void SignalBase::connect(BoundMethodBase *slot)\n> > +{\n> > +\tObject *object = slot->object();\n> > +\tif (object)\n> > +\t\tobject->connect(this);\n> > +\tslots_.push_back(slot);\n> > +}\n> > +\n> \n> I assume this will all get locked later\n\nYes, in patch 12/19, this call will be protected by a global mutex. As\nexplained in that patch we may want to use finer-grain locks in the\nfuture, but I think that can then be built on top.\n\n> The patch itself is fine, with or without locking squashed in\n\nI'd rather keep the locking separate, on top of this patch, as it's part\nof the threading model and should be reviewed separately from the\nrequired refactoring.\n\n> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n> \n> > +void SignalBase::disconnect(Object *object)\n> > +{\n> > +\tdisconnect([object](SlotList::iterator &iter) {\n> > +\t\treturn (*iter)->match(object);\n> > +\t});\n> > +}\n> > +\n> > +void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match)\n> > +{\n> > +\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> > +\t\tif (match(iter)) {\n> > +\t\t\tObject *object = (*iter)->object();\n> > +\t\t\tif (object)\n> > +\t\t\t\tobject->disconnect(this);\n> > +\n> > +\t\t\tdelete *iter;\n> > +\t\t\titer = slots_.erase(iter);\n> > +\t\t} else {\n> > +\t\t\t++iter;\n> > +\t\t}\n> > +\t}\n> > +}\n> > +\n> > +SignalBase::SlotList SignalBase::slots()\n> > +{\n> > +\treturn slots_;\n> > +}\n> > +\n> >  /**\n> >   * \\class Signal\n> >   * \\brief Generic signal and slot communication mechanism","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 93BF360455\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Jan 2020 18:48:12 +0100 (CET)","from pendragon.ideasonboard.com (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0551AA62;\n\tMon, 20 Jan 2020 18:48:11 +0100 (CET)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1579542492;\n\tbh=003QA9+RjfWGYThTWLdKk9Wab0KlJm/xX7sFk6uHZU4=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=ST3zC0R4sJHR+bo5J5QHpKNYe98+xBqcrpYZ4HmDXR29JOnbDHeNgFqKPYG4EcNA5\n\t/lN88wHufb6P35N8WsLU7x+rp9Bzf+/Mqc58yoTEQT6YqZASI+jA7XCEdO9G4Q51Q5\n\t5msfDjieZwIqQNbElccucC2pXedPJoeUOhUpLIRc=","Date":"Mon, 20 Jan 2020 19:48:11 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20200120174811.GN20122@pendragon.ideasonboard.com>","References":"<20200120002437.6633-1-laurent.pinchart@ideasonboard.com>\n\t<20200120002437.6633-10-laurent.pinchart@ideasonboard.com>\n\t<20200120140006.552a7lmfmwlxuqnv@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20200120140006.552a7lmfmwlxuqnv@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH 09/19] libcamera: signal: Make slots\n\tlist private","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Mon, 20 Jan 2020 17:48:12 -0000"}},{"id":3570,"web_url":"https://patchwork.libcamera.org/comment/3570/","msgid":"<20200122152621.GQ1124294@oden.dyn.berto.se>","date":"2020-01-22T15:26:21","subject":"Re: [libcamera-devel] [PATCH 09/19] libcamera: signal: Make slots\n\tlist private","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Laurent,\n\nThanks for your work.\n\nOn 2020-01-20 02:24:27 +0200, Laurent Pinchart wrote:\n> The slots list is touched from most of the Signal template functions. In\n> order to prepare for thread-safety, move handling of the lists list to a\n> small number of non-template functions in the SignalBase class.\n> \n> This incidently fixes a bug in signal disconnection handling where the\n> signal wasn't removed from the object's signals list, as pointed out by\n> the signals unit test.\n> \n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nWith the 'lists list' typo pointed out by Jacopo fixed,\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> ---\n>  include/libcamera/object.h |  4 +-\n>  include/libcamera/signal.h | 88 ++++++++++++++++----------------------\n>  src/libcamera/object.cpp   |  7 ++-\n>  src/libcamera/signal.cpp   | 36 ++++++++++++++++\n>  4 files changed, 81 insertions(+), 54 deletions(-)\n> \n> diff --git a/include/libcamera/object.h b/include/libcamera/object.h\n> index 9344af30bc79..4d16f3f2b610 100644\n> --- a/include/libcamera/object.h\n> +++ b/include/libcamera/object.h\n> @@ -48,9 +48,7 @@ protected:\n>  \tvirtual void message(Message *msg);\n>  \n>  private:\n> -\ttemplate<typename... Args>\n> -\tfriend class Signal;\n> -\tfriend class BoundMethodBase;\n> +\tfriend class SignalBase;\n>  \tfriend class Thread;\n>  \n>  \tvoid notifyThreadMove();\n> diff --git a/include/libcamera/signal.h b/include/libcamera/signal.h\n> index 432d95d0ed1c..c13bb30f0636 100644\n> --- a/include/libcamera/signal.h\n> +++ b/include/libcamera/signal.h\n> @@ -7,6 +7,7 @@\n>  #ifndef __LIBCAMERA_SIGNAL_H__\n>  #define __LIBCAMERA_SIGNAL_H__\n>  \n> +#include <functional>\n>  #include <list>\n>  #include <type_traits>\n>  #include <vector>\n> @@ -19,23 +20,18 @@ namespace libcamera {\n>  class SignalBase\n>  {\n>  public:\n> -\ttemplate<typename T>\n> -\tvoid disconnect(T *obj)\n> -\t{\n> -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> -\t\t\tBoundMethodBase *slot = *iter;\n> -\t\t\tif (slot->match(obj)) {\n> -\t\t\t\titer = slots_.erase(iter);\n> -\t\t\t\tdelete slot;\n> -\t\t\t} else {\n> -\t\t\t\t++iter;\n> -\t\t\t}\n> -\t\t}\n> -\t}\n> +\tvoid disconnect(Object *object);\n>  \n>  protected:\n> -\tfriend class Object;\n> -\tstd::list<BoundMethodBase *> slots_;\n> +\tusing SlotList = std::list<BoundMethodBase *>;\n> +\n> +\tvoid connect(BoundMethodBase *slot);\n> +\tvoid disconnect(std::function<bool(SlotList::iterator &)> match);\n> +\n> +\tSlotList slots();\n> +\n> +private:\n> +\tSlotList slots_;\n>  };\n>  \n>  template<typename... Args>\n> @@ -45,12 +41,7 @@ public:\n>  \tSignal() {}\n>  \t~Signal()\n>  \t{\n> -\t\tfor (BoundMethodBase *slot : slots_) {\n> -\t\t\tObject *object = slot->object();\n> -\t\t\tif (object)\n> -\t\t\t\tobject->disconnect(this);\n> -\t\t\tdelete slot;\n> -\t\t}\n> +\t\tdisconnect();\n>  \t}\n>  \n>  #ifndef __DOXYGEN__\n> @@ -59,8 +50,7 @@ public:\n>  \t\t     ConnectionType type = ConnectionTypeAuto)\n>  \t{\n>  \t\tObject *object = static_cast<Object *>(obj);\n> -\t\tobject->connect(this);\n> -\t\tslots_.push_back(new BoundMethodMember<T, void, Args...>(obj, object, func, type));\n> +\t\tSignalBase::connect(new BoundMethodMember<T, void, Args...>(obj, object, func, type));\n>  \t}\n>  \n>  \ttemplate<typename T, typename R, typename std::enable_if<!std::is_base_of<Object, T>::value>::type * = nullptr>\n> @@ -69,63 +59,62 @@ public:\n>  #endif\n>  \tvoid connect(T *obj, R (T::*func)(Args...))\n>  \t{\n> -\t\tslots_.push_back(new BoundMethodMember<T, R, Args...>(obj, nullptr, func));\n> +\t\tSignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func));\n>  \t}\n>  \n>  \ttemplate<typename R>\n>  \tvoid connect(R (*func)(Args...))\n>  \t{\n> -\t\tslots_.push_back(new BoundMethodStatic<R, Args...>(func));\n> +\t\tSignalBase::connect(new BoundMethodStatic<R, Args...>(func));\n>  \t}\n>  \n>  \tvoid disconnect()\n>  \t{\n> -\t\tfor (BoundMethodBase *slot : slots_)\n> -\t\t\tdelete slot;\n> -\t\tslots_.clear();\n> +\t\tSignalBase::disconnect([](SlotList::iterator &iter) {\n> +\t\t\treturn true;\n> +\t\t});\n>  \t}\n>  \n>  \ttemplate<typename T>\n>  \tvoid disconnect(T *obj)\n>  \t{\n> -\t\tSignalBase::disconnect(obj);\n> +\t\tSignalBase::disconnect([obj](SlotList::iterator &iter) {\n> +\t\t\treturn (*iter)->match(obj);\n> +\t\t});\n>  \t}\n>  \n>  \ttemplate<typename T, typename R>\n>  \tvoid disconnect(T *obj, R (T::*func)(Args...))\n>  \t{\n> -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\tSignalBase::disconnect([obj, func](SlotList::iterator &iter) {\n>  \t\t\tBoundMethodArgs<R, Args...> *slot =\n>  \t\t\t\tstatic_cast<BoundMethodArgs<R, Args...> *>(*iter);\n> +\n> +\t\t\tif (!slot->match(obj))\n> +\t\t\t\treturn false;\n> +\n>  \t\t\t/*\n>  \t\t\t * If the object matches the slot, the slot is\n>  \t\t\t * guaranteed to be a member slot, so we can safely\n>  \t\t\t * cast it to BoundMethodMember<T, Args...> to match\n>  \t\t\t * func.\n>  \t\t\t */\n> -\t\t\tif (slot->match(obj) &&\n> -\t\t\t    static_cast<BoundMethodMember<T, R, Args...> *>(slot)->match(func)) {\n> -\t\t\t\titer = slots_.erase(iter);\n> -\t\t\t\tdelete slot;\n> -\t\t\t} else {\n> -\t\t\t\t++iter;\n> -\t\t\t}\n> -\t\t}\n> +\t\t\treturn static_cast<BoundMethodMember<T, R, Args...> *>(slot)->match(func);\n> +\t\t});\n>  \t}\n>  \n>  \ttemplate<typename R>\n>  \tvoid disconnect(R (*func)(Args...))\n>  \t{\n> -\t\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> -\t\t\tBoundMethodArgs<R, Args...> *slot = *iter;\n> -\t\t\tif (slot->match(nullptr) &&\n> -\t\t\t    static_cast<BoundMethodStatic<R, Args...> *>(slot)->match(func)) {\n> -\t\t\t\titer = slots_.erase(iter);\n> -\t\t\t\tdelete slot;\n> -\t\t\t} else {\n> -\t\t\t\t++iter;\n> -\t\t\t}\n> -\t\t}\n> +\t\tSignalBase::disconnect([func](SlotList::iterator &iter) {\n> +\t\t\tBoundMethodArgs<R, Args...> *slot =\n> +\t\t\t\tstatic_cast<BoundMethodArgs<R, Args...> *>(*iter);\n> +\n> +\t\t\tif (!slot->match(nullptr))\n> +\t\t\t\treturn false;\n> +\n> +\t\t\treturn static_cast<BoundMethodStatic<R, Args...> *>(slot)->match(func);\n> +\t\t});\n>  \t}\n>  \n>  \tvoid emit(Args... args)\n> @@ -134,8 +123,7 @@ public:\n>  \t\t * Make a copy of the slots list as the slot could call the\n>  \t\t * disconnect operation, invalidating the iterator.\n>  \t\t */\n> -\t\tstd::vector<BoundMethodBase *> slots{ slots_.begin(), slots_.end() };\n> -\t\tfor (BoundMethodBase *slot : slots)\n> +\t\tfor (BoundMethodBase *slot : slots())\n>  \t\t\tstatic_cast<BoundMethodArgs<void, Args...> *>(slot)->activate(args...);\n>  \t}\n>  };\n> diff --git a/src/libcamera/object.cpp b/src/libcamera/object.cpp\n> index 21aad5652b38..f2a8be172547 100644\n> --- a/src/libcamera/object.cpp\n> +++ b/src/libcamera/object.cpp\n> @@ -76,7 +76,12 @@ Object::Object(Object *parent)\n>   */\n>  Object::~Object()\n>  {\n> -\tfor (SignalBase *signal : signals_)\n> +\t/*\n> +\t * Move signals to a private list to avoid concurrent iteration and\n> +\t * deletion of items from Signal::disconnect().\n> +\t */\n> +\tstd::list<SignalBase *> signals(std::move(signals_));\n> +\tfor (SignalBase *signal : signals)\n>  \t\tsignal->disconnect(this);\n>  \n>  \tif (pendingMessages_)\n> diff --git a/src/libcamera/signal.cpp b/src/libcamera/signal.cpp\n> index 190033317c72..9bb7eca8ed6e 100644\n> --- a/src/libcamera/signal.cpp\n> +++ b/src/libcamera/signal.cpp\n> @@ -14,6 +14,42 @@\n>  \n>  namespace libcamera {\n>  \n> +void SignalBase::connect(BoundMethodBase *slot)\n> +{\n> +\tObject *object = slot->object();\n> +\tif (object)\n> +\t\tobject->connect(this);\n> +\tslots_.push_back(slot);\n> +}\n> +\n> +void SignalBase::disconnect(Object *object)\n> +{\n> +\tdisconnect([object](SlotList::iterator &iter) {\n> +\t\treturn (*iter)->match(object);\n> +\t});\n> +}\n> +\n> +void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match)\n> +{\n> +\tfor (auto iter = slots_.begin(); iter != slots_.end(); ) {\n> +\t\tif (match(iter)) {\n> +\t\t\tObject *object = (*iter)->object();\n> +\t\t\tif (object)\n> +\t\t\t\tobject->disconnect(this);\n> +\n> +\t\t\tdelete *iter;\n> +\t\t\titer = slots_.erase(iter);\n> +\t\t} else {\n> +\t\t\t++iter;\n> +\t\t}\n> +\t}\n> +}\n> +\n> +SignalBase::SlotList SignalBase::slots()\n> +{\n> +\treturn slots_;\n> +}\n> +\n>  /**\n>   * \\class Signal\n>   * \\brief Generic signal and slot communication mechanism\n> -- \n> Regards,\n> \n> Laurent Pinchart\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<niklas.soderlund@ragnatech.se>","Received":["from mail-lj1-x241.google.com (mail-lj1-x241.google.com\n\t[IPv6:2a00:1450:4864:20::241])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C7D5E607F3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Jan 2020 16:26:22 +0100 (CET)","by mail-lj1-x241.google.com with SMTP id o13so7266889ljg.4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Jan 2020 07:26:22 -0800 (PST)","from localhost (h-93-159.A463.priv.bahnhof.se. [46.59.93.159])\n\tby smtp.gmail.com with ESMTPSA id\n\tn11sm20343196ljg.15.2020.01.22.07.26.21\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 22 Jan 2020 07:26:21 -0800 (PST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to;\n\tbh=bodONcpGYEd/09qVKsikJQJE8LAO20vIOrkQugo+1Fk=;\n\tb=Vh6XzzDeAshXQHVXS827JzsIW9fgYwFCraPNpz8YWR4axeB8/kNeqI9VxxkOjdO9Sq\n\tRaczNyKLUhv+bZLffhBTbFMzsCnN1gnq4N/5zk4RQSXH0S/u3Xz2NYORKvnJpFT9CsAH\n\tWDVnz1h3d3MEFoffXklWeAZ99fUqVpXDpt7JEkNOl8oaF6sH6VxYo3/fZDmFfcI+Z2hr\n\tp/yrCKBWW+kBzZW85ocFEHAOIGqeooHX9kIvsp1V5DWo+gvWeFuhKkRAIIiojqQSPXl9\n\tT+yfQqjdqNReyOzEFAnROr9N67AbinffSsvr1pPrla5QGsQVl0yEHHzJ9gY/uoTR0hTp\n\tXuMw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to;\n\tbh=bodONcpGYEd/09qVKsikJQJE8LAO20vIOrkQugo+1Fk=;\n\tb=f+kugkgGiyyGhVxH+fJSx6ZmqmwGEdr4H8uRX3abm1q3zQhvU8HlH9swsnZITvOseQ\n\tKFF6DME3b7I+RS1OAP5nb4f7IgCtPoZRwp9UF3tj+Vzc8K8v0RlG+yt++XMlsnZ+yCkN\n\t2ANScWrVO65+GKEMJS7c1J/wMtxo7YGtqbDLD60rKnZL6SZdx1hQbVbfkJuDzu30XTzm\n\tiaMX8aKIYigCgCGZL4vwQgoK3Iuo7TMofddRdWZt2FyUJIwEpO8wwUmOC6ipxU+Q81mo\n\txyK9/6r68WkkCZ6HsTlu22clLJpukVrodNsY3dxJzwJlrR9qFJYFr6U4jpRCfSmQsmCN\n\t2SzA==","X-Gm-Message-State":"APjAAAWGMedq8RET/AQ44/363dLLqpX2TEKcEX6nSfyOjMCS6OKOi48s\n\tfqJiI2RICvbH9L5kyuv+KOzBBo7PRzs=","X-Google-Smtp-Source":"APXvYqxYsy/sdXyFLzRLiIg8UiKAoRB85wsVSF8ktHQ0I5Ec+IT5GC7Auks/uUadrr716kmERb9sJw==","X-Received":"by 2002:a2e:9e19:: with SMTP id\n\te25mr4250829ljk.179.1579706782066; \n\tWed, 22 Jan 2020 07:26:22 -0800 (PST)","Date":"Wed, 22 Jan 2020 16:26:21 +0100","From":"Niklas =?iso-8859-1?q?S=F6derlund?= <niklas.soderlund@ragnatech.se>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20200122152621.GQ1124294@oden.dyn.berto.se>","References":"<20200120002437.6633-1-laurent.pinchart@ideasonboard.com>\n\t<20200120002437.6633-10-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20200120002437.6633-10-laurent.pinchart@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH 09/19] libcamera: signal: Make slots\n\tlist private","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Wed, 22 Jan 2020 15:26:22 -0000"}}]