diff options
Diffstat (limited to 'src/libcamera/base')
-rw-r--r-- | src/libcamera/base/bound_method.cpp | 109 | ||||
-rw-r--r-- | src/libcamera/base/event_dispatcher.cpp | 116 | ||||
-rw-r--r-- | src/libcamera/base/event_dispatcher_poll.cpp | 308 | ||||
-rw-r--r-- | src/libcamera/base/log.cpp | 998 | ||||
-rw-r--r-- | src/libcamera/base/meson.build | 11 | ||||
-rw-r--r-- | src/libcamera/base/message.cpp | 166 | ||||
-rw-r--r-- | src/libcamera/base/object.cpp | 300 | ||||
-rw-r--r-- | src/libcamera/base/semaphore.cpp | 103 | ||||
-rw-r--r-- | src/libcamera/base/signal.cpp | 179 | ||||
-rw-r--r-- | src/libcamera/base/thread.cpp | 680 | ||||
-rw-r--r-- | src/libcamera/base/timer.cpp | 185 |
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 */ |