Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README-RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,21 @@ path, который запрашивает stop.
долгий блокирующий `process()` может отложить обработку SIGINT/SIGTERM до
возврата управления в runner loop.

Для POSIX-приложений с длинными throttle delays добавьте opt-in self-pipe
component до компонентов, которые могут блокировать loop:

```cpp
consolix::add<consolix::PosixSignalWakeComponent>();
consolix::add<MyWorkerComponent>();
consolix::add<consolix::LoopThrottleComponent>(
std::chrono::seconds(10));
```

POSIX handler по-прежнему только сохраняет сигнал и пишет один байт в pipe.
Watcher thread будит `LoopWakeService` уже из обычного C++ кода, поэтому
SIGINT/SIGTERM может прервать ожидание `LoopThrottleComponent` без mutex/CV
работы внутри signal handler. На Windows этот component является no-op.

## Documentation

- developer guidelines: `docs/header-implementation-guidelines.md`
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,21 @@ pending signal flag. They do not wake C++ condition variables directly, so a
long blocking `process()` call can delay SIGINT/SIGTERM handling until control
returns to the runner loop.

For POSIX applications that use long throttle delays, add the opt-in self-pipe
wake component before components that may block the loop:

```cpp
consolix::add<consolix::PosixSignalWakeComponent>();
consolix::add<MyWorkerComponent>();
consolix::add<consolix::LoopThrottleComponent>(
std::chrono::seconds(10));
```

The POSIX handler still only records the signal and writes one byte to a pipe.
A watcher thread wakes `LoopWakeService` from normal C++ code, so
SIGINT/SIGTERM can interrupt `LoopThrottleComponent` waits without doing
mutex/CV work inside the signal handler. On Windows this component is a no-op.

## Diagnostic Streams

Consolix provides two multi-target log macros that route messages through
Expand Down
17 changes: 17 additions & 0 deletions docs/mainpage.dox
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ It also targets the recurring runtime concerns of console tools: startup and shu
- `ServiceLocator` for global resource management
- `AppComponentManager`, `ConsoleApplication`, and `ConsoleApplicationRunner` for application lifecycle
- `LoopWakeService` for waking polling-loop wait components
- `PosixSignalWakeService` for opt-in POSIX self-pipe wake-ups
- service and application helpers for common setup flows

- **Components**:
Expand All @@ -48,6 +49,7 @@ It also targets the recurring runtime concerns of console tools: startup and shu
- `LogoComponent`: displays a custom ASCII logo in the console
- `LoopComponent`: customizable application loops
- `LoopThrottleComponent`: optional wakeable wait step for polling loops
- `PosixSignalWakeComponent`: lifecycle wrapper for POSIX signal wake-ups

- **Utilities**:
- `path_utils.hpp`: path and executable-location helpers
Expand Down Expand Up @@ -174,6 +176,21 @@ pending signal flag. They do not wake C++ condition variables directly, so a
long blocking `process()` call can delay SIGINT/SIGTERM handling until control
returns to the runner loop.

For POSIX applications that use long throttle delays, add the opt-in self-pipe
wake component before components that may block the loop:

```cpp
consolix::add<consolix::PosixSignalWakeComponent>();
consolix::add<MyWorkerComponent>();
consolix::add<consolix::LoopThrottleComponent>(
std::chrono::seconds(10));
```

The POSIX handler still only records the signal and writes one byte to a pipe.
A watcher thread wakes `LoopWakeService` from normal C++ code, so
SIGINT/SIGTERM can interrupt `LoopThrottleComponent` waits without doing
mutex/CV work inside the signal handler. On Windows this component is a no-op.

## Documentation Structure

### Main Sections
Expand Down
4 changes: 4 additions & 0 deletions include/consolix/components.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
/// - **BaseLoopComponent**: A base class for loop-based components.
/// - **LoopComponent**: A component with customizable execution loops.
/// - **LoopThrottleComponent**: A wakeable wait step for throttling polling loops.
/// - **PosixSignalWakeComponent**: Optional POSIX signal wake bridge for throttled loops.
/// - **EventHubComponent**: Optional bridge to event-hub-cpp EventBus and TaskManager.
/// - **ModuleHubComponent**: Optional bridge to event-hub-cpp ModuleHub.
///
Expand Down Expand Up @@ -59,6 +60,7 @@
/// - `components/BaseLoopComponent.hpp`
/// - `components/LoopComponent.hpp`
/// - `components/LoopThrottleComponent.hpp`
/// - `components/PosixSignalWakeComponent.hpp`
/// - `components/EventHubComponent.hpp` when `CONSOLIX_USE_EVENT_HUB=1`
/// - `components/ModuleHubComponent.hpp` when `CONSOLIX_USE_EVENT_HUB=1`
///
Expand Down Expand Up @@ -100,6 +102,7 @@
#include "core/ServiceLocator.hpp" ///< Service locator for managing shared resources.
#include "core/service_utils.hpp" ///< Utility functions for working with services.
#include "core/LoopWakeService.hpp" ///< Shared wake channel for polling-loop waits.
#include "core/PosixSignalWakeService.hpp" ///< Optional self-pipe bridge for POSIX signal wake-ups.

