summaryrefslogtreecommitdiff
path: root/src/libcamera/base
diff options
context:
space:
mode:
authorKieran Bingham <kieran.bingham@ideasonboard.com>2021-06-15 16:15:12 +0100
committerKieran Bingham <kieran.bingham@ideasonboard.com>2021-06-25 16:11:08 +0100
commit27aff949fbc1b9aabfc594bbfd6f94be55a086ec (patch)
tree9ddbc2462a685a6db3ed33f09ed7a493376439d6 /src/libcamera/base
parent6410d1d37c1ea9d1d168840a7ba063facb0bc9d6 (diff)
libcamera/base: Move extended base functionality
Move the functionality for the following components to the new base support library: - BoundMethod - EventDispatcher - EventDispatcherPoll - Log - Message - Object - Signal - Semaphore - Thread - Timer While it would be preferable to see these split to move one component per commit, these components are all interdependent upon each other, which leaves us with one big change performing the move for all of them. Reviewed-by: Hirokazu Honda <hiroh@chromium.org> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Diffstat (limited to 'src/libcamera/base')
-rw-r--r--src/libcamera/base/bound_method.cpp109
-rw-r--r--src/libcamera/base/event_dispatcher.cpp116
-rw-r--r--src/libcamera/base/event_dispatcher_poll.cpp308
-rw-r--r--src/libcamera/base/log.cpp998
-rw-r--r--src/libcamera/base/meson.build11
-rw-r--r--src/libcamera/base/message.cpp166
-rw-r--r--src/libcamera/base/object.cpp300
-rw-r--r--src/libcamera/base/semaphore.cpp103
-rw-r--r--src/libcamera/base/signal.cpp179
-rw-r--r--src/libcamera/base/thread.cpp680
-rw-r--r--src/libcamera/base/timer.cpp185
11 files changed, 3155 insertions, 0 deletions
diff --git a/src/libcamera/base/bound_method.cpp b/src/libcamera/base/bound_method.cpp
new file mode 100644
index 00000000..3ecec51c
--- /dev/null
+++ b/src/libcamera/base/bound_method.cpp
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * bound_method.cpp - Method bind and invocation
+ */
+
+#include <libcamera/base/bound_method.h>
+#include <libcamera/base/message.h>
+#include <libcamera/base/semaphore.h>
+#include <libcamera/base/thread.h>
+
+/**
+ * \file base/bound_method.h
+ * \brief Method bind and invocation
+ */
+
+namespace libcamera {
+
+/**
+ * \enum ConnectionType
+ * \brief Connection type for asynchronous communication
+ *
+ * This enumeration describes the possible types of asynchronous communication
+ * between a sender and a receiver. It applies to Signal::emit() and
+ * Object::invokeMethod().
+ *
+ * \var ConnectionTypeAuto
+ * \brief If the sender and the receiver live in the same thread,
+ * ConnectionTypeDirect is used. Otherwise ConnectionTypeQueued is used.
+ *
+ * \var ConnectionTypeDirect
+ * \brief The receiver is invoked immediately and synchronously in the sender's
+ * thread.
+ *
+ * \var ConnectionTypeQueued
+ * \brief The receiver is invoked asynchronously
+ *
+ * Invoke the receiver asynchronously in its thread when control returns to the
+ * thread's event loop. The sender proceeds without waiting for the invocation
+ * to complete.
+ *
+ * \var ConnectionTypeBlocking
+ * \brief The receiver is invoked synchronously
+ *
+ * If the sender and the receiver live in the same thread, this is equivalent to
+ * ConnectionTypeDirect. Otherwise, the receiver is invoked asynchronously in
+ * its thread when control returns to the thread's event loop. The sender
+ * blocks until the receiver signals the completion of the invocation.
+ */
+
+/**
+ * \brief Invoke the bound method with packed arguments
+ * \param[in] pack Packed arguments
+ * \param[in] deleteMethod True to delete \a this bound method instance when
+ * method invocation completes
+ *
+ * The bound method stores its return value, if any, in the arguments \a pack.
+ * For direct and blocking invocations, this is performed synchronously, and
+ * the return value contained in the pack may be used. For queued invocations,
+ * the return value is stored at an undefined point of time and shall thus not
+ * be used by the caller.
+ *
+ * \return True if the return value contained in the \a pack may be used by the
+ * caller, false otherwise
+ */
+bool BoundMethodBase::activatePack(std::shared_ptr<BoundMethodPackBase> pack,
+ bool deleteMethod)
+{
+ ConnectionType type = connectionType_;
+ if (type == ConnectionTypeAuto) {
+ if (Thread::current() == object_->thread())
+ type = ConnectionTypeDirect;
+ else
+ type = ConnectionTypeQueued;
+ } else if (type == ConnectionTypeBlocking) {
+ if (Thread::current() == object_->thread())
+ type = ConnectionTypeDirect;
+ }
+
+ switch (type) {
+ case ConnectionTypeDirect:
+ default:
+ invokePack(pack.get());
+ if (deleteMethod)
+ delete this;
+ return true;
+
+ case ConnectionTypeQueued: {
+ std::unique_ptr<Message> msg =
+ std::make_unique<InvokeMessage>(this, pack, nullptr, deleteMethod);
+ object_->postMessage(std::move(msg));
+ return false;
+ }
+
+ case ConnectionTypeBlocking: {
+ Semaphore semaphore;
+
+ std::unique_ptr<Message> msg =
+ std::make_unique<InvokeMessage>(this, pack, &semaphore, deleteMethod);
+ object_->postMessage(std::move(msg));
+
+ semaphore.acquire();
+ return true;
+ }
+ }
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/event_dispatcher.cpp b/src/libcamera/base/event_dispatcher.cpp
new file mode 100644
index 00000000..4be89e81
--- /dev/null
+++ b/src/libcamera/base/event_dispatcher.cpp
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * event_dispatcher.cpp - Event dispatcher
+ */
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/log.h>
+
+/**
+ * \file base/event_dispatcher.h
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Event)
+
+/**
+ * \class EventDispatcher
+ * \brief Interface to manage the libcamera events and timers
+ *
+ * The EventDispatcher class allows the integration of the application event
+ * loop with libcamera by abstracting how events and timers are managed and
+ * processed.
+ *
+ * To listen to events, libcamera creates EventNotifier instances and registers
+ * them with the dispatcher with registerEventNotifier(). The event notifier
+ * \ref EventNotifier::activated signal is then emitted by the dispatcher
+ * whenever the event is detected.
+ *
+ * To set timers, libcamera creates Timer instances and registers them with the
+ * dispatcher with registerTimer(). The timer \ref Timer::timeout signal is then
+ * emitted by the dispatcher when the timer times out.
+ */
+
+EventDispatcher::~EventDispatcher()
+{
+}
+
+/**
+ * \fn EventDispatcher::registerEventNotifier()
+ * \brief Register an event notifier
+ * \param[in] notifier The event notifier to register
+ *
+ * Once the \a notifier is registered with the dispatcher, the dispatcher will
+ * emit the notifier \ref EventNotifier::activated signal whenever a
+ * corresponding event is detected on the notifier's file descriptor. The event
+ * is monitored until the notifier is unregistered with
+ * unregisterEventNotifier().
+ *
+ * Registering multiple notifiers for the same file descriptor and event type is
+ * not allowed and results in undefined behaviour.
+ */
+
+/**
+ * \fn EventDispatcher::unregisterEventNotifier()
+ * \brief Unregister an event notifier
+ * \param[in] notifier The event notifier to unregister
+ *
+ * After this function returns the \a notifier is guaranteed not to emit the
+ * \ref EventNotifier::activated signal.
+ *
+ * If the notifier isn't registered, this function performs no operation.
+ */
+
+/**
+ * \fn EventDispatcher::registerTimer()
+ * \brief Register a timer
+ * \param[in] timer The timer to register
+ *
+ * Once the \a timer is registered with the dispatcher, the dispatcher will emit
+ * the timer \ref Timer::timeout signal when the timer times out. The timer can
+ * be unregistered with unregisterTimer() before it times out, in which case the
+ * signal will not be emitted.
+ *
+ * When the \a timer times out, it is automatically unregistered by the
+ * dispatcher and can be registered back as early as from the \ref Timer::timeout
+ * signal handlers.
+ *
+ * Registering the same timer multiple times is not allowed and results in
+ * undefined behaviour.
+ */
+
+/**
+ * \fn EventDispatcher::unregisterTimer()
+ * \brief Unregister a timer
+ * \param[in] timer The timer to unregister
+ *
+ * After this function returns the \a timer is guaranteed not to emit the
+ * \ref Timer::timeout signal.
+ *
+ * If the timer isn't registered, this function performs no operation.
+ */
+
+/**
+ * \fn EventDispatcher::processEvents()
+ * \brief Wait for and process pending events
+ *
+ * This function processes all pending events associated with registered event
+ * notifiers and timers and signals the corresponding EventNotifier and Timer
+ * objects. If no events are pending, it waits for the first event and processes
+ * it before returning.
+ */
+
+/**
+ * \fn EventDispatcher::interrupt()
+ * \brief Interrupt any running processEvents() call as soon as possible
+ *
+ * Calling this function interrupts any blocking processEvents() call in
+ * progress. The processEvents() function will return as soon as possible,
+ * after processing pending timers and events. If processEvents() isn't in
+ * progress, it will be interrupted immediately the next time it gets called.
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/event_dispatcher_poll.cpp b/src/libcamera/base/event_dispatcher_poll.cpp
new file mode 100644
index 00000000..d76ca7fc
--- /dev/null
+++ b/src/libcamera/base/event_dispatcher_poll.cpp
@@ -0,0 +1,308 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * event_dispatcher_poll.cpp - Poll-based event dispatcher
+ */
+
+#include <libcamera/base/event_dispatcher_poll.h>
+
+#include <algorithm>
+#include <chrono>
+#include <iomanip>
+#include <poll.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <unistd.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/timer.h>
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/event_notifier.h"
+
+/**
+ * \file base/event_dispatcher_poll.h
+ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Event)
+
+static const char *notifierType(EventNotifier::Type type)
+{
+ if (type == EventNotifier::Read)
+ return "read";
+ if (type == EventNotifier::Write)
+ return "write";
+ if (type == EventNotifier::Exception)
+ return "exception";
+
+ return "";
+}
+
+/**
+ * \class EventDispatcherPoll
+ * \brief A poll-based event dispatcher
+ */
+
+EventDispatcherPoll::EventDispatcherPoll()
+ : processingEvents_(false)
+{
+ /*
+ * Create the event fd. Failures are fatal as we can't implement an
+ * interruptible dispatcher without the fd.
+ */
+ eventfd_ = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+ if (eventfd_ < 0)
+ LOG(Event, Fatal) << "Unable to create eventfd";
+}
+
+EventDispatcherPoll::~EventDispatcherPoll()
+{
+ close(eventfd_);
+}
+
+void EventDispatcherPoll::registerEventNotifier(EventNotifier *notifier)
+{
+ EventNotifierSetPoll &set = notifiers_[notifier->fd()];
+ EventNotifier::Type type = notifier->type();
+
+ if (set.notifiers[type] && set.notifiers[type] != notifier) {
+ LOG(Event, Warning)
+ << "Ignoring duplicate " << notifierType(type)
+ << " notifier for fd " << notifier->fd();
+ return;
+ }
+
+ set.notifiers[type] = notifier;
+}
+
+void EventDispatcherPoll::unregisterEventNotifier(EventNotifier *notifier)
+{
+ auto iter = notifiers_.find(notifier->fd());
+ if (iter == notifiers_.end())
+ return;
+
+ EventNotifierSetPoll &set = iter->second;
+ EventNotifier::Type type = notifier->type();
+
+ if (!set.notifiers[type])
+ return;
+
+ if (set.notifiers[type] != notifier) {
+ LOG(Event, Warning)
+ << notifierType(type) << " notifier for fd "
+ << notifier->fd() << " is not registered";
+ return;
+ }
+
+ set.notifiers[type] = nullptr;
+
+ /*
+ * Don't race with event processing if this method is called from an
+ * event notifier. The notifiers_ entry will be erased by
+ * processEvents().
+ */
+ if (processingEvents_)
+ return;
+
+ if (!set.notifiers[0] && !set.notifiers[1] && !set.notifiers[2])
+ notifiers_.erase(iter);
+}
+
+void EventDispatcherPoll::registerTimer(Timer *timer)
+{
+ for (auto iter = timers_.begin(); iter != timers_.end(); ++iter) {
+ if ((*iter)->deadline() > timer->deadline()) {
+ timers_.insert(iter, timer);
+ return;
+ }
+ }
+
+ timers_.push_back(timer);
+}
+
+void EventDispatcherPoll::unregisterTimer(Timer *timer)
+{
+ for (auto iter = timers_.begin(); iter != timers_.end(); ++iter) {
+ if (*iter == timer) {
+ timers_.erase(iter);
+ return;
+ }
+
+ /*
+ * As the timers list is ordered, we can stop as soon as we go
+ * past the deadline.
+ */
+ if ((*iter)->deadline() > timer->deadline())
+ break;
+ }
+}
+
+void EventDispatcherPoll::processEvents()
+{
+ int ret;
+
+ Thread::current()->dispatchMessages();
+
+ /* Create the pollfd array. */
+ std::vector<struct pollfd> pollfds;
+ pollfds.reserve(notifiers_.size() + 1);
+
+ for (auto notifier : notifiers_)
+ pollfds.push_back({ notifier.first, notifier.second.events(), 0 });
+
+ pollfds.push_back({ eventfd_, POLLIN, 0 });
+
+ /* Wait for events and process notifiers and timers. */
+ do {
+ ret = poll(&pollfds);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret < 0) {
+ ret = -errno;
+ LOG(Event, Warning) << "poll() failed with " << strerror(-ret);
+ } else if (ret > 0) {
+ processInterrupt(pollfds.back());
+ pollfds.pop_back();
+ processNotifiers(pollfds);
+ }
+
+ processTimers();
+}
+
+void EventDispatcherPoll::interrupt()
+{
+ uint64_t value = 1;
+ ssize_t ret = write(eventfd_, &value, sizeof(value));
+ if (ret != sizeof(value)) {
+ if (ret < 0)
+ ret = -errno;
+ LOG(Event, Error)
+ << "Failed to interrupt event dispatcher ("
+ << ret << ")";
+ }
+}
+
+short EventDispatcherPoll::EventNotifierSetPoll::events() const
+{
+ short events = 0;
+
+ if (notifiers[EventNotifier::Read])
+ events |= POLLIN;
+ if (notifiers[EventNotifier::Write])
+ events |= POLLOUT;
+ if (notifiers[EventNotifier::Exception])
+ events |= POLLPRI;
+
+ return events;
+}
+
+int EventDispatcherPoll::poll(std::vector<struct pollfd> *pollfds)
+{
+ /* Compute the timeout. */
+ Timer *nextTimer = !timers_.empty() ? timers_.front() : nullptr;
+ struct timespec timeout;
+
+ if (nextTimer) {
+ utils::time_point now = utils::clock::now();
+
+ if (nextTimer->deadline() > now)
+ timeout = utils::duration_to_timespec(nextTimer->deadline() - now);
+ else
+ timeout = { 0, 0 };
+
+ LOG(Event, Debug)
+ << "timeout " << timeout.tv_sec << "."
+ << std::setfill('0') << std::setw(9)
+ << timeout.tv_nsec;
+ }
+
+ return ppoll(pollfds->data(), pollfds->size(),
+ nextTimer ? &timeout : nullptr, nullptr);
+}
+
+void EventDispatcherPoll::processInterrupt(const struct pollfd &pfd)
+{
+ if (!(pfd.revents & POLLIN))
+ return;
+
+ uint64_t value;
+ ssize_t ret = read(eventfd_, &value, sizeof(value));
+ if (ret != sizeof(value)) {
+ if (ret < 0)
+ ret = -errno;
+ LOG(Event, Error)
+ << "Failed to process interrupt (" << ret << ")";
+ }
+}
+
+void EventDispatcherPoll::processNotifiers(const std::vector<struct pollfd> &pollfds)
+{
+ static const struct {
+ EventNotifier::Type type;
+ short events;
+ } events[] = {
+ { EventNotifier::Read, POLLIN },
+ { EventNotifier::Write, POLLOUT },
+ { EventNotifier::Exception, POLLPRI },
+ };
+
+ processingEvents_ = true;
+
+ for (const pollfd &pfd : pollfds) {
+ auto iter = notifiers_.find(pfd.fd);
+ ASSERT(iter != notifiers_.end());
+
+ EventNotifierSetPoll &set = iter->second;
+
+ for (const auto &event : events) {
+ EventNotifier *notifier = set.notifiers[event.type];
+
+ if (!notifier)
+ continue;
+
+ /*
+ * If the file descriptor is invalid, disable the
+ * notifier immediately.
+ */
+ if (pfd.revents & POLLNVAL) {
+ LOG(Event, Warning)
+ << "Disabling " << notifierType(event.type)
+ << " due to invalid file descriptor "
+ << pfd.fd;
+ unregisterEventNotifier(notifier);
+ continue;
+ }
+
+ if (pfd.revents & event.events)
+ notifier->activated.emit(notifier);
+ }
+
+ /* Erase the notifiers_ entry if it is now empty. */
+ if (!set.notifiers[0] && !set.notifiers[1] && !set.notifiers[2])
+ notifiers_.erase(iter);
+ }
+
+ processingEvents_ = false;
+}
+
+void EventDispatcherPoll::processTimers()
+{
+ utils::time_point now = utils::clock::now();
+
+ while (!timers_.empty()) {
+ Timer *timer = timers_.front();
+ if (timer->deadline() > now)
+ break;
+
+ timers_.pop_front();
+ timer->stop();
+ timer->timeout.emit(timer);
+ }
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
new file mode 100644
index 00000000..1801ae26
--- /dev/null
+++ b/src/libcamera/base/log.cpp
@@ -0,0 +1,998 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018, Google Inc.
+ *
+ * log.cpp - Logging infrastructure
+ */
+
+#include <libcamera/base/log.h>
+
+#include <array>
+#if HAVE_BACKTRACE
+#include <execinfo.h>
+#endif
+#include <fstream>
+#include <iostream>
+#include <list>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unordered_set>
+
+#include <libcamera/logging.h>
+
+#include <libcamera/base/thread.h>
+#include <libcamera/base/utils.h>
+
+/**
+ * \file base/log.h
+ * \brief Logging infrastructure
+ *
+ * libcamera includes a logging infrastructure used through the library that
+ * allows inspection of internal operation in a user-configurable way. The log
+ * messages are grouped in categories that represent areas of libcamera, and
+ * output of messages for each category can be controlled by independent log
+ * levels.
+ *
+ * The levels are configurable through the LIBCAMERA_LOG_LEVELS environment
+ * variable that contains a comma-separated list of 'category:level' pairs.
+ *
+ * The category names are strings and can include a wildcard ('*') character at
+ * the end to match multiple categories.
+ *
+ * The level are either numeric values, or strings containing the log level
+ * name. The available log levels are DEBUG, INFO, WARN, ERROR and FATAL. Log
+ * message with a level higher than or equal to the configured log level for
+ * their category are output to the log, while other messages are silently
+ * discarded.
+ *
+ * By default log messages are output to stderr. They can be redirected to a log
+ * file by setting the LIBCAMERA_LOG_FILE environment variable to the name of
+ * the file. The file must be writable and is truncated if it exists. If any
+ * error occurs when opening the file, the file is ignored and the log is output
+ * to stderr.
+ */
+
+/**
+ * \file logging.h
+ * \brief Logging management
+ *
+ * API to change the logging output destination and log levels programatically.
+ */
+
+namespace libcamera {
+
+static int log_severity_to_syslog(LogSeverity severity)
+{
+ switch (severity) {
+ case LogDebug:
+ return LOG_DEBUG;
+ case LogInfo:
+ return LOG_INFO;
+ case LogWarning:
+ return LOG_WARNING;
+ case LogError:
+ return LOG_ERR;
+ case LogFatal:
+ return LOG_ALERT;
+ default:
+ return LOG_NOTICE;
+ }
+}
+
+static const char *log_severity_name(LogSeverity severity)
+{
+ static const char *const names[] = {
+ "DEBUG",
+ " INFO",
+ " WARN",
+ "ERROR",
+ "FATAL",
+ };
+
+ if (static_cast<unsigned int>(severity) < std::size(names))
+ return names[severity];
+ else
+ return "UNKWN";
+}
+
+/**
+ * \brief Log output
+ *
+ * The LogOutput class models a log output destination
+ */
+class LogOutput
+{
+public:
+ LogOutput(const char *path);
+ LogOutput(std::ostream *stream);
+ LogOutput();
+ ~LogOutput();
+
+ bool isValid() const;
+ void write(const LogMessage &msg);
+ void write(const std::string &msg);
+
+private:
+ void writeSyslog(LogSeverity severity, const std::string &msg);
+ void writeStream(const std::string &msg);
+
+ std::ostream *stream_;
+ LoggingTarget target_;
+};
+
+/**
+ * \brief Construct a log output based on a file
+ * \param[in] path Full path to log file
+ */
+LogOutput::LogOutput(const char *path)
+ : target_(LoggingTargetFile)
+{
+ stream_ = new std::ofstream(path);
+}
+
+/**
+ * \brief Construct a log output based on a stream
+ * \param[in] stream Stream to send log output to
+ */
+LogOutput::LogOutput(std::ostream *stream)
+ : stream_(stream), target_(LoggingTargetStream)
+{
+}
+
+/**
+ * \brief Construct a log output to syslog
+ */
+LogOutput::LogOutput()
+ : stream_(nullptr), target_(LoggingTargetSyslog)
+{
+ openlog("libcamera", LOG_PID, 0);
+}
+
+LogOutput::~LogOutput()
+{
+ switch (target_) {
+ case LoggingTargetFile:
+ delete stream_;
+ break;
+ case LoggingTargetSyslog:
+ closelog();
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * \brief Check if the log output is valid
+ * \return True if the log output is valid
+ */
+bool LogOutput::isValid() const
+{
+ switch (target_) {
+ case LoggingTargetFile:
+ return stream_->good();
+ case LoggingTargetStream:
+ return stream_ != nullptr;
+ default:
+ return true;
+ }
+}
+
+/**
+ * \brief Write message to log output
+ * \param[in] msg Message to write
+ */
+void LogOutput::write(const LogMessage &msg)
+{
+ std::string str;
+
+ switch (target_) {
+ case LoggingTargetSyslog:
+ str = std::string(log_severity_name(msg.severity())) + " "
+ + msg.category().name() + " " + msg.fileInfo() + " "
+ + msg.msg();
+ writeSyslog(msg.severity(), str);
+ break;
+ case LoggingTargetStream:
+ case LoggingTargetFile:
+ str = "[" + utils::time_point_to_string(msg.timestamp()) + "] ["
+ + std::to_string(Thread::currentId()) + "] "
+ + log_severity_name(msg.severity()) + " "
+ + msg.category().name() + " " + msg.fileInfo() + " "
+ + msg.msg();
+ writeStream(str);
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * \brief Write string to log output
+ * \param[in] str String to write
+ */
+void LogOutput::write(const std::string &str)
+{
+ switch (target_) {
+ case LoggingTargetSyslog:
+ writeSyslog(LogDebug, str);
+ break;
+ case LoggingTargetStream:
+ case LoggingTargetFile:
+ writeStream(str);
+ break;
+ default:
+ break;
+ }
+}
+
+void LogOutput::writeSyslog(LogSeverity severity, const std::string &str)
+{
+ syslog(log_severity_to_syslog(severity), "%s", str.c_str());
+}
+
+void LogOutput::writeStream(const std::string &str)
+{
+ stream_->write(str.c_str(), str.size());
+ stream_->flush();
+}
+
+/**
+ * \brief Message logger
+ *
+ * The Logger class handles log configuration.
+ */
+class Logger
+{
+public:
+ ~Logger();
+
+ static Logger *instance();
+
+ void write(const LogMessage &msg);
+ void backtrace();
+
+ int logSetFile(const char *path);
+ int logSetStream(std::ostream *stream);
+ int logSetTarget(LoggingTarget target);
+ void logSetLevel(const char *category, const char *level);
+
+private:
+ Logger();
+
+ void parseLogFile();
+ void parseLogLevels();
+ static LogSeverity parseLogLevel(const std::string &level);
+
+ friend LogCategory;
+ void registerCategory(LogCategory *category);
+
+ std::unordered_set<LogCategory *> categories_;
+ std::list<std::pair<std::string, LogSeverity>> levels_;
+
+ std::shared_ptr<LogOutput> output_;
+};
+
+/**
+ * \enum LoggingTarget
+ * \brief Log destination type
+ * \var LoggingTargetNone
+ * \brief No logging destination
+ * \sa Logger::logSetTarget
+ * \var LoggingTargetSyslog
+ * \brief Log to syslog
+ * \sa Logger::logSetTarget
+ * \var LoggingTargetFile
+ * \brief Log to file
+ * \sa Logger::logSetFile
+ * \var LoggingTargetStream
+ * \brief Log to stream
+ * \sa Logger::logSetStream
+ */
+
+/**
+ * \brief Direct logging to a file
+ * \param[in] path Full path to the log file
+ *
+ * This function directs the log output to the file identified by \a path. The
+ * previous log target, if any, is closed, and all new log messages will be
+ * written to the new log file.
+ *
+ * If the function returns an error, the log target is not changed.
+ *
+ * \return Zero on success, or a negative error code otherwise
+ */
+int logSetFile(const char *path)
+{
+ return Logger::instance()->logSetFile(path);
+}
+
+/**
+ * \brief Direct logging to a stream
+ * \param[in] stream Stream to send log output to
+ *
+ * This function directs the log output to \a stream. The previous log target,
+ * if any, is closed, and all new log messages will be written to the new log
+ * stream.
+ *
+ * If the function returns an error, the log file is not changed
+ *
+ * \return Zero on success, or a negative error code otherwise.
+ */
+int logSetStream(std::ostream *stream)
+{
+ return Logger::instance()->logSetStream(stream);
+}
+
+/**
+ * \brief Set the logging target
+ * \param[in] target Logging destination
+ *
+ * This function sets the logging output to the target specified by \a target.
+ * The allowed values of \a target are LoggingTargetNone and
+ * LoggingTargetSyslog. LoggingTargetNone will send the log output to nowhere,
+ * and LoggingTargetSyslog will send the log output to syslog. The previous
+ * log target, if any, is closed, and all new log messages will be written to
+ * the new log destination.
+ *
+ * LoggingTargetFile and LoggingTargetStream are not valid values for \a target.
+ * Use logSetFile() and logSetStream() instead, respectively.
+ *
+ * If the function returns an error, the log file is not changed.
+ *
+ * \return Zero on success, or a negative error code otherwise.
+ */
+int logSetTarget(LoggingTarget target)
+{
+ return Logger::instance()->logSetTarget(target);
+}
+
+/**
+ * \brief Set the log level
+ * \param[in] category Logging category
+ * \param[in] level Log level
+ *
+ * This function sets the log level of \a category to \a level.
+ * \a level shall be one of the following strings:
+ * - "DEBUG"
+ * - "INFO"
+ * - "WARN"
+ * - "ERROR"
+ * - "FATAL"
+ *
+ * "*" is not a valid \a category for this function.
+ */
+void logSetLevel(const char *category, const char *level)
+{
+ Logger::instance()->logSetLevel(category, level);
+}
+
+Logger::~Logger()
+{
+ for (LogCategory *category : categories_)
+ delete category;
+}
+
+/**
+ * \brief Retrieve the logger instance
+ *
+ * The Logger is a singleton and can't be constructed manually. This function
+ * shall instead be used to retrieve the single global instance of the logger.
+ *
+ * \return The logger instance
+ */
+Logger *Logger::instance()
+{
+ static Logger instance;
+ return &instance;
+}
+
+/**
+ * \brief Write a message to the configured logger output
+ * \param[in] msg The message object
+ */
+void Logger::write(const LogMessage &msg)
+{
+ std::shared_ptr<LogOutput> output = std::atomic_load(&output_);
+ if (!output)
+ return;
+
+ output->write(msg);
+}
+
+/**
+ * \brief Write a backtrace to the log
+ */
+void Logger::backtrace()
+{
+#if HAVE_BACKTRACE
+ std::shared_ptr<LogOutput> output = std::atomic_load(&output_);
+ if (!output)
+ return;
+
+ void *buffer[32];
+ int num_entries = ::backtrace(buffer, std::size(buffer));
+ char **strings = backtrace_symbols(buffer, num_entries);
+ if (!strings)
+ return;
+
+ std::ostringstream msg;
+ msg << "Backtrace:" << std::endl;
+
+ /*
+ * Skip the first two entries that correspond to this method and
+ * ~LogMessage().
+ */
+ for (int i = 2; i < num_entries; ++i)
+ msg << strings[i] << std::endl;
+
+ output->write(msg.str());
+
+ free(strings);
+#endif
+}
+
+/**
+ * \brief Set the log file
+ * \param[in] path Full path to the log file
+ *
+ * \sa libcamera::logSetFile()
+ *
+ * \return Zero on success, or a negative error code otherwise.
+ */
+int Logger::logSetFile(const char *path)
+{
+ std::shared_ptr<LogOutput> output = std::make_shared<LogOutput>(path);
+ if (!output->isValid())
+ return -EINVAL;
+
+ std::atomic_store(&output_, output);
+ return 0;
+}
+
+/**
+ * \brief Set the log stream
+ * \param[in] stream Stream to send log output to
+ *
+ * \sa libcamera::logSetStream()
+ *
+ * \return Zero on success, or a negative error code otherwise.
+ */
+int Logger::logSetStream(std::ostream *stream)
+{
+ std::shared_ptr<LogOutput> output = std::make_shared<LogOutput>(stream);
+ std::atomic_store(&output_, output);
+ return 0;
+}
+
+/**
+ * \brief Set the log target
+ * \param[in] target Log destination
+ *
+ * \sa libcamera::logSetTarget()
+ *
+ * \return Zero on success, or a negative error code otherwise.
+ */
+int Logger::logSetTarget(enum LoggingTarget target)
+{
+ std::shared_ptr<LogOutput> output;
+
+ switch (target) {
+ case LoggingTargetSyslog:
+ output = std::make_shared<LogOutput>();
+ std::atomic_store(&output_, output);
+ break;
+ case LoggingTargetNone:
+ output = nullptr;
+ std::atomic_store(&output_, std::shared_ptr<LogOutput>());
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * \brief Set the log level
+ * \param[in] category Logging category
+ * \param[in] level Log level
+ *
+ * \sa libcamera::logSetLevel()
+ */
+void Logger::logSetLevel(const char *category, const char *level)
+{
+ LogSeverity severity = parseLogLevel(level);
+ if (severity == LogInvalid)
+ return;
+
+ for (LogCategory *c : categories_) {
+ if (!strcmp(c->name(), category)) {
+ c->setSeverity(severity);
+ break;
+ }
+ }
+}
+
+/**
+ * \brief Construct a logger
+ */
+Logger::Logger()
+{
+ parseLogFile();
+ parseLogLevels();
+}
+
+/**
+ * \brief Parse the log output file from the environment
+ *
+ * If the LIBCAMERA_LOG_FILE environment variable is set, open the file it
+ * points to and redirect the logger output to it. If the environment variable
+ * is set to "syslog", then the logger output will be directed to syslog. Errors
+ * are silently ignored and don't affect the logger output (set to stderr).
+ */
+void Logger::parseLogFile()
+{
+ const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE");
+ if (!file) {
+ logSetStream(&std::cerr);
+ return;
+ }
+
+ if (!strcmp(file, "syslog")) {
+ logSetTarget(LoggingTargetSyslog);
+ return;
+ }
+
+ logSetFile(file);
+}
+
+/**
+ * \brief Parse the log levels from the environment
+ *
+ * The log levels are stored in the LIBCAMERA_LOG_LEVELS environment variable
+ * as a list of "category:level" pairs, separated by commas (','). Parse the
+ * variable and store the levels to configure all log categories.
+ */
+void Logger::parseLogLevels()
+{
+ const char *debug = utils::secure_getenv("LIBCAMERA_LOG_LEVELS");
+ if (!debug)
+ return;
+
+ for (const char *pair = debug; *debug != '\0'; pair = debug) {
+ const char *comma = strchrnul(debug, ',');
+ size_t len = comma - pair;
+
+ /* Skip over the comma. */
+ debug = *comma == ',' ? comma + 1 : comma;
+
+ /* Skip to the next pair if the pair is empty. */
+ if (!len)
+ continue;
+
+ std::string category;
+ std::string level;
+
+ const char *colon = static_cast<const char *>(memchr(pair, ':', len));
+ if (!colon) {
+ /* 'x' is a shortcut for '*:x'. */
+ category = "*";
+ level = std::string(pair, len);
+ } else {
+ category = std::string(pair, colon - pair);
+ level = std::string(colon + 1, comma - colon - 1);
+ }
+
+ /* Both the category and the level must be specified. */
+ if (category.empty() || level.empty())
+ continue;
+
+ LogSeverity severity = parseLogLevel(level);
+ if (severity == LogInvalid)
+ continue;
+
+ levels_.push_back({ category, severity });
+ }
+}
+
+/**
+ * \brief Parse a log level string into a LogSeverity
+ * \param[in] level The log level string
+ *
+ * Log levels can be specified as an integer value in the range from LogDebug to
+ * LogFatal, or as a string corresponding to the severity name in uppercase. Any
+ * other value is invalid.
+ *
+ * \return The log severity, or LogInvalid if the string is invalid
+ */
+LogSeverity Logger::parseLogLevel(const std::string &level)
+{
+ static const char *const names[] = {
+ "DEBUG",
+ "INFO",
+ "WARN",
+ "ERROR",
+ "FATAL",
+ };
+
+ int severity;
+
+ if (std::isdigit(level[0])) {
+ char *endptr;
+ severity = strtoul(level.c_str(), &endptr, 10);
+ if (*endptr != '\0' || severity > LogFatal)
+ severity = LogInvalid;
+ } else {
+ severity = LogInvalid;
+ for (unsigned int i = 0; i < std::size(names); ++i) {
+ if (names[i] == level) {
+ severity = i;
+ break;
+ }
+ }
+ }
+
+ return static_cast<LogSeverity>(severity);
+}
+
+/**
+ * \brief Register a log category with the logger
+ * \param[in] category The log category
+ *
+ * Log categories must have unique names. If a category with the same name
+ * already exists this function performs no operation.
+ */
+void Logger::registerCategory(LogCategory *category)
+{
+ categories_.insert(category);
+
+ const std::string &name = category->name();
+ for (const std::pair<std::string, LogSeverity> &level : levels_) {
+ bool match = true;
+
+ for (unsigned int i = 0; i < level.first.size(); ++i) {
+ if (level.first[i] == '*')
+ break;
+
+ if (i >= name.size() ||
+ name[i] != level.first[i]) {
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ category->setSeverity(level.second);
+ break;
+ }
+ }
+}
+
+/**
+ * \enum LogSeverity
+ * Log message severity
+ * \var LogDebug
+ * Debug message
+ * \var LogInfo
+ * Informational message
+ * \var LogWarning
+ * Warning message, signals a potential issue
+ * \var LogError
+ * Error message, signals an unrecoverable issue
+ * \var LogFatal
+ * Fatal message, signals an unrecoverable issue and aborts execution
+ */
+
+/**
+ * \class LogCategory
+ * \brief A category of log message
+ *
+ * The LogCategory class represents a category of log messages, related to an
+ * area of the library. It groups all messages belonging to the same category,
+ * and is used to control the log level per group.
+ */
+
+/**
+ * \brief Construct a log category
+ * \param[in] name The category name
+ */
+LogCategory::LogCategory(const char *name)
+ : name_(name), severity_(LogSeverity::LogInfo)
+{
+ Logger::instance()->registerCategory(this);
+}
+
+/**
+ * \fn LogCategory::name()
+ * \brief Retrieve the log category name
+ * \return The log category name
+ */
+
+/**
+ * \fn LogCategory::severity()
+ * \brief Retrieve the severity of the log category
+ * \sa setSeverity()
+ * \return Return the severity of the log category
+ */
+
+/**
+ * \brief Set the severity of the log category
+ *
+ * Messages of severity higher than or equal to the severity of the log category
+ * are printed, other messages are discarded.
+ */
+void LogCategory::setSeverity(LogSeverity severity)
+{
+ severity_ = severity;
+}
+
+/**
+ * \brief Retrieve the default log category
+ *
+ * The default log category is named "default" and is used by the LOG() macro
+ * when no log category is specified.
+ *
+ * \return A reference to the default log category
+ */
+const LogCategory &LogCategory::defaultCategory()
+{
+ static const LogCategory *category = new LogCategory("default");
+ return *category;
+}
+
+/**
+ * \class LogMessage
+ * \brief Internal log message representation.
+ *
+ * The LogMessage class models a single message in the log. It serves as a
+ * helper to provide the std::ostream API for logging, and must never be used
+ * directly. Use the LOG() macro instead access the log infrastructure.
+ */
+
+/**
+ * \brief Construct a log message for a given category
+ * \param[in] fileName The file name where the message is logged from
+ * \param[in] line The line number where the message is logged from
+ * \param[in] category The log message category, controlling how the message
+ * will be displayed
+ * \param[in] severity The log message severity, controlling how the message
+ * will be displayed
+ *
+ * Create a log message pertaining to line \a line of file \a fileName. The
+ * \a severity argument sets the message severity to control whether it will be
+ * output or dropped.
+ */
+LogMessage::LogMessage(const char *fileName, unsigned int line,
+ const LogCategory &category, LogSeverity severity)
+ : category_(category), severity_(severity)
+{
+ init(fileName, line);
+}
+
+/**
+ * \brief Move-construct a log message
+ * \param[in] other The other message
+ *
+ * The move constructor is meant to support the _log() functions. Thanks to copy
+ * elision it will likely never be called, but C++11 only permits copy elision,
+ * it doesn't enforce it unlike C++17. To avoid potential link errors depending
+ * on the compiler type and version, and optimization level, the move
+ * constructor is defined even if it will likely never be called, and ensures
+ * that the destructor of the \a other message will not output anything to the
+ * log by setting the severity to LogInvalid.
+ */
+LogMessage::LogMessage(LogMessage &&other)
+ : msgStream_(std::move(other.msgStream_)), category_(other.category_),
+ severity_(other.severity_)
+{
+ other.severity_ = LogInvalid;
+}
+
+void LogMessage::init(const char *fileName, unsigned int line)
+{
+ /* Log the timestamp, severity and file information. */
+ timestamp_ = utils::clock::now();
+
+ std::ostringstream ossFileInfo;
+ ossFileInfo << utils::basename(fileName) << ":" << line;
+ fileInfo_ = ossFileInfo.str();
+}
+
+LogMessage::~LogMessage()
+{
+ /* Don't print anything if we have been moved to another LogMessage. */
+ if (severity_ == LogInvalid)
+ return;
+
+ msgStream_ << std::endl;
+
+ if (severity_ >= category_.severity())
+ Logger::instance()->write(*this);
+
+ if (severity_ == LogSeverity::LogFatal) {
+ Logger::instance()->backtrace();
+ std::abort();
+ }
+}
+
+/**
+ * \fn std::ostream& LogMessage::stream()
+ *
+ * Data is added to a LogMessage through the stream returned by this function.
+ * The stream implements the std::ostream API and can be used for logging as
+ * std::cout.
+ *
+ * \return A reference to the log message stream
+ */
+
+/**
+ * \fn LogMessage::timestamp()
+ * \brief Retrieve the timestamp of the log message
+ * \return The timestamp of the message
+ */
+
+/**
+ * \fn LogMessage::severity()
+ * \brief Retrieve the severity of the log message
+ * \return The severity of the message
+ */
+
+/**
+ * \fn LogMessage::category()
+ * \brief Retrieve the category of the log message
+ * \return The category of the message
+ */
+
+/**
+ * \fn LogMessage::fileInfo()
+ * \brief Retrieve the file info of the log message
+ * \return The file info of the message
+ */
+
+/**
+ * \fn LogMessage::msg()
+ * \brief Retrieve the message text of the log message
+ * \return The message text of the message, as a string
+ */
+
+/**
+ * \class Loggable
+ * \brief Base class to support log message extensions
+ *
+ * The Loggable class allows classes to extend log messages without any change
+ * to the way the LOG() macro is invoked. By inheriting from Loggable and
+ * implementing the logPrefix() virtual method, a class can specify extra
+ * information to be automatically added to messages logged from class member
+ * methods.
+ */
+
+Loggable::~Loggable()
+{
+}
+
+/**
+ * \fn Loggable::logPrefix()
+ * \brief Retrieve a string to be prefixed to the log message
+ *
+ * This method allows classes inheriting from the Loggable class to extend the
+ * logger with an object-specific prefix output right before the log message
+ * contents.
+ *
+ * \return A string to be prefixed to the log message
+ */
+
+/**
+ * \brief Create a temporary LogMessage object to log a message
+ * \param[in] category The log message category
+ * \param[in] severity The log message severity
+ * \param[in] fileName The file name where the message is logged from
+ * \param[in] line The line number where the message is logged from
+ *
+ * This method is used as a backeng by the LOG() macro to create a log message
+ * for locations inheriting from the Loggable class.
+ *
+ * \return A log message
+ */
+LogMessage Loggable::_log(const LogCategory *category, LogSeverity severity,
+ const char *fileName, unsigned int line) const
+{
+ LogMessage msg(fileName, line,
+ category ? *category : LogCategory::defaultCategory(),
+ severity);
+
+ msg.stream() << logPrefix() << ": ";
+ return msg;
+}
+
+/**
+ * \brief Create a temporary LogMessage object to log a message
+ * \param[in] category The log message category
+ * \param[in] severity The log message severity
+ * \param[in] fileName The file name where the message is logged from
+ * \param[in] line The line number where the message is logged from
+ *
+ * This function is used as a backeng by the LOG() macro to create a log
+ * message for locations not inheriting from the Loggable class.
+ *
+ * \return A log message
+ */
+LogMessage _log(const LogCategory *category, LogSeverity severity,
+ const char *fileName, unsigned int line)
+{
+ return LogMessage(fileName, line,
+ category ? *category : LogCategory::defaultCategory(),
+ severity);
+}
+
+/**
+ * \def LOG_DECLARE_CATEGORY(name)
+ * \hideinitializer
+ * \brief Declare a category of log messages
+ *
+ * This macro is used to declare a log category defined in another compilation
+ * unit by the LOG_DEFINE_CATEGORY() macro.
+ *
+ * The LOG_DECLARE_CATEGORY() macro must be used in the libcamera namespace.
+ *
+ * \sa LogCategory
+ */
+
+/**
+ * \def LOG_DEFINE_CATEGORY(name)
+ * \hideinitializer
+ * \brief Define a category of log messages
+ *
+ * This macro is used to define a log category that can then be used with the
+ * LOGC() macro. Category names shall be unique, if a category is shared between
+ * compilation units, it shall be defined in one compilation unit only and
+ * declared with LOG_DECLARE_CATEGORY() in the other compilation units.
+ *
+ * The LOG_DEFINE_CATEGORY() macro must be used in the libcamera namespace.
+ *
+ * \sa LogCategory
+ */
+
+/**
+ * \def LOG(category, severity)
+ * \hideinitializer
+ * \brief Log a message
+ * \param[in] category Category (optional)
+ * \param[in] severity Severity
+ *
+ * Return an std::ostream reference to which a message can be logged using the
+ * iostream API. The \a category, if specified, sets the message category. When
+ * absent the default category is used. The \a severity controls whether the
+ * message is printed or discarded, depending on the log level for the category.
+ *
+ * If the severity is set to Fatal, execution is aborted and the program
+ * terminates immediately after printing the message.
+ *
+ * \warning Logging from the destructor of a global object, either directly or
+ * indirectly, results in undefined behaviour.
+ *
+ * \todo Allow logging from destructors of global objects to the largest
+ * possible extent
+ */
+
+/**
+ * \def ASSERT(condition)
+ * \hideinitializer
+ * \brief Abort program execution if assertion fails
+ *
+ * If \a condition is false, ASSERT() logs an error message with the Fatal log
+ * level and aborts program execution.
+ *
+ * If the macro NDEBUG is defined before including log.h, ASSERT() generates no
+ * code.
+ *
+ * Using conditions that have side effects with ASSERT() is not recommended, as
+ * these effects would depend on whether NDEBUG is defined or not. Similarly,
+ * ASSERT() should not be used to check for errors that can occur under normal
+ * conditions as those checks would then be removed when compiling with NDEBUG.
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
index 302a2886..7a19c67c 100644
--- a/src/libcamera/base/meson.build
+++ b/src/libcamera/base/meson.build
@@ -2,10 +2,21 @@
libcamera_base_sources = files([
'class.cpp',
+ 'bound_method.cpp',
+ 'event_dispatcher.cpp',
+ 'event_dispatcher_poll.cpp',
+ 'log.cpp',
+ 'message.cpp',
+ 'object.cpp',
+ 'semaphore.cpp',
+ 'signal.cpp',
+ 'thread.cpp',
+ 'timer.cpp',
'utils.cpp',
])
libcamera_base_deps = [
+ dependency('threads'),
]
libcamera_base_lib = shared_library('libcamera-base',
diff --git a/src/libcamera/base/message.cpp b/src/libcamera/base/message.cpp
new file mode 100644
index 00000000..f1d772e4
--- /dev/null
+++ b/src/libcamera/base/message.cpp
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * message.cpp - Message queue support
+ */
+
+#include <libcamera/base/message.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/signal.h>
+
+/**
+ * \file base/message.h
+ * \brief Message queue support
+ *
+ * The messaging API enables inter-thread communication through message
+ * posting. Messages can be sent from any thread to any recipient deriving from
+ * the Object class.
+ *
+ * To post a message, the sender allocates it dynamically as instance of a class
+ * derived from Message. It then posts the message to an Object recipient
+ * through Object::postMessage(). Message ownership is passed to the object,
+ * thus the message shall not store any temporary data.
+ *
+ * The message is delivered in the context of the object's thread, through the
+ * Object::message() virtual method. After delivery the message is
+ * automatically deleted.
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Message)
+
+std::atomic_uint Message::nextUserType_{ Message::UserMessage };
+
+/**
+ * \class Message
+ * \brief A message that can be posted to a Thread
+ */
+
+/**
+ * \enum Message::Type
+ * \brief The message type
+ * \var Message::None
+ * \brief Invalid message type
+ * \var Message::InvokeMessage
+ * \brief Asynchronous method invocation across threads
+ * \var Message::ThreadMoveMessage
+ * \brief Object is being moved to a different thread
+ * \var Message::DeferredDelete
+ * \brief Object is scheduled for deletion
+ * \var Message::UserMessage
+ * \brief First value available for user-defined messages
+ */
+
+/**
+ * \brief Construct a message object of type \a type
+ * \param[in] type The message type
+ */
+Message::Message(Message::Type type)
+ : type_(type)
+{
+}
+
+Message::~Message()
+{
+}
+
+/**
+ * \fn Message::type()
+ * \brief Retrieve the message type
+ * \return The message type
+ */
+
+/**
+ * \fn Message::receiver()
+ * \brief Retrieve the message receiver
+ * \return The message receiver
+ */
+
+/**
+ * \brief Reserve and register a custom user-defined message type
+ *
+ * Custom message types use values starting at Message::UserMessage. Assigning
+ * custom types manually may lead to accidental duplicated types. To avoid this
+ * problem, this method reserves and returns the next available user-defined
+ * message type.
+ *
+ * The recommended way to use this method is to subclass Message and provide a
+ * static accessor for the custom message type.
+ *
+ * \code{.cpp}
+ * class MyCustomMessage : public Message
+ * {
+ * public:
+ * MyCustomMessage() : Message(type()) {}
+ *
+ * static Message::Type type()
+ * {
+ * static MessageType type = registerMessageType();
+ * return type;
+ * }
+ * };
+ * \endcode
+ *
+ * \return A new unique message type
+ */
+Message::Type Message::registerMessageType()
+{
+ return static_cast<Message::Type>(nextUserType_++);
+}
+
+/**
+ * \class InvokeMessage
+ * \brief A message carrying a method invocation across threads
+ */
+
+/**
+ * \brief Construct an InvokeMessage for method invocation on an Object
+ * \param[in] method The bound method
+ * \param[in] pack The packed method arguments
+ * \param[in] semaphore The semaphore used to signal message delivery
+ * \param[in] deleteMethod True to delete the \a method when the message is
+ * destroyed
+ */
+InvokeMessage::InvokeMessage(BoundMethodBase *method,
+ std::shared_ptr<BoundMethodPackBase> pack,
+ Semaphore *semaphore, bool deleteMethod)
+ : Message(Message::InvokeMessage), method_(method), pack_(pack),
+ semaphore_(semaphore), deleteMethod_(deleteMethod)
+{
+}
+
+InvokeMessage::~InvokeMessage()
+{
+ if (deleteMethod_)
+ delete method_;
+}
+
+/**
+ * \fn InvokeMessage::semaphore()
+ * \brief Retrieve the message semaphore passed to the constructor
+ * \return The message semaphore
+ */
+
+/**
+ * \brief Invoke the method bound to InvokeMessage::method_ with arguments
+ * InvokeMessage::pack_
+ */
+void InvokeMessage::invoke()
+{
+ method_->invokePack(pack_.get());
+}
+
+/**
+ * \var InvokeMessage::method_
+ * \brief The method to be invoked
+ */
+
+/**
+ * \var InvokeMessage::pack_
+ * \brief The packed method invocation arguments
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/object.cpp b/src/libcamera/base/object.cpp
new file mode 100644
index 00000000..25410ecd
--- /dev/null
+++ b/src/libcamera/base/object.cpp
@@ -0,0 +1,300 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * object.cpp - Base object
+ */
+
+#include <libcamera/base/object.h>
+
+#include <algorithm>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/message.h>
+#include <libcamera/base/semaphore.h>
+#include <libcamera/base/signal.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/utils.h>
+
+/**
+ * \file base/object.h
+ * \brief Base object to support automatic signal disconnection
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Object)
+
+/**
+ * \class Object
+ * \brief Base object to support automatic signal disconnection
+ *
+ * The Object class simplifies signal/slot handling for classes implementing
+ * slots. By inheriting from Object, an object is automatically disconnected
+ * from all connected signals when it gets destroyed.
+ *
+ * Object instances are bound to the thread of their parent, or the thread in
+ * which they're created when they have no parent. When a message is posted to
+ * an object, its handler will run in the object's thread. This allows
+ * implementing easy message passing between threads by inheriting from the
+ * Object class.
+ *
+ * Deleting an object from a thread other than the one the object is bound to is
+ * unsafe, unless the caller ensures that the object isn't processing any
+ * message concurrently.
+ *
+ * Object slots connected to signals will also run in the context of the
+ * object's thread, regardless of whether the signal is emitted in the same or
+ * in another thread.
+ *
+ * \sa Message, Signal, Thread
+ */
+
+/**
+ * \brief Construct an Object instance
+ * \param[in] parent The object parent
+ *
+ * The new Object instance is bound to the thread of its \a parent, or to the
+ * current thread if the \a parent is nullptr.
+ */
+Object::Object(Object *parent)
+ : parent_(parent), pendingMessages_(0)
+{
+ thread_ = parent ? parent->thread() : Thread::current();
+
+ if (parent)
+ parent->children_.push_back(this);
+}
+
+/**
+ * \brief Destroy an Object instance
+ *
+ * Deleting an Object automatically disconnects all signals from the Object's
+ * slots. All the Object's children are made orphan, but stay bound to their
+ * current thread.
+ *
+ * Object instances shall be destroyed from the thread they are bound to,
+ * otherwise undefined behaviour may occur. If deletion of an Object needs to
+ * be scheduled from a different thread, deleteLater() shall be used.
+ */
+Object::~Object()
+{
+ /*
+ * Move signals to a private list to avoid concurrent iteration and
+ * deletion of items from Signal::disconnect().
+ */
+ std::list<SignalBase *> signals(std::move(signals_));
+ for (SignalBase *signal : signals)
+ signal->disconnect(this);
+
+ if (pendingMessages_)
+ thread()->removeMessages(this);
+
+ if (parent_) {
+ auto it = std::find(parent_->children_.begin(),
+ parent_->children_.end(), this);
+ ASSERT(it != parent_->children_.end());
+ parent_->children_.erase(it);
+ }
+
+ for (auto child : children_)
+ child->parent_ = 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<MyObject> createObject()
+ * {
+ * struct Deleter : std::default_delete<MyObject> {
+ * void operator()(MyObject *obj)
+ * {
+ * obj->deleteLater();
+ * }
+ * };
+ *
+ * MyObject *obj = new MyObject();
+ *
+ * return std::shared_ptr<MyObject>(obj, Deleter());
+ * }
+ * \endcode
+ *
+ * \context This function is \threadsafe.
+ */
+void Object::deleteLater()
+{
+ postMessage(std::make_unique<Message>(Message::DeferredDelete));
+}
+
+/**
+ * \brief Post a message to the object's thread
+ * \param[in] msg The message
+ *
+ * This method posts the message \a msg to the message queue of the object's
+ * thread, to be delivered to the object through the message() method in the
+ * context of its thread. Message ownership is passed to the thread, and the
+ * message will be deleted after being delivered.
+ *
+ * Messages are delivered through the thread's event loop. If the thread is not
+ * running its event loop the message will not be delivered until the event
+ * loop gets started.
+ *
+ * Due to their asynchronous nature, threads do not provide any guarantee that
+ * all posted messages are delivered before the thread is stopped. See
+ * \ref thread-stop for additional information.
+ *
+ * \context This function is \threadsafe.
+ */
+void Object::postMessage(std::unique_ptr<Message> msg)
+{
+ thread()->postMessage(std::move(msg), this);
+}
+
+/**
+ * \brief Message handler for the object
+ * \param[in] msg The message
+ *
+ * This virtual method receives messages for the object. It is called in the
+ * context of the object's thread, and can be overridden to process custom
+ * messages. The parent Object::message() method shall be called for any
+ * message not handled by the override method.
+ *
+ * The message \a msg is valid only for the duration of the call, no reference
+ * to it shall be kept after this method returns.
+ */
+void Object::message(Message *msg)
+{
+ switch (msg->type()) {
+ case Message::InvokeMessage: {
+ InvokeMessage *iMsg = static_cast<InvokeMessage *>(msg);
+ Semaphore *semaphore = iMsg->semaphore();
+ iMsg->invoke();
+
+ if (semaphore)
+ semaphore->release();
+
+ break;
+ }
+
+ case Message::DeferredDelete:
+ delete this;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * \fn R Object::invokeMethod()
+ * \brief Invoke a method asynchronously on an Object instance
+ * \param[in] func The object method to invoke
+ * \param[in] type Connection type for method invocation
+ * \param[in] args The method arguments
+ *
+ * This method invokes the member method \a func with arguments \a args, based
+ * on the connection \a type. Depending on the type, the method will be called
+ * synchronously in the same thread or asynchronously in the object's thread.
+ *
+ * Arguments \a args passed by value or reference are copied, while pointers
+ * are passed untouched. The caller shall ensure that any pointer argument
+ * remains valid until the method is invoked.
+ *
+ * Due to the asynchronous nature of threads, functions invoked asynchronously
+ * with the ConnectionTypeQueued type are not guaranteed to be called before
+ * the thread is stopped. See \ref thread-stop for additional information.
+ *
+ * \context This function is \threadsafe.
+ *
+ * \return For connection types ConnectionTypeDirect and
+ * ConnectionTypeBlocking, return the return value of the invoked method. For
+ * connection type ConnectionTypeQueued, return a default-constructed R value.
+ */
+
+/**
+ * \fn Object::thread()
+ * \brief Retrieve the thread the object is bound to
+ * \context This function is \threadsafe.
+ * \return The thread the object is bound to
+ */
+
+/**
+ * \brief Move the object and all its children to a different thread
+ * \param[in] thread The target thread
+ *
+ * This method moves the object and all its children from the current thread to
+ * the new \a thread.
+ *
+ * Before the object is moved, a Message::ThreadMoveMessage message is sent to
+ * it. The message() method can be reimplement in derived classes to be notified
+ * of the upcoming thread move and perform any required processing.
+ *
+ * Moving an object that has a parent is not allowed, and causes undefined
+ * behaviour.
+ *
+ * \context This function is thread-bound.
+ */
+void Object::moveToThread(Thread *thread)
+{
+ ASSERT(Thread::current() == thread_);
+
+ if (thread_ == thread)
+ return;
+
+ if (parent_) {
+ LOG(Object, Error)
+ << "Moving object to thread with a parent is not permitted";
+ return;
+ }
+
+ notifyThreadMove();
+
+ thread->moveObject(this);
+}
+
+void Object::notifyThreadMove()
+{
+ Message msg(Message::ThreadMoveMessage);
+ message(&msg);
+
+ for (auto child : children_)
+ child->notifyThreadMove();
+}
+
+/**
+ * \fn Object::parent()
+ * \brief Retrieve the object's parent
+ * \return The object's parent
+ */
+
+void Object::connect(SignalBase *signal)
+{
+ signals_.push_back(signal);
+}
+
+void Object::disconnect(SignalBase *signal)
+{
+ for (auto iter = signals_.begin(); iter != signals_.end(); ) {
+ if (*iter == signal)
+ iter = signals_.erase(iter);
+ else
+ iter++;
+ }
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/semaphore.cpp b/src/libcamera/base/semaphore.cpp
new file mode 100644
index 00000000..7aedc6a8
--- /dev/null
+++ b/src/libcamera/base/semaphore.cpp
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * semaphore.cpp - General-purpose counting semaphore
+ */
+
+#include <libcamera/base/semaphore.h>
+#include <libcamera/base/thread.h>
+
+/**
+ * \file base/semaphore.h
+ * \brief General-purpose counting semaphore
+ */
+
+namespace libcamera {
+
+/**
+ * \class Semaphore
+ * \brief General-purpose counting semaphore
+ *
+ * A semaphore is a locking primitive that protects resources. It is created
+ * with an initial number of resources (which may be 0), and offers two
+ * primitives to acquire and release resources. The acquire() method tries to
+ * acquire a number of resources, and blocks if not enough resources are
+ * available until they get released. The release() method releases a number of
+ * resources, waking up any consumer blocked on an acquire() call.
+ */
+
+/**
+ * \brief Construct a semaphore with \a n resources
+ * \param[in] n The resource count
+ */
+Semaphore::Semaphore(unsigned int n)
+ : available_(n)
+{
+}
+
+/**
+ * \brief Retrieve the number of available resources
+ * \return The number of available resources
+ */
+unsigned int Semaphore::available()
+{
+ MutexLocker locker(mutex_);
+ return available_;
+}
+
+/**
+ * \brief Acquire \a n resources
+ * \param[in] n The resource count
+ *
+ * This method attempts to acquire \a n resources. If \a n is higher than the
+ * number of available resources, the call will block until enough resources
+ * become available.
+ */
+void Semaphore::acquire(unsigned int n)
+{
+ MutexLocker locker(mutex_);
+ cv_.wait(locker, [&] { return available_ >= n; });
+ available_ -= n;
+}
+
+/**
+ * \brief Try to acquire \a n resources without blocking
+ * \param[in] n The resource count
+ *
+ * This method attempts to acquire \a n resources. If \a n is higher than the
+ * number of available resources, it returns false immediately without
+ * acquiring any resource. Otherwise it acquires the resources and returns
+ * true.
+ *
+ * \return True if the resources have been acquired, false otherwise
+ */
+bool Semaphore::tryAcquire(unsigned int n)
+{
+ MutexLocker locker(mutex_);
+ if (available_ < n)
+ return false;
+
+ available_ -= n;
+ return true;
+}
+
+/**
+ * \brief Release \a n resources
+ * \param[in] n The resource count
+ *
+ * This method releases \a n resources, increasing the available resource count
+ * by \a n. If the number of available resources becomes large enough for any
+ * consumer blocked on an acquire() call, those consumers get woken up.
+ */
+void Semaphore::release(unsigned int n)
+{
+ {
+ MutexLocker locker(mutex_);
+ available_ += n;
+ }
+
+ cv_.notify_all();
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/signal.cpp b/src/libcamera/base/signal.cpp
new file mode 100644
index 00000000..298b2d4b
--- /dev/null
+++ b/src/libcamera/base/signal.cpp
@@ -0,0 +1,179 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * signal.cpp - Signal & slot implementation
+ */
+
+#include <libcamera/base/signal.h>
+
+#include <libcamera/base/thread.h>
+
+/**
+ * \file base/signal.h
+ * \brief Signal & slot implementation
+ */
+
+namespace libcamera {
+
+namespace {
+
+/*
+ * Mutex to protect the SignalBase::slots_ and Object::signals_ lists. If lock
+ * contention needs to be decreased, this could be replaced with locks in
+ * Object and SignalBase, or with a mutex pool.
+ */
+Mutex signalsLock;
+
+} /* namespace */
+
+void SignalBase::connect(BoundMethodBase *slot)
+{
+ MutexLocker locker(signalsLock);
+
+ Object *object = slot->object();
+ if (object)
+ object->connect(this);
+ slots_.push_back(slot);
+}
+
+void SignalBase::disconnect(Object *object)
+{
+ disconnect([object](SlotList::iterator &iter) {
+ return (*iter)->match(object);
+ });
+}
+
+void SignalBase::disconnect(std::function<bool(SlotList::iterator &)> match)
+{
+ MutexLocker locker(signalsLock);
+
+ for (auto iter = slots_.begin(); iter != slots_.end(); ) {
+ if (match(iter)) {
+ Object *object = (*iter)->object();
+ if (object)
+ object->disconnect(this);
+
+ delete *iter;
+ iter = slots_.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+SignalBase::SlotList SignalBase::slots()
+{
+ MutexLocker locker(signalsLock);
+ return slots_;
+}
+
+/**
+ * \class Signal
+ * \brief Generic signal and slot communication mechanism
+ *
+ * Signals and slots are a language construct aimed at communication between
+ * objects through the observer pattern without the need for boilerplate code.
+ * See http://doc.qt.io/qt-5/signalsandslots.html for more information.
+ *
+ * Signals model events that can be observed from objects unrelated to the event
+ * source. Slots are functions that are called in response to a signal. Signals
+ * can be connected to and disconnected from slots dynamically at runtime. When
+ * a signal is emitted, all connected slots are called sequentially in the order
+ * they have been connected.
+ *
+ * Signals are defined with zero, one or more typed parameters. They are emitted
+ * with a value for each of the parameters, and those values are passed to the
+ * connected slots.
+ *
+ * Slots are normal static or class member functions. In order to be connected
+ * to a signal, their signature must match the signal type (taking the same
+ * arguments as the signal and returning void).
+ *
+ * Connecting a signal to a slot results in the slot being called with the
+ * arguments passed to the emit() function when the signal is emitted. Multiple
+ * slots can be connected to the same signal, and multiple signals can connected
+ * to the same slot. Duplicate connections between a signal and a slot are
+ * allowed and result in the slot being called multiple times for the same
+ * signal emission.
+ *
+ * When a slot belongs to an instance of the Object class, the slot is called
+ * in the context of the thread that the object is bound to. If the signal is
+ * emitted from the same thread, the slot will be called synchronously, before
+ * Signal::emit() returns. If the signal is emitted from a different thread,
+ * the slot will be called asynchronously from the object's thread's event
+ * loop, after the Signal::emit() method returns, with a copy of the signal's
+ * arguments. The emitter shall thus ensure that any pointer or reference
+ * passed through the signal will remain valid after the signal is emitted.
+ */
+
+/**
+ * \fn Signal::connect(T *object, R (T::*func)(Args...))
+ * \brief Connect the signal to a member function slot
+ * \param[in] object The slot object pointer
+ * \param[in] func The slot member function
+ *
+ * If the typename T inherits from Object, the signal will be automatically
+ * disconnected from the \a func slot of \a object when \a object is destroyed.
+ * Otherwise the caller shall disconnect signals manually before destroying \a
+ * object.
+ *
+ * \context This function is \threadsafe.
+ */
+
+/**
+ * \fn Signal::connect(R (*func)(Args...))
+ * \brief Connect the signal to a static function slot
+ * \param[in] func The slot static function
+ *
+ * \context This function is \threadsafe.
+ */
+
+/**
+ * \fn Signal::disconnect()
+ * \brief Disconnect the signal from all slots
+ *
+ * \context This function is \threadsafe.
+ */
+
+/**
+ * \fn Signal::disconnect(T *object)
+ * \brief Disconnect the signal from all slots of the \a object
+ * \param[in] object The object pointer whose slots to disconnect
+ *
+ * \context This function is \threadsafe.
+ */
+
+/**
+ * \fn Signal::disconnect(T *object, R (T::*func)(Args...))
+ * \brief Disconnect the signal from the \a object slot member function \a func
+ * \param[in] object The object pointer whose slots to disconnect
+ * \param[in] func The slot member function to disconnect
+ *
+ * \context This function is \threadsafe.
+ */
+
+/**
+ * \fn Signal::disconnect(R (*func)(Args...))
+ * \brief Disconnect the signal from the slot static function \a func
+ * \param[in] func The slot static function to disconnect
+ *
+ * \context This function is \threadsafe.
+ */
+
+/**
+ * \fn Signal::emit(Args... args)
+ * \brief Emit the signal and call all connected slots
+ * \param args The arguments passed to the connected slots
+ *
+ * Emitting a signal calls all connected slots synchronously and sequentially in
+ * the order the slots have been connected. The arguments passed to the emit()
+ * function are passed to the slot functions unchanged. If a slot modifies one
+ * of the arguments (when passed by pointer or reference), the modification is
+ * thus visible to all subsequently called slots.
+ *
+ * This function is not \threadsafe, but thread-safety is guaranteed against
+ * concurrent connect() and disconnect() calls.
+ */
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
new file mode 100644
index 00000000..c7c2d6b2
--- /dev/null
+++ b/src/libcamera/base/thread.cpp
@@ -0,0 +1,680 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * thread.cpp - Thread support
+ */
+
+#include <libcamera/base/thread.h>
+
+#include <atomic>
+#include <condition_variable>
+#include <list>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/event_dispatcher_poll.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/message.h>
+
+/**
+ * \page thread Thread Support
+ *
+ * libcamera supports multi-threaded applications through a threading model that
+ * sets precise rules to guarantee thread-safe usage of the API. Additionally,
+ * libcamera makes internal use of threads, and offers APIs that simplify
+ * interactions with application threads. Careful compliance with the threading
+ * model will ensure avoidance of race conditions.
+ *
+ * Every thread created by libcamera is associated with an instance of the
+ * Thread class. Those threads run an internal event loop by default to
+ * dispatch events to objects. Additionally, the main thread of the application
+ * (defined as the thread that calls CameraManager::start()) is also associated
+ * with a Thread instance, but has no event loop accessible to libcamera. Other
+ * application threads are not visible to libcamera.
+ *
+ * \section thread-objects Threads and Objects
+ *
+ * Instances of the Object class and all its derived classes are thread-aware
+ * and are bound to the thread they are created in. They are said to *live* in
+ * a thread, and they interact with the event loop of their thread for the
+ * purpose of message passing and signal delivery. Messages posted to the
+ * object with Object::postMessage() will be delivered from the event loop of
+ * the thread that the object lives in. Signals delivered to the object, unless
+ * explicitly connected with ConnectionTypeDirect, will also be delivered from
+ * the object thread's event loop.
+ *
+ * All Object instances created internally by libcamera are bound to internal
+ * threads. As objects interact with thread event loops for proper operation,
+ * creating an Object instance in a thread that has no internal event loop (such
+ * as the main application thread, or libcamera threads that have a custom main
+ * loop), prevents some features of the Object class from being used. See
+ * Thread::exec() for more details.
+ *
+ * \section thread-signals Threads and Signals
+ *
+ * When sent to a receiver that does not inherit from the Object class, signals
+ * are delivered synchronously in the thread of the sender. When the receiver
+ * inherits from the Object class, delivery is by default asynchronous if the
+ * sender and receiver live in different threads. In that case, the signal is
+ * posted to the receiver's message queue and will be delivered from the
+ * receiver's event loop, running in the receiver's thread. This mechanism can
+ * be overridden by selecting a different connection type when calling
+ * Signal::connect().
+ *
+ * \section thread-reentrancy Reentrancy and Thread-Safety
+ *
+ * Through the documentation, several terms are used to define how classes and
+ * their member functions can be used from multiple threads.
+ *
+ * - A **reentrant** function may be called simultaneously from multiple
+ * threads if and only if each invocation uses a different instance of the
+ * class. This is the default for all member functions not explictly marked
+ * otherwise.
+ *
+ * - \anchor thread-safe A **thread-safe** function may be called
+ * simultaneously from multiple threads on the same instance of a class. A
+ * thread-safe function is thus reentrant. Thread-safe functions may also be
+ * called simultaneously with any other reentrant function of the same class
+ * on the same instance.
+ *
+ * - \anchor thread-bound A **thread-bound** function may be called only from
+ * the thread that the class instances lives in (see section \ref
+ * thread-objects). For instances of classes that do not derive from the
+ * Object class, this is the thread in which the instance was created. A
+ * thread-bound function is not thread-safe, and may or may not be reentrant.
+ *
+ * Neither reentrancy nor thread-safety, in this context, mean that a function
+ * may be called simultaneously from the same thread, for instance from a
+ * callback invoked by the function. This may deadlock and isn't allowed unless
+ * separately documented.
+ *
+ * A class is defined as reentrant, thread-safe or thread-bound if all its
+ * member functions are reentrant, thread-safe or thread-bound respectively.
+ * Some member functions may additionally be documented as having additional
+ * thread-related attributes.
+ *
+ * Most classes are reentrant but not thread-safe, as making them fully
+ * thread-safe would incur locking costs considered prohibitive for the
+ * expected use cases.
+ */
+
+/**
+ * \file base/thread.h
+ * \brief Thread support
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Thread)
+
+class ThreadMain;
+
+/**
+ * \brief A queue of posted messages
+ */
+class MessageQueue
+{
+public:
+ /**
+ * \brief List of queued Message instances
+ */
+ std::list<std::unique_ptr<Message>> list_;
+ /**
+ * \brief Protects the \ref list_
+ */
+ Mutex mutex_;
+};
+
+/**
+ * \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_;
+ pid_t tid_;
+
+ Mutex mutex_;
+
+ std::atomic<EventDispatcher *> dispatcher_;
+
+ std::condition_variable cv_;
+ std::atomic<bool> exit_;
+ int exitCode_;
+
+ MessageQueue messages_;
+};
+
+/**
+ * \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_;
+ data->tid_ = syscall(SYS_gettid);
+ 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. The event loop dispatches events (messages, notifiers and timers)
+ * sent to the objects living in the thread. This behaviour can be modified by
+ * overriding the run() function.
+ *
+ * \section thread-stop Stopping Threads
+ *
+ * Threads can't be forcibly stopped. Instead, a thread user first requests the
+ * thread to exit and then waits for the thread's main function to react to the
+ * request and return, at which points the thread will stop.
+ *
+ * For threads running exec(), the exit() function is used to request the thread
+ * to exit. For threads subclassing the Thread class and implementing a custom
+ * run() function, a subclass-specific mechanism shall be provided. In either
+ * case, the wait() function shall be called to wait for the thread to stop.
+ *
+ * Due to their asynchronous nature, threads are subject to race conditions when
+ * they stop. This is of particular importance for messages posted to the thread
+ * with postMessage() (and the other mechanisms that rely on it, such as
+ * Object::invokeMethod() or asynchronous signal delivery). To understand the
+ * issues, three contexts need to be considered:
+ *
+ * - The worker is the Thread performing work and being instructed to stop.
+ * - The controller is the context which instructs the worker thread to stop.
+ * - The other contexts are any threads other than the worker and controller
+ * that interact with the worker thread.
+ *
+ * Messages posted to the worker thread from the controller context before
+ * calling exit() are queued to the thread's message queue, and the Thread class
+ * offers no guarantee that those messages will be processed before the thread
+ * stops. This allows threads to stop fast.
+ *
+ * A thread that requires delivery of messages posted from the controller
+ * context before exit() should reimplement the run() function and call
+ * dispatchMessages() after exec().
+ *
+ * Messages posted to the worker thread from the other contexts are asynchronous
+ * with respect to the exit() call from the controller context. There is no
+ * guarantee as to whether those messages will be processed or not before the
+ * thread stops.
+ *
+ * Messages that are not processed will stay in the queue, in the exact same way
+ * as messages posted after the thread has stopped. They will be processed when
+ * the thread is restarted. If the thread is never restarted, they will be
+ * deleted without being processed when the Thread instance is destroyed.
+ */
+
+/**
+ * \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);
+
+ data_->tid_ = syscall(SYS_gettid);
+ currentThreadData = data_;
+
+ run();
+}
+
+/**
+ * \brief Enter the event loop
+ *
+ * This method enters 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 overridden to perform custom
+ * work, either custom initialization and cleanup before and after calling the
+ * Thread::exec() function, or a custom thread loop altogether. When this
+ * method returns the thread execution is stopped, and the \ref finished signal
+ * is emitted.
+ *
+ * Note that if this function is overridden and doesn't call Thread::exec(), no
+ * events will be dispatched to the objects living in the thread. These objects
+ * will not be able to use the EventNotifier, Timer or Message facilities. This
+ * includes functions that rely on message dispatching, such as
+ * Object::deleteLater().
+ *
+ * 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);
+ data_->cv_.notify_all();
+}
+
+/**
+ * \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.
+ *
+ * \context This function is \threadsafe.
+ */
+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
+ * \param[in] duration Maximum wait duration
+ *
+ * This function waits until the thread finishes or the \a duration has
+ * elapsed, whichever happens first. If \a duration is equal to
+ * utils::duration::max(), the wait never times out. If the thread is not
+ * running the function returns immediately.
+ *
+ * \context This function is \threadsafe.
+ *
+ * \return True if the thread has finished, or false if the wait timed out
+ */
+bool Thread::wait(utils::duration duration)
+{
+ bool hasFinished = true;
+
+ {
+ MutexLocker locker(data_->mutex_);
+
+ if (duration == utils::duration::max())
+ data_->cv_.wait(locker, [&]() { return !data_->running_; });
+ else
+ hasFinished = data_->cv_.wait_for(locker, duration,
+ [&]() { return !data_->running_; });
+ }
+
+ if (thread_.joinable())
+ thread_.join();
+
+ return hasFinished;
+}
+
+/**
+ * \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.
+ *
+ * \context This function is \threadsafe.
+ *
+ * \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
+ * \context This function is \threadsafe.
+ * \return The Thread instance for the current thread
+ */
+Thread *Thread::current()
+{
+ ThreadData *data = ThreadData::current();
+ return data->thread_;
+}
+
+/**
+ * \brief Retrieve the ID of the current thread
+ *
+ * The thread ID corresponds to the Linux thread ID (TID) as returned by the
+ * gettid system call.
+ *
+ * \context This function is \threadsafe.
+ *
+ * \return The ID of the current thread
+ */
+pid_t Thread::currentId()
+{
+ ThreadData *data = ThreadData::current();
+ return data->tid_;
+}
+
+/**
+ * \brief Retrieve the event dispatcher
+ *
+ * This function retrieves the internal event dispatcher for the thread. The
+ * returned event dispatcher is valid until the thread is destroyed.
+ *
+ * \context This function is \threadsafe.
+ *
+ * \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);
+}
+
+/**
+ * \brief Post a message to the thread for the \a receiver
+ * \param[in] msg The message
+ * \param[in] receiver The receiver
+ *
+ * This method stores the message \a msg in the message queue of the thread for
+ * the \a receiver and wake up the thread's event loop. Message ownership is
+ * passed to the thread, and the message will be deleted after being delivered.
+ *
+ * Messages are delivered through the thread's event loop. If the thread is not
+ * running its event loop the message will not be delivered until the event
+ * loop gets started.
+ *
+ * When the thread is stopped, posted messages may not have all been processed.
+ * See \ref thread-stop for additional information.
+ *
+ * If the \a receiver is not bound to this thread the behaviour is undefined.
+ *
+ * \sa exec()
+ */
+void Thread::postMessage(std::unique_ptr<Message> msg, Object *receiver)
+{
+ msg->receiver_ = receiver;
+
+ ASSERT(data_ == receiver->thread()->data_);
+
+ MutexLocker locker(data_->messages_.mutex_);
+ data_->messages_.list_.push_back(std::move(msg));
+ receiver->pendingMessages_++;
+ locker.unlock();
+
+ EventDispatcher *dispatcher =
+ data_->dispatcher_.load(std::memory_order_acquire);
+ if (dispatcher)
+ dispatcher->interrupt();
+}
+
+/**
+ * \brief Remove all posted messages for the \a receiver
+ * \param[in] receiver The receiver
+ *
+ * If the \a receiver is not bound to this thread the behaviour is undefined.
+ */
+void Thread::removeMessages(Object *receiver)
+{
+ ASSERT(data_ == receiver->thread()->data_);
+
+ MutexLocker locker(data_->messages_.mutex_);
+ if (!receiver->pendingMessages_)
+ return;
+
+ std::vector<std::unique_ptr<Message>> toDelete;
+ for (std::unique_ptr<Message> &msg : data_->messages_.list_) {
+ if (!msg)
+ continue;
+ if (msg->receiver_ != receiver)
+ continue;
+
+ /*
+ * Move the message to the pending deletion list to delete it
+ * after releasing the lock. The messages list element will
+ * contain a null pointer, and will be removed when dispatching
+ * messages.
+ */
+ toDelete.push_back(std::move(msg));
+ receiver->pendingMessages_--;
+ }
+
+ ASSERT(!receiver->pendingMessages_);
+ locker.unlock();
+
+ toDelete.clear();
+}
+
+/**
+ * \brief Dispatch posted messages for this thread
+ * \param[in] type The message type
+ *
+ * This function immediately dispatches all the messages previously posted for
+ * this thread with postMessage() that match the message \a type. If the \a type
+ * is Message::Type::None, all messages are dispatched.
+ *
+ * Messages shall only be dispatched from the current thread, typically within
+ * the thread from the run() function. Calling this function outside of the
+ * thread results in undefined behaviour.
+ */
+void Thread::dispatchMessages(Message::Type type)
+{
+ ASSERT(data_ == ThreadData::current());
+
+ MutexLocker locker(data_->messages_.mutex_);
+
+ std::list<std::unique_ptr<Message>> &messages = data_->messages_.list_;
+
+ for (auto iter = messages.begin(); iter != messages.end(); ) {
+ std::unique_ptr<Message> &msg = *iter;
+
+ if (!msg) {
+ iter = data_->messages_.list_.erase(iter);
+ continue;
+ }
+
+ if (type != Message::Type::None && msg->type() != type) {
+ ++iter;
+ continue;
+ }
+
+ std::unique_ptr<Message> message = std::move(msg);
+ iter = data_->messages_.list_.erase(iter);
+
+ Object *receiver = message->receiver_;
+ ASSERT(data_ == receiver->thread()->data_);
+ receiver->pendingMessages_--;
+
+ locker.unlock();
+ receiver->message(message.get());
+ message.reset();
+ locker.lock();
+ }
+}
+
+/**
+ * \brief Move an \a object and all its children to the thread
+ * \param[in] object The object
+ */
+void Thread::moveObject(Object *object)
+{
+ ThreadData *currentData = object->thread_->data_;
+ ThreadData *targetData = data_;
+
+ MutexLocker lockerFrom(currentData->messages_.mutex_, std::defer_lock);
+ MutexLocker lockerTo(targetData->messages_.mutex_, std::defer_lock);
+ std::lock(lockerFrom, lockerTo);
+
+ moveObject(object, currentData, targetData);
+}
+
+void Thread::moveObject(Object *object, ThreadData *currentData,
+ ThreadData *targetData)
+{
+ /* Move pending messages to the message queue of the new thread. */
+ if (object->pendingMessages_) {
+ unsigned int movedMessages = 0;
+
+ for (std::unique_ptr<Message> &msg : currentData->messages_.list_) {
+ if (!msg)
+ continue;
+ if (msg->receiver_ != object)
+ continue;
+
+ targetData->messages_.list_.push_back(std::move(msg));
+ movedMessages++;
+ }
+
+ if (movedMessages) {
+ EventDispatcher *dispatcher =
+ targetData->dispatcher_.load(std::memory_order_acquire);
+ if (dispatcher)
+ dispatcher->interrupt();
+ }
+ }
+
+ object->thread_ = this;
+
+ /* Move all children. */
+ for (auto child : object->children_)
+ moveObject(child, currentData, targetData);
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/timer.cpp b/src/libcamera/base/timer.cpp
new file mode 100644
index 00000000..9c54352d
--- /dev/null
+++ b/src/libcamera/base/timer.cpp
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * timer.cpp - Generic timer
+ */
+
+#include <libcamera/base/timer.h>
+
+#include <chrono>
+
+#include <libcamera/base/event_dispatcher.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/message.h>
+#include <libcamera/base/thread.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/camera_manager.h>
+
+/**
+ * \file base/timer.h
+ * \brief Generic timer
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Timer)
+
+/**
+ * \class Timer
+ * \brief Single-shot timer interface
+ *
+ * The Timer class models a single-shot timer that is started with start() and
+ * emits the \ref timeout signal when it times out.
+ *
+ * Once started the timer will run until it times out. It can be stopped with
+ * stop(), and once it times out or is stopped, can be started again with
+ * start().
+ *
+ * The timer deadline is specified as either a duration in milliseconds or an
+ * absolute time point. If the deadline is set to the current time or to the
+ * past, the timer will time out immediately when execution returns to the
+ * event loop of the timer's thread.
+ *
+ * Timers run in the thread they belong to, and thus emit the \a ref timeout
+ * signal from that thread. To avoid race conditions they must not be started
+ * or stopped from a different thread, attempts to do so will be rejected and
+ * logged, and may cause undefined behaviour.
+ */
+
+/**
+ * \brief Construct a timer
+ * \param[in] parent The parent Object
+ */
+Timer::Timer(Object *parent)
+ : Object(parent), running_(false)
+{
+}
+
+Timer::~Timer()
+{
+ stop();
+}
+
+/**
+ * \fn Timer::start(unsigned int msec)
+ * \brief Start or restart the timer with a timeout of \a msec
+ * \param[in] msec The timer duration in milliseconds
+ *
+ * If the timer is already running it will be stopped and restarted.
+ *
+ * \context This function is \threadbound.
+ */
+
+/**
+ * \brief Start or restart the timer with a timeout of \a duration
+ * \param[in] duration The timer duration in milliseconds
+ *
+ * If the timer is already running it will be stopped and restarted.
+ *
+ * \context This function is \threadbound.
+ */
+void Timer::start(std::chrono::milliseconds duration)
+{
+ start(utils::clock::now() + duration);
+}
+
+/**
+ * \brief Start or restart the timer with a \a deadline
+ * \param[in] deadline The timer deadline
+ *
+ * If the timer is already running it will be stopped and restarted.
+ *
+ * \context This function is \threadbound.
+ */
+void Timer::start(std::chrono::steady_clock::time_point deadline)
+{
+ if (Thread::current() != thread()) {
+ LOG(Timer, Error) << "Timer can't be started from another thread";
+ return;
+ }
+
+ deadline_ = deadline;
+
+ LOG(Timer, Debug)
+ << "Starting timer " << this << ": deadline "
+ << utils::time_point_to_string(deadline_);
+
+ if (isRunning())
+ unregisterTimer();
+
+ registerTimer();
+}
+
+/**
+ * \brief Stop the timer
+ *
+ * After this function returns the timer is guaranteed not to emit the
+ * \ref timeout signal.
+ *
+ * If the timer is not running this function performs no operation.
+ *
+ * \context This function is \threadbound.
+ */
+void Timer::stop()
+{
+ if (!isRunning())
+ return;
+
+ if (Thread::current() != thread()) {
+ LOG(Timer, Error) << "Timer can't be stopped from another thread";
+ return;
+ }
+
+ unregisterTimer();
+}
+
+void Timer::registerTimer()
+{
+ thread()->eventDispatcher()->registerTimer(this);
+ running_ = true;
+}
+
+void Timer::unregisterTimer()
+{
+ running_ = false;
+ thread()->eventDispatcher()->unregisterTimer(this);
+}
+
+/**
+ * \brief Check if the timer is running
+ * \return True if the timer is running, false otherwise
+ */
+bool Timer::isRunning() const
+{
+ return running_;
+}
+
+/**
+ * \fn Timer::deadline()
+ * \brief Retrieve the timer deadline
+ * \return The timer deadline
+ */
+
+/**
+ * \var Timer::timeout
+ * \brief Signal emitted when the timer times out
+ *
+ * The timer pointer is passed as a parameter.
+ */
+
+void Timer::message(Message *msg)
+{
+ if (msg->type() == Message::ThreadMoveMessage) {
+ if (isRunning()) {
+ unregisterTimer();
+ invokeMethod(&Timer::registerTimer,
+ ConnectionTypeQueued);
+ }
+ }
+
+ Object::message(msg);
+}
+
+} /* namespace libcamera */