From patchwork Fri Jul 31 10:53:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 9083 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 761FBBD86F for ; Fri, 31 Jul 2020 10:53:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B5E5161DD8; Fri, 31 Jul 2020 12:53:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="N2tiQ0MU"; dkim-atps=neutral Received: from o1.f.az.sendgrid.net (o1.f.az.sendgrid.net [208.117.55.132]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C1E4060396 for ; Fri, 31 Jul 2020 12:53:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=uajain.com; h=from:subject:in-reply-to:references:mime-version:to:cc: content-transfer-encoding:content-type; s=s1; bh=bMNOnkEkTHpbNEBGYnD0orvQ1VUtKvvk0NQGieyHQHA=; b=N2tiQ0MUCGJ7NWGtRrofpBsRcUB8/QceckFKu/bnSgWqGyvwPJSH28XRzNfwXHKmQxvU SplbkgTS4xpyq1vGzys+qKfSZZ7wyOWIr+KOMBFRssmugd9rxzIkiaYJYscl95zDsWpuh2 ICRn/DJYCetXoU9tIFkGt36Amm/sVEKfU= Received: by filterdrecv-p3mdw1-7ff865655c-g882x with SMTP id filterdrecv-p3mdw1-7ff865655c-g882x-20-5F23F835-1 2020-07-31 10:53:41.138588816 +0000 UTC m=+146250.616757875 Received: from mail.uajain.com (unknown) by ismtpd0004p1hnd1.sendgrid.net (SG) with ESMTP id BWbqALB4RgC8qD5ODvWMTA for ; Fri, 31 Jul 2020 10:53:40.725 +0000 (UTC) From: Umang Jain Date: Fri, 31 Jul 2020 10:53:41 +0000 (UTC) Message-Id: <20200731105335.62014-2-email@uajain.com> In-Reply-To: <20200731105335.62014-1-email@uajain.com> References: <20200731105335.62014-1-email@uajain.com> Mime-Version: 1.0 X-SG-EID: 1Q40EQ7YGir8a9gjSIAdTjhngY657NMk9ckeo4dbHZDiOpywc/L3L9rFqlwE4KPcofpXQ1KwJPJayd5tGsU1eL8pT6L5XI068+X2BjFsmbU12v7JkpGTHOEJUQjc1upjdOPJ81lysMBHJkG1DANMDVGdCJzM3BkM9m2bkrfHzYmQuH0hngP3RhjlZHRa7NXZiHEUQo0a9R+kMNa2sttZ40V6npMTrTxkX9O5/ODAFxkiJ37InOB5O7WRIz4jnoyQyAceSy+WBh1G6StX1+bhzg== To: libcamera-devel@lists.libcamera.org Subject: [libcamera-devel] [PATCH v2 1/3] libcamera: thread: Support selective message dispatch to thread X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Extend the current dispatchMessages() to support dispatching of selective messsages according to the Message::Type passed in the function argument. dispatchMessages() can now be called explicitly to force deliver selected type's message to the thread for processing (typically when event loop is not running). Add a helper Message::Type::CatchAll message type to deliver every message posted to the thread. Signed-off-by: Umang Jain --- include/libcamera/internal/message.h | 1 + include/libcamera/internal/thread.h | 3 +- src/libcamera/event_dispatcher_poll.cpp | 2 +- src/libcamera/message.cpp | 2 ++ src/libcamera/thread.cpp | 47 ++++++++++++++++++------- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/include/libcamera/internal/message.h b/include/libcamera/internal/message.h index 92ea64a..b8d9866 100644 --- a/include/libcamera/internal/message.h +++ b/include/libcamera/internal/message.h @@ -25,6 +25,7 @@ public: None = 0, InvokeMessage = 1, ThreadMoveMessage = 2, + CatchAll = 999, UserMessage = 1000, }; diff --git a/include/libcamera/internal/thread.h b/include/libcamera/internal/thread.h index 7b59e58..1dfeb72 100644 --- a/include/libcamera/internal/thread.h +++ b/include/libcamera/internal/thread.h @@ -14,6 +14,7 @@ #include +#include "libcamera/internal/message.h" #include "libcamera/internal/utils.h" namespace libcamera { @@ -47,7 +48,7 @@ public: EventDispatcher *eventDispatcher(); void setEventDispatcher(std::unique_ptr dispatcher); - void dispatchMessages(); + void dispatchMessages(Message::Type type); protected: int exec(); diff --git a/src/libcamera/event_dispatcher_poll.cpp b/src/libcamera/event_dispatcher_poll.cpp index 9ab85da..b9fabf8 100644 --- a/src/libcamera/event_dispatcher_poll.cpp +++ b/src/libcamera/event_dispatcher_poll.cpp @@ -146,7 +146,7 @@ void EventDispatcherPoll::processEvents() { int ret; - Thread::current()->dispatchMessages(); + Thread::current()->dispatchMessages(Message::Type::CatchAll); /* Create the pollfd array. */ std::vector pollfds; diff --git a/src/libcamera/message.cpp b/src/libcamera/message.cpp index e9b3e73..e462f90 100644 --- a/src/libcamera/message.cpp +++ b/src/libcamera/message.cpp @@ -49,6 +49,8 @@ std::atomic_uint Message::nextUserType_{ Message::UserMessage }; * \brief Asynchronous method invocation across threads * \var Message::ThreadMoveMessage * \brief Object is being moved to a different thread + * \var Message::CatchAll + * \brief Helper to match message of any type * \var Message::UserMessage * \brief First value available for user-defined messages */ diff --git a/src/libcamera/thread.cpp b/src/libcamera/thread.cpp index d1750d7..d3a5f81 100644 --- a/src/libcamera/thread.cpp +++ b/src/libcamera/thread.cpp @@ -552,26 +552,47 @@ void Thread::removeMessages(Object *receiver) } /** - * \brief Dispatch all posted messages for this thread + * \brief Dispatch posted messages for this thread as per \a type Message::Type. + * Pass Message::Type::CatchAll to dispatch every posted message posted for + * this thread. */ -void Thread::dispatchMessages() +void Thread::dispatchMessages(Message::Type type) { MutexLocker locker(data_->messages_.mutex_); - while (!data_->messages_.list_.empty()) { - std::unique_ptr msg = std::move(data_->messages_.list_.front()); - data_->messages_.list_.pop_front(); - if (!msg) - continue; + if (type == Message::Type::CatchAll) { + while (!data_->messages_.list_.empty()) { + std::unique_ptr msg = + std::move(data_->messages_.list_.front()); + data_->messages_.list_.pop_front(); + if (!msg) + continue; - Object *receiver = msg->receiver_; - ASSERT(data_ == receiver->thread()->data_); + Object *receiver = msg->receiver_; + ASSERT(data_ == receiver->thread()->data_); - receiver->pendingMessages_--; + receiver->pendingMessages_--; - locker.unlock(); - receiver->message(msg.get()); - locker.lock(); + locker.unlock(); + receiver->message(msg.get()); + locker.lock(); + } + } else { + for (std::unique_ptr &msg : data_->messages_.list_) { + if (!msg) + continue; + + if (msg->type() == type) { + std::unique_ptr message = std::move(msg); + Object *receiver = message->receiver_; + ASSERT(data_ == receiver->thread()->data_); + receiver->pendingMessages_--; + + locker.unlock(); + receiver->message(message.get()); + locker.lock(); + } + } } } From patchwork Fri Jul 31 10:53:42 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 9085 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 6B5DABD86F for ; Fri, 31 Jul 2020 10:53:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 34EB161DE1; Fri, 31 Jul 2020 12:53:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="Fj/BEi5h"; dkim-atps=neutral Received: from o1.f.az.sendgrid.net (o1.f.az.sendgrid.net [208.117.55.132]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E7B5361988 for ; Fri, 31 Jul 2020 12:53:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=uajain.com; h=from:subject:in-reply-to:references:mime-version:to:cc: content-transfer-encoding:content-type; s=s1; bh=JrUmiymC4o9tof+s4KHbfmcsApVDZTu4G1cxMyAHBRU=; b=Fj/BEi5h+Th22CcLMRDqmGvZ8jyS58jrRZVa3WkKBNQAWwrBsgZh5dxQGQEp6ytHaaZH asWvOMMaT0k+JTYHqp45e6/JbsRwhUWNWhHFsm0ZfiSqiJMpLTkTJmfPBgbXBDwgDQbbzk Y0u0Bv9eCCuwPpxTB0NSOCk2Px+SsC1Zo= Received: by filterdrecv-p3iad2-d8cc6d457-jqj7p with SMTP id filterdrecv-p3iad2-d8cc6d457-jqj7p-18-5F23F836-20 2020-07-31 10:53:42.622161326 +0000 UTC m=+146724.453296916 Received: from mail.uajain.com (unknown) by ismtpd0001p1maa1.sendgrid.net (SG) with ESMTP id tIcws8jzS4mwFnmpr3Ogow Fri, 31 Jul 2020 10:53:41.898 +0000 (UTC) From: Umang Jain Date: Fri, 31 Jul 2020 10:53:42 +0000 (UTC) Message-Id: <20200731105335.62014-3-email@uajain.com> In-Reply-To: <20200731105335.62014-1-email@uajain.com> References: <20200731105335.62014-1-email@uajain.com> Mime-Version: 1.0 X-SG-EID: 1Q40EQ7YGir8a9gjSIAdTjhngY657NMk9ckeo4dbHZDiOpywc/L3L9rFqlwE4KPc+BcW/nTZaHvUFD9w8u96rp9eFG0+/ILAMuChoGefdnk1+uePLUnDkefJPXUZqBWmUnyAErqjiYJguGNkXO82Fi7jS4PU0IFlvVVCIg3kcDu4mFBy3jaW2Br6ctlD3k3niFuJIgNcqkG9pNuCJ0Ct3nBrNh9hXZoLP3PA/m925CO9XPfWB8+OJyY+F3IKV/2RM4xJLRXH+hm1iL+On/ckLw== To: libcamera-devel@lists.libcamera.org Subject: [libcamera-devel] [PATCH v2 2/3] libcamera: object: Add deleteLater() support X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" This commit adds support to schedule the deletion of an Object to the thread it is bound to (similar to [1]). An Object getting destroyed by a different thread is considered as a violation as per the libcamera threading model. This will be useful for an Object where its ownership is shared via shared pointers in different threads. If the thread which drops the last reference of the Object is a different thread, the destructors get called in that particular thread, not the one Object is bound to. Hence, in order to resolve this kind of situation, the creation of shared pointer can be accompanied by a custom deleter which in turns use deleteLater() to ensure the Object is destroyed in its own thread. [1] https://doc.qt.io/qt-5/qobject.html#deleteLater Signed-off-by: Umang Jain Reviewed-by: Laurent Pinchart --- include/libcamera/internal/message.h | 1 + include/libcamera/object.h | 2 + src/libcamera/message.cpp | 2 + src/libcamera/object.cpp | 48 ++++++++++ test/meson.build | 1 + test/object-delete.cpp | 125 +++++++++++++++++++++++++++ 6 files changed, 179 insertions(+) create mode 100644 test/object-delete.cpp diff --git a/include/libcamera/internal/message.h b/include/libcamera/internal/message.h index b8d9866..6e19822 100644 --- a/include/libcamera/internal/message.h +++ b/include/libcamera/internal/message.h @@ -25,6 +25,7 @@ public: None = 0, InvokeMessage = 1, ThreadMoveMessage = 2, + DeferredDelete = 3, CatchAll = 999, UserMessage = 1000, }; diff --git a/include/libcamera/object.h b/include/libcamera/object.h index 9a3dd07..a1882f0 100644 --- a/include/libcamera/object.h +++ b/include/libcamera/object.h @@ -27,6 +27,8 @@ public: Object(Object *parent = nullptr); virtual ~Object(); + void deleteLater(); + void postMessage(std::unique_ptr msg); templateparent_ = nullptr; } +/** + * \brief Schedule deletion of the instance in the thread it belongs to + * + * This function schedules deletion of the Object when control returns to the + * event loop that the object belongs to. This ensures the object is destroyed + * from the right context, as required by the libcamera threading model. + * + * If this function is called before the thread's event loop is started, the + * object will be deleted when the event loop starts. + * + * Deferred deletion can be used to control the destruction context with shared + * pointers. An object managed with shared pointers is deleted when the last + * reference is destroyed, which makes difficult to ensure through software + * design which context the deletion will take place in. With a custom deleter + * for the shared pointer using deleteLater(), the deletion can be guaranteed to + * happen in the thread the object is bound to. + * + * \code{.cpp} + * std::shared_ptr createObject() + * { + * struct Deleter : std::default_delete { + * void operator()(MyObject *obj) + * { + * delete obj; + * } + * }; + * + * MyObject *obj = new MyObject(); + * + * return std::shared_ptr(obj, Deleter()); + * } + * \endcode + * + * \context This function is \threadsafe. + */ +void Object::deleteLater() +{ + postMessage(std::make_unique(Message::DeferredDelete)); +} + /** * \brief Post a message to the object's thread * \param[in] msg The message @@ -144,6 +188,10 @@ void Object::message(Message *msg) break; } + case Message::DeferredDelete: + delete this; + break; + default: break; } diff --git a/test/meson.build b/test/meson.build index f41d6e7..f5aa144 100644 --- a/test/meson.build +++ b/test/meson.build @@ -34,6 +34,7 @@ internal_tests = [ ['hotplug-cameras', 'hotplug-cameras.cpp'], ['message', 'message.cpp'], ['object', 'object.cpp'], + ['object-delete', 'object-delete.cpp'], ['object-invoke', 'object-invoke.cpp'], ['pixel-format', 'pixel-format.cpp'], ['signal-threads', 'signal-threads.cpp'], diff --git a/test/object-delete.cpp b/test/object-delete.cpp new file mode 100644 index 0000000..6cd1a1e --- /dev/null +++ b/test/object-delete.cpp @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Umang Jain + * + * object.cpp - Object deletion tests + */ + +#include + +#include +#include + +#include "libcamera/internal/thread.h" + +#include "test.h" + +using namespace std; +using namespace libcamera; + +/* + * Global flag to check if object is on its way to deletion from it's + * own thread only. + */ +bool ObjectDeleted = false; + +class NewThread : public Thread +{ +public: + NewThread(Object *obj = nullptr) + : object_(obj), deleteScheduled_(false) + { + } + + bool objectScheduledForDeletion() { return deleteScheduled_; } + +protected: + void run() + { + object_->deleteLater(); + deleteScheduled_ = true; + } + +private: + Object *object_; + bool deleteScheduled_; +}; + + +class TestObject : public Object +{ +public: + TestObject() + { + } + + ~TestObject() + { + if (thread() == Thread::current()) + ObjectDeleted = true; + } +}; + +class ObjectDeleteTest : public Test +{ +protected: + int init() + { + isDeleteScheduled_ = false; + + return 0; + } + + int run() + { + TestObject *obj; + + obj = new TestObject(); + NewThread thread(obj); + thread.start(); + Timer timer; + timer.start(100); + + while (timer.isRunning() && !isDeleteScheduled_) + isDeleteScheduled_ = thread.objectScheduledForDeletion(); + + thread.exit(); + thread.wait(); + + if (!isDeleteScheduled_) { + cout << "Failed to queue object for deletion from another thread" << endl; + return TestFail; + } + + Thread::current()->dispatchMessages(Message::Type::DeferredDelete); + + if (!ObjectDeleted) { + cout << "Failed to delete object queued from another thread" << endl; + return TestFail; + } + + ObjectDeleted = false; + obj = new TestObject(); + obj->deleteLater(); + /* + * \todo Devise a check for multiple deleteLater() calls to a object. + * It should be checked that the object is deleted only once. + */ + Thread::current()->dispatchMessages(Message::Type::DeferredDelete); + if(!ObjectDeleted) { + cout << "Failed to delete object"; + return TestFail; + } + + return TestPass; + } + + void cleanup() + { + } + +private: + bool isDeleteScheduled_; +}; + +TEST_REGISTER(ObjectDeleteTest) From patchwork Fri Jul 31 10:53:43 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Umang Jain X-Patchwork-Id: 9086 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id DAFD8BD86F for ; Fri, 31 Jul 2020 10:53:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9927061EAB; Fri, 31 Jul 2020 12:53:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=uajain.com header.i=@uajain.com header.b="rTV8zgL4"; dkim-atps=neutral Received: from o1.f.az.sendgrid.net (o1.f.az.sendgrid.net [208.117.55.132]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4CD8061DC3 for ; Fri, 31 Jul 2020 12:53:44 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=uajain.com; h=from:subject:in-reply-to:references:mime-version:to:cc: content-transfer-encoding:content-type; s=s1; bh=Q13Y/KIA9jXepafD4MdInIVxJgHdpkJml9AgdG7ctRo=; b=rTV8zgL4Kjur1x6RTQqEG8WDYxUOlT2iM3gZQ9nmE0x87jKDASt9mh036xAX3FcTZOBc GzGKlCXR8WocbZMjeq8bpUTgFZExfwEXYXn6HXBRSoP680/Z/zFc6SNR/qTXmqQdRfyFNI lNfgHUFIXDS1Lln5+SCMhkMq03y2JOuCc= Received: by filterdrecv-p3mdw1-7ff865655c-g882x with SMTP id filterdrecv-p3mdw1-7ff865655c-g882x-20-5F23F836-56 2020-07-31 10:53:43.011386453 +0000 UTC m=+146252.489555543 Received: from mail.uajain.com (unknown) by ismtpd0005p1hnd1.sendgrid.net (SG) with ESMTP id W0LrwjoTRy25EnBiyiVl2w Fri, 31 Jul 2020 10:53:42.688 +0000 (UTC) From: Umang Jain Date: Fri, 31 Jul 2020 10:53:43 +0000 (UTC) Message-Id: <20200731105335.62014-4-email@uajain.com> In-Reply-To: <20200731105335.62014-1-email@uajain.com> References: <20200731105335.62014-1-email@uajain.com> Mime-Version: 1.0 X-SG-EID: 1Q40EQ7YGir8a9gjSIAdTjhngY657NMk9ckeo4dbHZDiOpywc/L3L9rFqlwE4KPcsL0v8VFFOnVCltD6I338jMD1MLjIeeAEM88dkYf60Kf3IBk5AwVG5GFoahk93LIUADk+7bCK7RTdEj/tdw6Ymf4UobQYlils4p1Xvqa3IvdZh50MHwRguXp98KzVR4KkTvcGyqei/wGQ7lMLe0uGRToa75BFG88HFwbmsHIS4KoJ9aKvgVXQ5VI8KY55C4TAADCbaMkj5pj7DtQgXtDtDw== To: libcamera-devel@lists.libcamera.org Subject: [libcamera-devel] [PATCH v2 3/3] libcamera: camera: Ensure deletion via deleteLater() X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Object::deleteLater() ensures that the deletion of the Object takes place in a thread it is bound to. Deleting the Object in a different thread is a violation according to the threading model. On hot-unplug of a currently streaming camera, the last reference of Camera when dropped from the application thread (for e.g. QCam's thread), the destructor is then called from this thread. This is not allowed by the threading model. Camera is meant to be deleted in the thread it is bound to - in this case the CameraManager's thread. Signed-off-by: Umang Jain Reviewed-by: Laurent Pinchart --- include/libcamera/camera.h | 3 ++- src/libcamera/camera.cpp | 2 +- src/libcamera/camera_manager.cpp | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 4d1a4a9..7dd23d7 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -66,7 +67,7 @@ protected: std::vector config_; }; -class Camera final : public std::enable_shared_from_this +class Camera final : public Object, public std::enable_shared_from_this { public: static std::shared_ptr create(PipelineHandler *pipe, diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 69a1b44..034f341 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -464,7 +464,7 @@ std::shared_ptr Camera::create(PipelineHandler *pipe, struct Deleter : std::default_delete { void operator()(Camera *camera) { - delete camera; + camera->deleteLater(); } }; diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index f60491d..c45bf33 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -164,9 +164,13 @@ void CameraManager::Private::cleanup() /* * Release all references to cameras to ensure they all get destroyed - * before the device enumerator deletes the media devices. + * before the device enumerator deletes the media devices. Cameras are + * destroyed via Object::deleteLater() API, hence we need to explicitly + * process deletion requests from the thread's message queue as the event + * loop is not in action here. */ cameras_.clear(); + dispatchMessages(Message::Type::DeferredDelete); enumerator_.reset(nullptr); }