// Core components of the Consolix framework
#include "components/TitleComponent.hpp" ///< Component for managing the console window title across platforms.
Expand All @@ -108,6 +111,7 @@
#include "components/BaseLoopComponent.hpp" ///< Base class for implementing loop-based components.
#include "components/LoopComponent.hpp" ///< Component with configurable initialization, loop, and shutdown callbacks.
#include "components/LoopThrottleComponent.hpp" ///< Wakeable throttle component for polling loops.
#include "components/PosixSignalWakeComponent.hpp" ///< POSIX signal wake component for throttled loops.

#if CONSOLIX_USE_EVENT_HUB == 1
#include "components/EventHubComponent.hpp" ///< Optional event-hub-cpp EventBus/TaskManager integration component.
Expand Down
81 changes: 81 additions & 0 deletions include/consolix/components/PosixSignalWakeComponent.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once
#ifndef _CONSOLIX_POSIX_SIGNAL_WAKE_COMPONENT_HPP_INCLUDED
#define _CONSOLIX_POSIX_SIGNAL_WAKE_COMPONENT_HPP_INCLUDED

/// \file PosixSignalWakeComponent.hpp
/// \brief Opt-in component that wakes polling waits for POSIX termination signals.
/// \ingroup Components

#include <atomic>
#include <memory>

namespace consolix {

/// \class PosixSignalWakeComponent
/// \brief Starts `PosixSignalWakeService` for the application lifecycle.
///
/// Register this component when POSIX SIGINT/SIGTERM should wake
/// `LoopThrottleComponent` waits immediately. The component is a no-op on
/// Windows, where console-control handling already uses the runner stop path.
class PosixSignalWakeComponent final : public IAppComponent, public IShutdownable {
public:
/// \brief Stops the wake service if it is still active.
~PosixSignalWakeComponent() override {
stop_service();
}

protected:
/// \brief Starts or reuses the shared POSIX signal wake service.
/// \return Always `true` once setup succeeds or when unsupported.
bool initialize() override {
if (m_initialized.load()) {
return true;
}

auto service = ServiceLocator::get_instance().find_service<PosixSignalWakeService>();
if (!service) {
ServiceLocator::get_instance().register_service<PosixSignalWakeService>();
service = ServiceLocator::get_instance().find_service<PosixSignalWakeService>();
}

if (service) {
service->start();
m_service = service;
}

m_initialized.store(true);
return true;
}

/// \brief Reports whether the service has been initialized.
/// \return `true` after successful initialization.
bool is_initialized() const override {
return m_initialized.load();
}

/// \brief Does no per-loop work.
void process() override {
}

/// \brief Stops the POSIX wake service during application shutdown.
/// \param signal Exit code or signal that initiated shutdown.
void shutdown(int signal) override {
(void)signal;
stop_service();
}

private:
void stop_service() {
if (m_service) {
m_service->stop();
}
m_initialized.store(false);
}

std::atomic<bool> m_initialized{false};
std::shared_ptr<PosixSignalWakeService> m_service;
}; // PosixSignalWakeComponent

} // namespace consolix

#endif // _CONSOLIX_POSIX_SIGNAL_WAKE_COMPONENT_HPP_INCLUDED
3 changes: 3 additions & 0 deletions include/consolix/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
/// - **AppComponentManager**: A manager for handling application components.
/// - **ConsoleApplication**: A singleton for managing the console application. It includes `AppComponentManager`.
/// - **LoopWakeService**: A shared wake channel for polling-loop wait components.
/// - **PosixSignalWakeService**: An opt-in self-pipe bridge for POSIX signal wake-ups.
/// - **Utilities**: Functions to simplify working with applications and services.
///
/// ### Key Features:
Expand All @@ -33,6 +34,7 @@
/// - `core/service_utils.hpp`
/// - `core/AppComponentManager.hpp`
/// - `core/LoopWakeService.hpp`
/// - `core/PosixSignalWakeService.hpp`
/// - `core/ConsoleApplicationRunner.hpp`
/// - `core/ConsoleApplication.hpp`
/// - `core/application_utils.hpp`
Expand Down Expand Up @@ -78,6 +80,7 @@
#include "core/service_utils.hpp" ///< Helper functions for working with services.
#include "core/AppComponentManager.hpp" ///< Manager for application components and their lifecycle.
#include "core/LoopWakeService.hpp" ///< Shared wake channel for polling-loop waits.
#include "core/PosixSignalWakeService.hpp" ///< Optional self-pipe bridge for POSIX signal wake-ups.
#include "core/ConsoleApplicationRunner.hpp" ///< Runner returning exit codes without std::exit.
#include "core/ConsoleApplication.hpp" ///< Singleton managing the console application's lifecycle.
#include "core/application_utils.hpp" ///< Helper functions for application setup and execution.
Expand Down
2 changes: 2 additions & 0 deletions include/consolix/core/ConsoleApplicationRunner.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "ServiceLocator.hpp"
#include "AppComponentManager.hpp"
#include "LoopWakeService.hpp"
#include "PosixSignalWakeService.hpp"

#if !defined(_WIN32) && !defined(_WIN64)
#include <signal.h>
Expand Down Expand Up @@ -372,6 +373,7 @@ namespace consolix {
static void signal_handler(int exit_code) {
pending_signal_code() = static_cast<std::sig_atomic_t>(exit_code);
signal_stop_requested() = 1;
PosixSignalWakeService::notify_signal_handler();
}

static void reset_signal_state() {
Expand Down
Loading
Loading