diff options
-rw-r--r-- | include/libcamera/camera_manager.h | 2 | ||||
-rw-r--r-- | src/libcamera/camera_manager.cpp | 15 | ||||
-rw-r--r-- | src/libcamera/include/thread.h | 61 | ||||
-rw-r--r-- | src/libcamera/meson.build | 3 | ||||
-rw-r--r-- | src/libcamera/thread.cpp | 335 |
5 files changed, 403 insertions, 13 deletions
diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h index 633d27d1..0e8881ba 100644 --- a/include/libcamera/camera_manager.h +++ b/include/libcamera/camera_manager.h @@ -46,8 +46,6 @@ private: std::vector<std::shared_ptr<PipelineHandler>> pipes_; std::vector<std::shared_ptr<Camera>> cameras_; - std::unique_ptr<EventDispatcher> dispatcher_; - static const std::string version_; }; diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp index 337496c2..2cf01423 100644 --- a/src/libcamera/camera_manager.cpp +++ b/src/libcamera/camera_manager.cpp @@ -14,6 +14,7 @@ #include "event_dispatcher_poll.h" #include "log.h" #include "pipeline_handler.h" +#include "thread.h" #include "utils.h" /** @@ -56,7 +57,7 @@ LOG_DEFINE_CATEGORY(Camera) */ CameraManager::CameraManager() - : enumerator_(nullptr), dispatcher_(nullptr) + : enumerator_(nullptr) { } @@ -247,12 +248,7 @@ CameraManager *CameraManager::instance() */ void CameraManager::setEventDispatcher(std::unique_ptr<EventDispatcher> dispatcher) { - if (dispatcher_) { - LOG(Camera, Warning) << "Event dispatcher is already set"; - return; - } - - dispatcher_ = std::move(dispatcher); + Thread::current()->setEventDispatcher(std::move(dispatcher)); } /** @@ -268,10 +264,7 @@ void CameraManager::setEventDispatcher(std::unique_ptr<EventDispatcher> dispatch */ EventDispatcher *CameraManager::eventDispatcher() { - if (!dispatcher_) - dispatcher_ = utils::make_unique<EventDispatcherPoll>(); - - return dispatcher_.get(); + return Thread::current()->eventDispatcher(); } } /* namespace libcamera */ diff --git a/src/libcamera/include/thread.h b/src/libcamera/include/thread.h new file mode 100644 index 00000000..e881d90e --- /dev/null +++ b/src/libcamera/include/thread.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * thread.h - Thread support + */ +#ifndef __LIBCAMERA_THREAD_H__ +#define __LIBCAMERA_THREAD_H__ + +#include <memory> +#include <mutex> +#include <thread> + +#include <libcamera/signal.h> + +namespace libcamera { + +class EventDispatcher; +class ThreadData; +class ThreadMain; + +using Mutex = std::mutex; +using MutexLocker = std::unique_lock<std::mutex>; + +class Thread +{ +public: + Thread(); + virtual ~Thread(); + + void start(); + void exit(int code = 0); + void wait(); + + bool isRunning(); + + Signal<Thread *> finished; + + static Thread *current(); + + EventDispatcher *eventDispatcher(); + void setEventDispatcher(std::unique_ptr<EventDispatcher> dispatcher); + +protected: + int exec(); + virtual void run(); + +private: + void startThread(); + void finishThread(); + + friend class ThreadData; + friend class ThreadMain; + + std::thread thread_; + ThreadData *data_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_THREAD_H__ */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index adc749ec..bad60da4 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -23,6 +23,7 @@ libcamera_sources = files([ 'request.cpp', 'signal.cpp', 'stream.cpp', + 'thread.cpp', 'timer.cpp', 'utils.cpp', 'v4l2_controls.cpp', @@ -45,6 +46,7 @@ libcamera_headers = files([ 'include/media_device.h', 'include/media_object.h', 'include/pipeline_handler.h', + 'include/thread.h', 'include/utils.h', 'include/v4l2_device.h', 'include/v4l2_subdevice.h', @@ -91,6 +93,7 @@ libcamera_sources += version_cpp libcamera_deps = [ cc.find_library('dl'), libudev, + dependency('threads'), ] libcamera = shared_library('camera', diff --git a/src/libcamera/thread.cpp b/src/libcamera/thread.cpp new file mode 100644 index 00000000..95636eca --- /dev/null +++ b/src/libcamera/thread.cpp @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * thread.cpp - Thread support + */ + +#include "thread.h" + +#include <atomic> + +#include <libcamera/event_dispatcher.h> + +#include "event_dispatcher_poll.h" +#include "log.h" + +/** + * \file thread.h + * \brief Thread support + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(Thread) + +class ThreadMain; + +/** + * \brief Thread-local internal data + */ +class ThreadData +{ +public: + ThreadData() + : thread_(nullptr), running_(false), dispatcher_(nullptr) + { + } + + static ThreadData *current(); + +private: + friend class Thread; + friend class ThreadMain; + + Thread *thread_; + bool running_; + + Mutex mutex_; + + std::atomic<EventDispatcher *> dispatcher_; + + std::atomic<bool> exit_; + int exitCode_; +}; + +/** + * \brief Thread wrapper for the main thread + */ +class ThreadMain : public Thread +{ +public: + ThreadMain() + { + data_->running_ = true; + } + +protected: + void run() override + { + LOG(Thread, Fatal) << "The main thread can't be restarted"; + } +}; + +static thread_local ThreadData *currentThreadData = nullptr; +static ThreadMain mainThread; + +/** + * \brief Retrieve thread-local internal data for the current thread + * \return The thread-local internal data for the current thread + */ +ThreadData *ThreadData::current() +{ + if (currentThreadData) + return currentThreadData; + + /* + * The main thread doesn't receive thread-local data when it is + * started, set it here. + */ + ThreadData *data = mainThread.data_; + currentThreadData = data; + return data; +} + +/** + * \typedef Mutex + * \brief An alias for std::mutex + */ + +/** + * \typedef MutexLocker + * \brief An alias for std::unique_lock<std::mutex> + */ + +/** + * \class Thread + * \brief A thread of execution + * + * The Thread class is a wrapper around std::thread that handles integration + * with the Object, Signal and EventDispatcher classes. + * + * Thread instances by default run an event loop until the exit() method is + * called. A custom event dispatcher may be installed with + * setEventDispatcher(), otherwise a poll-based event dispatcher is used. This + * behaviour can be overriden by overloading the run() method. + */ + +/** + * \brief Create a thread + */ +Thread::Thread() +{ + data_ = new ThreadData; + data_->thread_ = this; +} + +Thread::~Thread() +{ + delete data_->dispatcher_.load(std::memory_order_relaxed); + delete data_; +} + +/** + * \brief Start the thread + */ +void Thread::start() +{ + MutexLocker locker(data_->mutex_); + + if (data_->running_) + return; + + data_->running_ = true; + data_->exitCode_ = -1; + data_->exit_.store(false, std::memory_order_relaxed); + + thread_ = std::thread(&Thread::startThread, this); +} + +void Thread::startThread() +{ + struct ThreadCleaner { + ThreadCleaner(Thread *thread, void (Thread::*cleaner)()) + : thread_(thread), cleaner_(cleaner) + { + } + ~ThreadCleaner() + { + (thread_->*cleaner_)(); + } + + Thread *thread_; + void (Thread::*cleaner_)(); + }; + + /* + * Make sure the thread is cleaned up even if the run method exits + * abnormally (for instance via a direct call to pthread_cancel()). + */ + thread_local ThreadCleaner cleaner(this, &Thread::finishThread); + + currentThreadData = data_; + + run(); +} + +/** + * \brief Enter the event loop + * + * This method enter an event loop based on the event dispatcher instance for + * the thread, and blocks until the exit() method is called. It is meant to be + * called within the thread from the run() method and shall not be called + * outside of the thread. + * + * \return The exit code passed to the exit() method + */ +int Thread::exec() +{ + MutexLocker locker(data_->mutex_); + + EventDispatcher *dispatcher = eventDispatcher(); + + locker.unlock(); + + while (!data_->exit_.load(std::memory_order_acquire)) + dispatcher->processEvents(); + + locker.lock(); + + return data_->exitCode_; +} + +/** + * \brief Main method of the thread + * + * When the thread is started with start(), it calls this method in the context + * of the new thread. The run() method can be overloaded to perform custom + * work. When this method returns the thread execution is stopped, and the \ref + * finished signal is emitted. + * + * The base implementation just calls exec(). + */ +void Thread::run() +{ + exec(); +} + +void Thread::finishThread() +{ + data_->mutex_.lock(); + data_->running_ = false; + data_->mutex_.unlock(); + + finished.emit(this); +} + +/** + * \brief Stop the thread's event loop + * \param[in] code The exit code + * + * This method interrupts the event loop started by the exec() method, causing + * exec() to return \a code. + * + * Calling exit() on a thread that reimplements the run() method and doesn't + * call exec() will likely have no effect. + */ +void Thread::exit(int code) +{ + data_->exitCode_ = code; + data_->exit_.store(true, std::memory_order_release); + + EventDispatcher *dispatcher = data_->dispatcher_.load(std::memory_order_relaxed); + if (!dispatcher) + return; + + dispatcher->interrupt(); +} + +/** + * \brief Wait for the thread to finish + * + * This method waits until the thread finishes, or returns immediately if the + * thread is not running. + */ +void Thread::wait() +{ + if (thread_.joinable()) + thread_.join(); +} + +/** + * \brief Check if the thread is running + * + * A Thread instance is considered as running once the underlying thread has + * started. This method guarantees that it returns true after the start() + * method returns, and false after the wait() method returns. + * + * \return True if the thread is running, false otherwise + */ +bool Thread::isRunning() +{ + MutexLocker locker(data_->mutex_); + return data_->running_; +} + +/** + * \var Thread::finished + * \brief Signal the end of thread execution + */ + +/** + * \brief Retrieve the Thread instance for the current thread + * \return The Thread instance for the current thread + */ +Thread *Thread::current() +{ + ThreadData *data = ThreadData::current(); + return data->thread_; +} + +/** + * \brief Set the event dispatcher + * \param[in] dispatcher Pointer to the event dispatcher + * + * Threads that run an event loop require an event dispatcher to integrate + * event notification and timers with the loop. Users that want to provide + * their own event dispatcher shall call this method once and only once before + * the thread is started with start(). If no event dispatcher is provided, a + * default poll-based implementation will be used. + * + * The Thread takes ownership of the event dispatcher and will delete it when + * the thread is destroyed. + */ +void Thread::setEventDispatcher(std::unique_ptr<EventDispatcher> dispatcher) +{ + if (data_->dispatcher_.load(std::memory_order_relaxed)) { + LOG(Thread, Warning) << "Event dispatcher is already set"; + return; + } + + data_->dispatcher_.store(dispatcher.release(), + std::memory_order_relaxed); +} + +/** + * \brief Retrieve the event dispatcher + * + * This method retrieves the event dispatcher set with setEventDispatcher(). + * If no dispatcher has been set, a default poll-based implementation is created + * and returned, and no custom event dispatcher may be installed anymore. + * + * The returned event dispatcher is valid until the thread is destroyed. + * + * \return Pointer to the event dispatcher + */ +EventDispatcher *Thread::eventDispatcher() +{ + if (!data_->dispatcher_.load(std::memory_order_relaxed)) + data_->dispatcher_.store(new EventDispatcherPoll(), + std::memory_order_release); + + return data_->dispatcher_.load(std::memory_order_relaxed); +} + +}; /* namespace libcamera */ |