update: updating files with detailed comments

This commit is contained in:
Patrick Stewart 2024-09-06 11:52:15 -07:00
parent 7fa2bb0f7e
commit e7f8083b25
8 changed files with 957 additions and 59 deletions

View file

@ -7,6 +7,14 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
/// This library exports various components of the application framework.
///
/// It includes:
/// - Application and ApplicationServer classes for managing the application lifecycle
/// - Channel for handling request/response cycles
/// - IsolateApplicationServer and IsolateSupervisor for managing isolates
/// - Options for configuring the application
/// - Starter for initializing and running the application
library; library;
export 'src/application.dart'; export 'src/application.dart';

View file

@ -1,3 +1,12 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
@ -11,56 +20,138 @@ export 'application_server.dart';
export 'options.dart'; export 'options.dart';
export 'starter.dart'; export 'starter.dart';
/// This object starts and stops instances of your [ApplicationChannel]. /// The Application class is responsible for starting and managing instances of an ApplicationChannel.
/// ///
/// An application object opens HTTP listeners that forward requests to instances of your [ApplicationChannel]. /// An application object opens HTTP listeners that forward requests to instances of your [ApplicationChannel].
/// It is unlikely that you need to use this class directly - the `conduit serve` command creates an application object /// It is unlikely that you need to use this class directly - the `conduit serve` command creates an application object
/// on your behalf. /// on your behalf.
class Application<T extends ApplicationChannel> { class Application<T extends ApplicationChannel> {
/// A list of isolates that this application supervises. /// A list of isolates that this application supervises.
///
/// This list contains [ApplicationIsolateSupervisor] instances, each representing
/// an isolate running a separate instance of the application. These supervisors
/// are responsible for managing the lifecycle and communication with their
/// respective isolates. The list is populated when the application starts and
/// is cleared when the application stops.
List<ApplicationIsolateSupervisor> supervisors = []; List<ApplicationIsolateSupervisor> supervisors = [];
/// The [ApplicationServer] listening for HTTP requests while under test. /// The [ApplicationServer] listening for HTTP requests while under test.
/// ///
/// This property is only valid when an application is started via [startOnCurrentIsolate]. /// This property is only valid when an application is started via [startOnCurrentIsolate].
/// It represents the server instance that handles incoming HTTP requests during testing.
/// The server is initialized when the application starts on the current isolate and
/// provides access to the underlying HTTP server and application channel.
///
/// Note: This property should not be accessed before calling [startOnCurrentIsolate],
/// as it will not be initialized until then.
late ApplicationServer server; late ApplicationServer server;
/// The [ApplicationChannel] handling requests while under test. /// The [ApplicationChannel] handling requests while under test.
/// ///
/// This property is only valid when an application is started via [startOnCurrentIsolate]. You use /// This property provides access to the application channel instance when the application
/// this value to access elements of your application channel during testing. /// is started using [startOnCurrentIsolate]. It allows direct interaction with the channel
/// during testing, enabling access to its properties and methods.
///
/// The returned value is cast to type [T], which should match the generic type parameter
/// of the [Application] class.
///
/// This property is only valid and accessible after calling [startOnCurrentIsolate].
/// Attempting to access it before starting the application or when using [start] instead
/// of [startOnCurrentIsolate] may result in unexpected behavior or errors.
///
/// Usage example:
/// ```dart
/// final app = Application<MyChannel>();
/// await app.startOnCurrentIsolate();
/// final myChannel = app.channel;
/// // Now you can interact with myChannel for testing purposes
/// ```
T get channel => server.channel as T; T get channel => server.channel as T;
/// The logger that this application will write messages to. /// The logger that this application will write messages to.
/// ///
/// This logger's name will appear as 'conduit'. /// This logger is used throughout the application to record messages, errors,
Logger logger = Logger("conduit"); /// and other important information. It is configured with the name 'protevus',
/// which will appear as the source of all log messages generated by this logger.
///
/// The Logger class is likely from a logging package, providing various methods
/// for different log levels (e.g., info, warning, error) and potentially
/// supporting different output destinations or formatting options.
///
/// Usage of this logger helps in debugging, monitoring, and maintaining the
/// application by providing a centralized way to capture and analyze runtime
/// information.
Logger logger = Logger("protevus");
/// The options used to configure this application. /// The options used to configure this application.
/// ///
/// Changing these values once the application has started will have no effect. /// This property holds an instance of [ApplicationOptions] that contains various
/// configuration settings for the application. These options can include things
/// like port numbers, database configurations, or any other application-specific
/// settings.
///
/// The options are typically set before the application is started. It's important
/// to note that modifying these options after the application has been started
/// will not have any effect on the running application.
///
/// Example usage:
/// ```dart
/// final app = Application<MyChannel>();
/// app.options.port = 8080;
/// app.options.configurationFilePath = 'config.yaml';
/// await app.start();
/// ```
///
/// Default value is an instance of [ApplicationOptions] with default settings.
ApplicationOptions options = ApplicationOptions(); ApplicationOptions options = ApplicationOptions();
/// The duration to wait for each isolate during startup before failing. /// The duration to wait for each isolate during startup before failing.
/// ///
/// A [TimeoutException] is thrown if an isolate fails to startup in this time period. /// This property sets the maximum time allowed for each isolate to start up
/// during the application's initialization process. If an isolate fails to
/// complete its startup within this time frame, a [TimeoutException] is thrown.
/// ///
/// Defaults to 30 seconds. /// Defaults to 30 seconds.
Duration isolateStartupTimeout = const Duration(seconds: 30); Duration isolateStartupTimeout = const Duration(seconds: 30);
/// Whether or not this application is running. /// Indicates whether the application is currently running.
/// ///
/// This will return true if [start]/[startOnCurrentIsolate] have been invoked and completed; i.e. this is the synchronous version of the [Future] returned by [start]/[startOnCurrentIsolate]. /// This will return true if [start]/[startOnCurrentIsolate] have been invoked and completed; i.e. this is the synchronous version of the [Future] returned by [start]/[startOnCurrentIsolate].
/// ///
/// This value will return to false after [stop] has completed. /// This value will return to false after [stop] has completed.
bool get isRunning => _hasFinishedLaunching; bool get isRunning => _hasFinishedLaunching;
/// Indicates whether the application has finished launching.
///
/// This boolean flag is set to true once the application has successfully
/// completed its startup process, including initializing all isolates and
/// opening HTTP listeners. It is used internally to track the application's
/// running state and is consulted by the [isRunning] getter.
///
/// The value is set to false initially and when the application is stopped,
/// and set to true at the end of successful [start] or [startOnCurrentIsolate]
/// method execution.
bool _hasFinishedLaunching = false; bool _hasFinishedLaunching = false;
/// Retrieves the [ChannelRuntime] for the current application channel type.
///
/// This getter accesses the [RuntimeContext.current] map using the generic type [T]
/// (which represents the application's channel type) as the key. It then casts
/// the retrieved value to [ChannelRuntime].
///
/// The [ChannelRuntime] object contains runtime information and utilities
/// specific to the channel type, which are used in various parts of the
/// application for setup, initialization, and execution.
///
/// This getter is used internally by the Application class to access
/// channel-specific runtime information without exposing it publicly.
ChannelRuntime get _runtime => RuntimeContext.current[T] as ChannelRuntime; ChannelRuntime get _runtime => RuntimeContext.current[T] as ChannelRuntime;
/// Starts this application, allowing it to handle HTTP requests. /// Starts this application, allowing it to handle HTTP requests.
/// ///
/// This method spawns [numberOfInstances] isolates, instantiates your application channel /// This method initializes the application by spawning a specified number of isolates,
/// for each of these isolates, and opens an HTTP listener that sends requests to these instances. /// each running an instance of the application channel. It then sets up an HTTP listener
/// to distribute incoming requests across these isolates.
/// ///
/// The [Future] returned from this method will complete once all isolates have successfully started /// The [Future] returned from this method will complete once all isolates have successfully started
/// and are available to handle requests. /// and are available to handle requests.
@ -110,7 +201,7 @@ class Application<T extends ApplicationChannel> {
_hasFinishedLaunching = true; _hasFinishedLaunching = true;
} }
/// Starts the application on the current isolate, and does not spawn additional isolates. /// Starts the application on the current isolate without spawning additional isolates.
/// ///
/// An application started in this way will run on the same isolate this method is invoked on. /// An application started in this way will run on the same isolate this method is invoked on.
/// Performance is limited when running the application with this method; prefer to use [start]. /// Performance is limited when running the application with this method; prefer to use [start].
@ -139,8 +230,20 @@ class Application<T extends ApplicationChannel> {
/// Stops the application from running. /// Stops the application from running.
/// ///
/// Closes every isolate and their channel and stops listening for HTTP requests. /// This method performs the following actions:
/// The [ServiceRegistry] will close any of its resources. /// 1. Sets the '_hasFinishedLaunching' flag to false.
/// 2. Stops all supervisor isolates concurrently.
/// 3. Handles potential errors during supervisor shutdown, particularly checking for 'LateError'.
/// 4. Attempts to close the server forcefully.
/// 5. Logs any errors that occur during server closure.
/// 6. Resets the '_hasFinishedLaunching' flag and clears the supervisors list.
/// 7. Removes all listeners from the logger.
///
/// If a 'LateError' is encountered during supervisor shutdown, it throws a [StateError]
/// indicating that the channel type was not properly loaded.
///
/// This method ensures a clean shutdown of all application components and should be
/// called when the application needs to be terminated.
Future stop() async { Future stop() async {
_hasFinishedLaunching = false; _hasFinishedLaunching = false;
await Future.wait(supervisors.map((s) => s.stop())) await Future.wait(supervisors.map((s) => s.stop()))
@ -167,6 +270,26 @@ class Application<T extends ApplicationChannel> {
/// Creates an [APIDocument] from an [ApplicationChannel]. /// Creates an [APIDocument] from an [ApplicationChannel].
/// ///
/// This static method generates API documentation for a given application channel type.
/// It is primarily used by the 'conduit document' CLI command to create OpenAPI (formerly Swagger) documentation.
///
/// The method performs the following steps:
/// 1. Retrieves the runtime context for the specified channel type.
/// 2. Runs global initialization with the provided configuration.
/// 3. Creates an ApplicationServer instance.
/// 4. Prepares the channel.
/// 5. Generates the API documentation.
/// 6. Closes the channel.
/// 7. Returns the generated APIDocument.
///
/// Parameters:
/// - [type]: The Type of the ApplicationChannel subclass.
/// - [config]: The ApplicationOptions containing configuration for the application.
/// - [projectSpec]: A Map containing additional project-specific information for the documentation.
///
/// Returns:
/// A Future that resolves to an [APIDocument] containing the generated API documentation.
///
/// This method is called by the `conduit document` CLI. /// This method is called by the `conduit document` CLI.
static Future<APIDocument> document( static Future<APIDocument> document(
Type type, Type type,
@ -188,6 +311,25 @@ class Application<T extends ApplicationChannel> {
return doc; return doc;
} }
/// Spawns a new isolate to run an instance of the application.
///
/// This method creates a new isolate that runs an instance of the application channel.
/// It sets up the necessary communication channels and initializes the isolate with
/// the application's configuration and runtime information.
///
/// Parameters:
/// - [application]: The main Application instance.
/// - [config]: ApplicationOptions containing the configuration for this instance.
/// - [identifier]: A unique identifier for this isolate.
/// - [logger]: The Logger instance for logging.
/// - [startupTimeout]: The maximum duration allowed for the isolate to start up.
/// - [logToConsole]: Whether to enable console logging for this isolate (default: false).
///
/// Returns:
/// A Future that resolves to an [ApplicationIsolateSupervisor] managing the new isolate.
///
/// This method is crucial for scaling the application across multiple isolates,
/// allowing for better performance and resource utilization.
Future<ApplicationIsolateSupervisor> _spawn( Future<ApplicationIsolateSupervisor> _spawn(
Application application, Application application,
ApplicationOptions config, ApplicationOptions config,
@ -224,7 +366,7 @@ class Application<T extends ApplicationChannel> {
} }
} }
/// Thrown when an application encounters an exception during startup. /// Represents an exception that occurs during the startup process of an application.
/// ///
/// Contains the original exception that halted startup. /// Contains the original exception that halted startup.
class ApplicationStartupException implements Exception { class ApplicationStartupException implements Exception {

View file

@ -1,17 +1,38 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:logging/logging.dart';
import 'package:protevus_application/application.dart'; import 'package:protevus_application/application.dart';
import 'package:protevus_http/http.dart'; import 'package:protevus_http/http.dart';
import 'package:protevus_runtime/runtime.dart'; import 'package:protevus_runtime/runtime.dart';
import 'package:logging/logging.dart';
/// Listens for HTTP requests and delivers them to its [ApplicationChannel] instance. /// A class representing an application server in the Conduit framework.
/// ///
/// A Conduit application creates instances of this type to pair an HTTP server and an /// The ApplicationServer class is responsible for managing the lifecycle of an HTTP server
/// instance of an [ApplicationChannel] subclass. Instances are created by [Application] /// and its associated [ApplicationChannel]. It handles server creation, starting, and stopping,
/// and shouldn't be created otherwise. /// as well as routing incoming requests to the appropriate handlers.
///
/// Key features:
/// - Creates and manages an instance of [ApplicationChannel]
/// - Configures and starts an HTTP or HTTPS server
/// - Handles incoming requests and routes them to the appropriate controller
/// - Manages server lifecycle (start, stop, close)
/// - Provides logging capabilities
/// - Supports both IPv4 and IPv6
/// - Handles secure connections with SSL/TLS
///
/// This class is typically instantiated and managed by the Application class and should not
/// be created directly in most cases
class ApplicationServer { class ApplicationServer {
/// Creates a new server. /// Creates a new server instance.
/// ///
/// You should not need to invoke this method directly. /// You should not need to invoke this method directly.
ApplicationServer(this.channelType, this.options, this.identifier) { ApplicationServer(this.channelType, this.options, this.identifier) {
@ -21,41 +42,145 @@ class ApplicationServer {
..options = options; ..options = options;
} }
/// The configuration this instance used to start its [channel]. /// The configuration options used to start this server's [channel].
///
/// This property holds an instance of [ApplicationOptions] which contains
/// various settings used to configure the server, such as the address to bind to,
/// the port number, SSL/TLS settings, and other application-specific options.
/// These options are passed to the [ApplicationChannel] when it is initialized.
ApplicationOptions options; ApplicationOptions options;
/// The underlying [HttpServer]. /// The underlying [HttpServer] instance used by this [ApplicationServer].
///
/// This property represents the core HTTP server that handles incoming requests.
/// It is initialized when the server starts and is used throughout the lifecycle
/// of the [ApplicationServer] to manage incoming connections and route requests
/// to the appropriate handlers.
///
/// The server can be either a standard HTTP server or an HTTPS server, depending
/// on the configuration and security context provided during initialization.
late final HttpServer server; late final HttpServer server;
/// The instance of [ApplicationChannel] serving requests. /// The instance of [ApplicationChannel] serving requests.
///
/// This property represents the primary request handling pipeline for the application.
/// It is instantiated when the ApplicationServer is created and is responsible for
/// processing incoming HTTP requests, routing them to appropriate controllers,
/// and generating responses.
///
/// The [ApplicationChannel] is a custom class defined by the application developer
/// that sets up the request handling logic, including middleware, controllers,
/// and other application-specific components.
late ApplicationChannel channel; late ApplicationChannel channel;
/// The cached entrypoint of [channel]. /// The cached entrypoint of [channel].
///
/// This property stores the main [Controller] that serves as the entry point for request handling.
/// It is initialized when the server starts and is used to process incoming HTTP requests.
/// The entrypoint controller typically represents the root of the request handling pipeline
/// and may delegate to other controllers or middleware as needed.
late Controller entryPoint; late Controller entryPoint;
/// The type of [ApplicationChannel] this server will use.
///
/// This property stores the Type of the ApplicationChannel subclass that will be
/// instantiated and used by this ApplicationServer. The ApplicationChannel
/// defines the request handling logic and routing for the application.
final Type channelType; final Type channelType;
/// Target for sending messages to other [ApplicationChannel.messageHub]s. /// Target for sending messages to other [ApplicationChannel.messageHub]s.
/// ///
/// Events are added to this property by instances of [ApplicationMessageHub] and should not otherwise be used. /// This property represents an [EventSink] that can be used to send messages
/// to other [ApplicationChannel.messageHub]s across different instances of
/// the application. It is primarily used for inter-server communication in
/// distributed setups.
///
/// The [hubSink] is typically set and managed by instances of [ApplicationMessageHub].
/// Application developers should not directly modify or use this property, as it is
/// intended for internal framework use.
///
/// The sink can be null if no message hub has been configured for this server.
EventSink<dynamic>? hubSink; EventSink<dynamic>? hubSink;
/// Whether or not this server requires an HTTPS listener. /// Indicates whether this server requires an HTTPS listener.
///
/// This getter returns a boolean value that determines if the server
/// should use HTTPS instead of HTTP. It is typically set to true when
/// a security context is provided during server initialization.
///
/// Returns:
/// [bool]: true if the server requires HTTPS, false otherwise.
bool get requiresHTTPS => _requiresHTTPS; bool get requiresHTTPS => _requiresHTTPS;
/// Indicates whether this server instance is configured to use HTTPS.
///
/// This private variable is set to true when a security context is provided
/// during server initialization, indicating that the server should use HTTPS.
/// It is used internally to determine the server's connection type and is
/// accessed through the public getter [requiresHTTPS].
///
/// The value is false by default, assuming HTTP connection, and is only set to
/// true when HTTPS is explicitly configured.
bool _requiresHTTPS = false; bool _requiresHTTPS = false;
/// The unique identifier of this instance. /// The unique identifier of this instance.
/// ///
/// Each instance has its own identifier, a numeric value starting at 1, to identify it /// Each instance has its own identifier, a numeric value starting at 1, to identify it
/// among other instances. /// among other instances.
///
/// This identifier is used to distinguish between different [ApplicationServer] instances
/// when multiple servers are running concurrently. It's particularly useful for logging
/// and debugging purposes, allowing developers to trace which server instance is handling
/// specific requests or operations.
///
/// The identifier is typically assigned automatically by the [Application] class when
/// creating new server instances, ensuring that each server has a unique number.
///
/// Example:
/// If three server instances are created, they might have identifiers 1, 2, and 3 respectively.
int identifier; int identifier;
/// The logger of this instance /// Returns the logger instance for this ApplicationServer.
Logger get logger => Logger("conduit"); ///
/// This getter provides access to a [Logger] instance specifically configured
/// for the Conduit framework. The logger is named "conduit" and can be used
/// throughout the ApplicationServer and its associated classes for consistent
/// logging purposes.
///
/// The logger can be used to record various levels of information, warnings,
/// and errors during the server's operation, which is crucial for debugging
/// and monitoring the application's behavior.
///
/// Returns:
/// A [Logger] instance named "conduit".
Logger get logger => Logger("protevus");
/// Starts this instance, allowing it to receive HTTP requests. /// Starts this instance, allowing it to receive HTTP requests.
/// ///
/// Do not invoke this method directly. /// This method initializes the server, preparing it to handle incoming HTTP requests.
/// It performs the following steps:
/// 1. Prepares the channel by calling [channel.prepare()].
/// 2. Sets up the entry point for request handling.
/// 3. Binds the HTTP server to the specified address and port.
/// 4. Configures HTTPS if a security context is provided.
///
/// The method supports both HTTP and HTTPS connections, determined by the presence
/// of a security context. It also handles IPv6 configuration and server sharing options.
///
/// Parameters:
/// [shareHttpServer] - A boolean indicating whether to share the HTTP server
/// across multiple instances. Defaults to false.
///
/// Returns:
/// A [Future] that completes when the server has successfully started and is
/// ready to receive requests.
///
/// Throws:
/// May throw exceptions related to network binding or security context configuration.
///
/// Note:
/// This method should not be invoked directly under normal circumstances.
/// It is typically called by the framework during the application startup process.
Future start({bool shareHttpServer = false}) async { Future start({bool shareHttpServer = false}) async {
logger.fine("ApplicationServer($identifier).start entry"); logger.fine("ApplicationServer($identifier).start entry");
@ -92,7 +217,21 @@ class ApplicationServer {
return didOpen(); return didOpen();
} }
/// Closes this HTTP server and channel. /// Closes this HTTP server and associated channel.
///
/// This method performs the following steps:
/// 1. Closes the HTTP server, forcibly terminating any ongoing connections.
/// 2. Closes the associated [ApplicationChannel].
/// 3. Closes the [hubSink] if it exists.
///
/// The method logs the progress of each step for debugging purposes.
///
/// Returns:
/// A [Future] that completes when all closing operations are finished.
///
/// Note:
/// The [hubSink] is actually closed by channel.messageHub.close, but it's
/// explicitly closed here to satisfy the Dart analyzer.
Future close() async { Future close() async {
logger.fine("ApplicationServer($identifier).close Closing HTTP listener"); logger.fine("ApplicationServer($identifier).close Closing HTTP listener");
await server.close(force: true); await server.close(force: true);
@ -104,7 +243,7 @@ class ApplicationServer {
logger.fine("ApplicationServer($identifier).close Closing complete"); logger.fine("ApplicationServer($identifier).close Closing complete");
} }
/// Invoked when this server becomes ready receive requests. /// Invoked when this server becomes ready to receive requests.
/// ///
/// [ApplicationChannel.willStartReceivingRequests] is invoked after this opening has completed. /// [ApplicationChannel.willStartReceivingRequests] is invoked after this opening has completed.
Future didOpen() async { Future didOpen() async {
@ -117,6 +256,18 @@ class ApplicationServer {
logger.info("Server conduit/$identifier started."); logger.info("Server conduit/$identifier started.");
} }
/// Sends an application event.
///
/// This method is designed to handle application-wide events. By default,
/// it does nothing and serves as a placeholder for potential event handling
/// implementations in derived classes.
///
/// Parameters:
/// [event]: A dynamic object representing the event to be sent.
/// It can be of any type, allowing flexibility in event structures.
///
/// Note:
/// Override this method in subclasses to implement specific event handling logic.
void sendApplicationEvent(dynamic event) { void sendApplicationEvent(dynamic event) {
// By default, do nothing // By default, do nothing
} }

View file

@ -1,3 +1,12 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
@ -9,7 +18,7 @@ import 'package:protevus_runtime/runtime.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
/// An object that defines the behavior specific to your application. /// An abstract class that defines the behavior specific to your application.
/// ///
/// You create a subclass of [ApplicationChannel] to initialize your application's services and define how HTTP requests are handled by your application. /// You create a subclass of [ApplicationChannel] to initialize your application's services and define how HTTP requests are handled by your application.
/// There *must* only be one subclass in an application and it must be visible to your application library file, e.g., 'package:my_app/my_app.dart'. /// There *must* only be one subclass in an application and it must be visible to your application library file, e.g., 'package:my_app/my_app.dart'.
@ -21,7 +30,7 @@ import 'package:meta/meta.dart';
/// When your application is started, an instance of your application channel is created for each isolate (see [Application.start]). Each instance /// When your application is started, an instance of your application channel is created for each isolate (see [Application.start]). Each instance
/// is a replica of your application that runs in its own memory isolated thread. /// is a replica of your application that runs in its own memory isolated thread.
abstract class ApplicationChannel implements APIComponentDocumenter { abstract class ApplicationChannel implements APIComponentDocumenter {
/// You implement this method to provide global initialization for your application. /// Provides global initialization for the application.
/// ///
/// Most of your application initialization code is written in [prepare], which is invoked for each isolate. For initialization that /// Most of your application initialization code is written in [prepare], which is invoked for each isolate. For initialization that
/// needs to occur once per application start, you must provide an implementation for this method. This method is invoked prior /// needs to occur once per application start, you must provide an implementation for this method. This method is invoked prior
@ -51,27 +60,43 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
/// is for documentation purposes. /// is for documentation purposes.
static Future initializeApplication(ApplicationOptions options) async {} static Future initializeApplication(ApplicationOptions options) async {}
/// The logger that this object will write messages to. /// Returns a Logger instance for this object.
/// ///
/// This logger's name appears as 'conduit'. /// This logger's name appears as 'conduit'.
Logger get logger => Logger("conduit"); Logger get logger => Logger("protevus");
/// The [ApplicationServer] that sends HTTP requests to this object. /// Returns the [ApplicationServer] instance that sends HTTP requests to this object.
///
/// This getter provides access to the server associated with this ApplicationChannel.
/// The server is responsible for handling incoming HTTP requests and routing them
/// to the appropriate controllers within the channel.
ApplicationServer get server => _server; ApplicationServer get server => _server;
/// Sets the ApplicationServer for this channel and establishes message hub connections.
///
/// This setter method performs two main tasks:
/// 1. It assigns the provided [server] to the private [_server] variable.
/// 2. It sets up the message hub connections:
/// - It adds a listener to the outbound stream of the messageHub, which sends
/// application events through the server.
/// - It sets the inbound sink of the messageHub as the hubSink of the server.
///
/// This setup allows for inter-isolate communication through the ApplicationMessageHub.
///
/// [server] The ApplicationServer instance to be set for this channel.
set server(ApplicationServer server) { set server(ApplicationServer server) {
_server = server; _server = server;
messageHub._outboundController.stream.listen(server.sendApplicationEvent); messageHub._outboundController.stream.listen(server.sendApplicationEvent);
server.hubSink = messageHub._inboundController.sink; server.hubSink = messageHub._inboundController.sink;
} }
/// Use this object to send data to channels running on other isolates. /// A messaging hub for inter-isolate communication within the application.
/// ///
/// You use this object to synchronize state across the isolates of an application. Any data sent /// You use this object to synchronize state across the isolates of an application. Any data sent
/// through this object will be received by every other channel in your application (except the one that sent it). /// through this object will be received by every other channel in your application (except the one that sent it).
final ApplicationMessageHub messageHub = ApplicationMessageHub(); final ApplicationMessageHub messageHub = ApplicationMessageHub();
/// The context used for setting up HTTPS in an application. /// Returns a SecurityContext for HTTPS configuration if certificate and private key files are provided.
/// ///
/// If this value is non-null, the [server] receiving HTTP requests will only accept requests over HTTPS. /// If this value is non-null, the [server] receiving HTTP requests will only accept requests over HTTPS.
/// ///
@ -93,6 +118,17 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
/// ///
/// These options are set when starting the application. Changes to this object have no effect /// These options are set when starting the application. Changes to this object have no effect
/// on other isolates. /// on other isolates.
///
/// This property holds an instance of [ApplicationOptions] which contains various
/// configuration settings for the application. These options are typically set
/// during the application's startup process.
///
/// The options stored here are specific to this channel instance and do not
/// affect other isolates running in the application. This means that modifying
/// these options at runtime will only impact the current isolate.
///
/// The property is nullable, allowing for cases where options might not be set
/// or where default configurations are used in the absence of specific options.
ApplicationOptions? options; ApplicationOptions? options;
/// You implement this accessor to define how HTTP requests are handled by your application. /// You implement this accessor to define how HTTP requests are handled by your application.
@ -112,9 +148,17 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
/// } /// }
Controller get entryPoint; Controller get entryPoint;
/// The [ApplicationServer] instance associated with this channel.
///
/// This private variable stores the server that handles HTTP requests for this
/// ApplicationChannel. It is marked as 'late' because it will be initialized
/// after the channel is created, typically when the 'server' setter is called.
///
/// The server is responsible for managing incoming HTTP connections and
/// routing requests to the appropriate controllers within the channel.
late ApplicationServer _server; late ApplicationServer _server;
/// You override this method to perform initialization tasks. /// Performs initialization tasks for the application channel.
/// ///
/// This method allows this instance to perform any initialization (other than setting up the [entryPoint]). This method /// This method allows this instance to perform any initialization (other than setting up the [entryPoint]). This method
/// is often used to set up services that [Controller]s use to fulfill their duties. This method is invoked /// is often used to set up services that [Controller]s use to fulfill their duties. This method is invoked
@ -123,12 +167,12 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
/// By default, this method does nothing. /// By default, this method does nothing.
Future prepare() async {} Future prepare() async {}
/// You override this method to perform initialization tasks that occur after [entryPoint] has been established. /// Overridable method called just before the application starts receiving requests.
/// ///
/// Override this method to take action just before [entryPoint] starts receiving requests. By default, does nothing. /// Override this method to take action just before [entryPoint] starts receiving requests. By default, does nothing.
void willStartReceivingRequests() {} void willStartReceivingRequests() {}
/// You override this method to release any resources created in [prepare]. /// Releases resources and performs cleanup when the application channel is closing.
/// ///
/// This method is invoked when the owning [Application] is stopped. It closes open ports /// This method is invoked when the owning [Application] is stopped. It closes open ports
/// that this channel was using so that the application can be properly shut down. /// that this channel was using so that the application can be properly shut down.
@ -146,7 +190,8 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
/// Creates an OpenAPI document for the components and paths in this channel. /// Creates an OpenAPI document for the components and paths in this channel.
/// ///
/// This method invokes [entryPoint] and [prepare] before starting the documentation process. /// This method generates a complete OpenAPI specification document for the application,
/// including all components, paths, and operations defined in the channel.
/// ///
/// The documentation process first invokes [documentComponents] on this channel. Every controller in the channel will have its /// The documentation process first invokes [documentComponents] on this channel. Every controller in the channel will have its
/// [documentComponents] methods invoked. Any declared property /// [documentComponents] methods invoked. Any declared property
@ -181,6 +226,24 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
return doc; return doc;
} }
/// Documents the components of this ApplicationChannel and its controllers.
///
/// This method is responsible for generating API documentation for the components
/// of this ApplicationChannel and its associated controllers. It performs the following tasks:
///
/// 1. Calls `documentComponents` on the entry point controller, which typically
/// initiates the documentation process for all linked controllers.
///
/// 2. Retrieves all documentable channel components using the ChannelRuntime,
/// which are typically services or other objects that implement APIComponentDocumenter.
///
/// 3. Calls `documentComponents` on each of these channel components, allowing
/// them to add their own documentation to the API registry.
///
/// This method is marked with @mustCallSuper, indicating that subclasses
/// overriding this method must call the superclass implementation.
///
/// [registry] The APIDocumentContext used to store and organize the API documentation.
@mustCallSuper @mustCallSuper
@override @override
void documentComponents(APIDocumentContext registry) { void documentComponents(APIDocumentContext registry) {
@ -194,7 +257,7 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
} }
} }
/// An object that sends and receives messages between [ApplicationChannel]s. /// An object that facilitates message passing between [ApplicationChannel]s in different isolates.
/// ///
/// You use this object to share information between isolates. Each [ApplicationChannel] has a property of this type. A message sent through this object /// You use this object to share information between isolates. Each [ApplicationChannel] has a property of this type. A message sent through this object
/// is received by every other channel through its hub. /// is received by every other channel through its hub.
@ -218,14 +281,44 @@ abstract class ApplicationChannel implements APIComponentDocumenter {
/// } /// }
/// }); /// });
class ApplicationMessageHub extends Stream<dynamic> implements Sink<dynamic> { class ApplicationMessageHub extends Stream<dynamic> implements Sink<dynamic> {
final Logger _logger = Logger("conduit"); /// A logger instance for the ApplicationMessageHub.
///
/// This logger is used to log messages and errors related to the ApplicationMessageHub.
/// It is named "protevus" to identify logs from this specific component.
final Logger _logger = Logger("protevus");
/// A StreamController for outbound messages.
///
/// This controller manages the stream of outbound messages sent from this
/// ApplicationMessageHub to other hubs. It is used internally to handle
/// the flow of messages being sent out to other isolates.
///
/// The stream is not broadcast, meaning it only allows a single subscriber.
/// This is typically used by the ApplicationServer to listen for outbound
/// messages and distribute them to other isolates.
final StreamController<dynamic> _outboundController = final StreamController<dynamic> _outboundController =
StreamController<dynamic>(); StreamController<dynamic>();
/// A StreamController for inbound messages.
///
/// This controller manages the stream of inbound messages received by this
/// ApplicationMessageHub from other hubs. It is used internally to handle
/// the flow of messages coming in from other isolates.
///
/// The stream is broadcast, meaning it allows multiple subscribers. This allows
/// multiple parts of the application to listen for and react to incoming messages
/// independently.
final StreamController<dynamic> _inboundController = final StreamController<dynamic> _inboundController =
StreamController<dynamic>.broadcast(); StreamController<dynamic>.broadcast();
/// Adds a listener for messages from other hubs. /// Adds a listener for messages from other hubs.
/// ///
/// A class that facilitates message passing between [ApplicationChannel]s in different isolates.
///
/// This class implements both [Stream] and [Sink] interfaces, allowing it to send and receive messages
/// across isolates. It uses separate controllers for inbound and outbound messages to manage the flow
/// of data.
///
/// You use this method to add listeners for messages from other hubs. /// You use this method to add listeners for messages from other hubs.
/// When another hub [add]s a message, this hub will receive it on [onData]. /// When another hub [add]s a message, this hub will receive it on [onData].
/// ///
@ -249,7 +342,8 @@ class ApplicationMessageHub extends Stream<dynamic> implements Sink<dynamic> {
/// Sends a message to all other hubs. /// Sends a message to all other hubs.
/// ///
/// [event] will be delivered to all other isolates that have set up a callback for [listen]. /// This method allows sending a message [event] to all other isolates in the application.
/// The message will be delivered to all other isolates that have set up a callback using [listen].
/// ///
/// [event] must be isolate-safe data - in general, this means it may not be or contain a closure. Consult the API reference `dart:isolate` for more details. If [event] /// [event] must be isolate-safe data - in general, this means it may not be or contain a closure. Consult the API reference `dart:isolate` for more details. If [event]
/// is not isolate-safe data, an error is delivered to [listen] on this isolate. /// is not isolate-safe data, an error is delivered to [listen] on this isolate.
@ -258,6 +352,19 @@ class ApplicationMessageHub extends Stream<dynamic> implements Sink<dynamic> {
_outboundController.sink.add(event); _outboundController.sink.add(event);
} }
/// Closes the message hub and its associated stream controllers.
///
/// This method performs the following tasks:
/// 1. If the outbound controller has no listeners, it adds a dummy listener
/// to prevent potential issues with unhandled stream events.
/// 2. If the inbound controller has no listeners, it adds a dummy listener
/// for the same reason.
/// 3. Closes both the outbound and inbound controllers.
///
/// This method should be called when the application is shutting down or
/// when the message hub is no longer needed to ensure proper cleanup of resources.
///
/// Returns a Future that completes when both controllers have been closed.
@override @override
Future close() async { Future close() async {
if (!_outboundController.hasListener) { if (!_outboundController.hasListener) {
@ -273,6 +380,10 @@ class ApplicationMessageHub extends Stream<dynamic> implements Sink<dynamic> {
} }
} }
/// An abstract class that defines the runtime behavior of an ApplicationChannel.
///
/// This class provides methods and properties for managing the lifecycle,
/// documentation, and instantiation of an ApplicationChannel.
abstract class ChannelRuntime { abstract class ChannelRuntime {
Iterable<APIComponentDocumenter> getDocumentableChannelComponents( Iterable<APIComponentDocumenter> getDocumentableChannelComponents(
ApplicationChannel channel, ApplicationChannel channel,

View file

@ -1,10 +1,47 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:async'; import 'dart:async';
import 'dart:isolate'; import 'dart:isolate';
import 'package:protevus_application/application.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:protevus_application/application.dart';
/// An isolated server implementation of the ApplicationServer class.
///
/// This class extends ApplicationServer to run in a separate isolate, allowing
/// for concurrent execution of multiple server instances. It manages communication
/// with a supervising application through message passing.
///
/// The server can be started, stopped, and can send and receive application events.
/// It also supports optional console logging for debugging purposes.
///
/// Constructor parameters:
/// - channelType: The type of channel to be used.
/// - configuration: ApplicationOptions for server configuration.
/// - identifier: A unique identifier for this server instance.
/// - supervisingApplicationPort: SendPort for communicating with the supervising application.
/// - logToConsole: Optional flag to enable console logging (default is false).
class ApplicationIsolateServer extends ApplicationServer { class ApplicationIsolateServer extends ApplicationServer {
/// Constructor for ApplicationIsolateServer.
///
/// Creates a new instance of ApplicationIsolateServer with the specified parameters.
///
/// Parameters:
/// - channelType: The type of channel to be used for communication.
/// - configuration: ApplicationOptions for configuring the server.
/// - identifier: A unique identifier for this server instance.
/// - supervisingApplicationPort: SendPort for communicating with the supervising application.
/// - logToConsole: Optional flag to enable console logging (default is false).
///
/// This constructor initializes the server, sets up logging if enabled, and establishes
/// communication with the supervising application. It also sets up a listener for
/// incoming messages from the supervisor.
ApplicationIsolateServer( ApplicationIsolateServer(
Type channelType, Type channelType,
ApplicationOptions configuration, ApplicationOptions configuration,
@ -26,9 +63,40 @@ class ApplicationIsolateServer extends ApplicationServer {
supervisingApplicationPort.send(supervisingReceivePort.sendPort); supervisingApplicationPort.send(supervisingReceivePort.sendPort);
} }
/// A SendPort used for communication with the supervising application.
///
/// This SendPort allows the ApplicationIsolateServer to send messages and events
/// back to the supervising application, enabling bidirectional communication
/// between the isolated server and its parent process.
SendPort supervisingApplicationPort; SendPort supervisingApplicationPort;
/// A ReceivePort for receiving messages from the supervising application.
///
/// This ReceivePort is used to listen for incoming messages from the supervising
/// application. It's initialized in the constructor and is used to set up a
/// listener for handling various commands and messages, such as stop requests
/// or application events.
///
/// The 'late' keyword indicates that this variable will be initialized after
/// the constructor body, but before it's used.
late ReceivePort supervisingReceivePort; late ReceivePort supervisingReceivePort;
/// Starts the ApplicationIsolateServer.
///
/// This method overrides the base class's start method to add functionality
/// specific to the isolated server. It performs the following steps:
/// 1. Calls the superclass's start method with the provided shareHttpServer parameter.
/// 2. Logs a fine-level message indicating that the server has started.
/// 3. Sends a 'listening' message to the supervising application.
///
/// Parameters:
/// - shareHttpServer: A boolean indicating whether to share the HTTP server (default is false).
///
/// Returns:
/// A Future that completes with the result of the superclass's start method.
///
/// Throws:
/// Any exceptions that may be thrown by the superclass's start method.
@override @override
Future start({bool shareHttpServer = false}) async { Future start({bool shareHttpServer = false}) async {
final result = await super.start(shareHttpServer: shareHttpServer); final result = await super.start(shareHttpServer: shareHttpServer);
@ -41,6 +109,21 @@ class ApplicationIsolateServer extends ApplicationServer {
return result; return result;
} }
/// Sends an application event to the supervising application.
///
/// This method overrides the base class's sendApplicationEvent method to
/// implement event sending in the context of an isolated server. It wraps
/// the event in a MessageHubMessage and sends it through the
/// supervisingApplicationPort.
///
/// Parameters:
/// - event: The application event to be sent. Can be of any type.
///
/// If an error occurs during the sending process, it is caught and added
/// to the hubSink as an error, along with the stack trace.
///
/// Note: This method does not throw exceptions directly; instead, it
/// reports errors through the hubSink.
@override @override
void sendApplicationEvent(dynamic event) { void sendApplicationEvent(dynamic event) {
try { try {
@ -50,6 +133,22 @@ class ApplicationIsolateServer extends ApplicationServer {
} }
} }
/// Listener method for handling incoming messages from the supervising application.
///
/// This method processes two types of messages:
/// 1. A stop message (ApplicationIsolateSupervisor.messageKeyStop):
/// When received, it calls the stop() method to shut down the server.
/// 2. A MessageHubMessage:
/// When received, it adds the payload of the message to the hubSink.
///
/// Parameters:
/// - message: The incoming message. Can be either a stop command or a MessageHubMessage.
///
/// This method doesn't return any value but performs actions based on the message type:
/// - For a stop message, it initiates the server shutdown process.
/// - For a MessageHubMessage, it propagates the payload to the hubSink if it exists.
///
/// Note: This method assumes that hubSink is properly initialized elsewhere in the class.
void listener(dynamic message) { void listener(dynamic message) {
if (message == ApplicationIsolateSupervisor.messageKeyStop) { if (message == ApplicationIsolateSupervisor.messageKeyStop) {
stop(); stop();
@ -58,6 +157,21 @@ class ApplicationIsolateServer extends ApplicationServer {
} }
} }
/// Stops the ApplicationIsolateServer and performs cleanup operations.
///
/// This method performs the following steps:
/// 1. Closes the supervisingReceivePort to stop receiving messages.
/// 2. Logs a fine-level message indicating the server is closing.
/// 3. Calls the close() method to shut down the server.
/// 4. Logs a fine-level message confirming the server has closed.
/// 5. Clears all listeners from the logger.
/// 6. Logs a fine-level message indicating it's sending a stop acknowledgement.
/// 7. Sends a stop acknowledgement message to the supervising application.
///
/// Returns:
/// A Future that completes when all stop operations are finished.
///
/// Note: This method is asynchronous and should be awaited when called.
Future stop() async { Future stop() async {
supervisingReceivePort.close(); supervisingReceivePort.close();
logger.fine("ApplicationIsolateServer($identifier) closing server"); logger.fine("ApplicationIsolateServer($identifier) closing server");
@ -72,10 +186,40 @@ class ApplicationIsolateServer extends ApplicationServer {
} }
} }
/// A typedef defining the signature for an isolate entry function.
///
/// This function type is used to define the entry point for an isolate in the context
/// of an ApplicationIsolateServer. It takes a single parameter of type
/// ApplicationInitialServerMessage, which contains all the necessary information
/// to initialize and run the server within the isolate.
///
/// Parameters:
/// - message: An ApplicationInitialServerMessage object containing configuration
/// details, identifiers, and communication ports needed to set up the server
/// in the isolate.
///
/// This typedef is typically used when spawning new isolates for server instances,
/// allowing for a standardized way of passing initial setup information to the isolate.
typedef IsolateEntryFunction = void Function( typedef IsolateEntryFunction = void Function(
ApplicationInitialServerMessage message, ApplicationInitialServerMessage message,
); );
/// Represents the initial message sent to an ApplicationIsolateServer when it's created.
///
/// This class encapsulates all the necessary information needed to initialize and
/// configure an ApplicationIsolateServer within an isolate. It includes details about
/// the stream type, configuration options, communication ports, and logging preferences.
///
/// Properties:
/// - streamTypeName: The name of the stream type to be used by the server.
/// - streamLibraryURI: The URI of the library containing the stream implementation.
/// - configuration: ApplicationOptions object containing server configuration details.
/// - parentMessagePort: SendPort for communicating with the parent (supervising) application.
/// - identifier: A unique identifier for the server instance.
/// - logToConsole: A boolean flag indicating whether to enable console logging (default is false).
///
/// This class is typically used when spawning a new isolate for an ApplicationIsolateServer,
/// providing all the necessary information in a single, structured message.
class ApplicationInitialServerMessage { class ApplicationInitialServerMessage {
ApplicationInitialServerMessage( ApplicationInitialServerMessage(
this.streamTypeName, this.streamTypeName,
@ -94,6 +238,16 @@ class ApplicationInitialServerMessage {
bool logToConsole = false; bool logToConsole = false;
} }
/// Represents a message that can be sent through the message hub.
///
/// This class encapsulates a payload of any type, allowing for flexible
/// communication between different parts of the application.
///
/// The payload can be of any type (dynamic), making this class versatile
/// for various types of messages.
///
/// Parameters:
/// - payload: The content of the message, which can be of any type.
class MessageHubMessage { class MessageHubMessage {
MessageHubMessage(this.payload); MessageHubMessage(this.payload);

View file

@ -1,3 +1,12 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:async'; import 'dart:async';
import 'dart:isolate'; import 'dart:isolate';
@ -6,9 +15,26 @@ import 'package:logging/logging.dart';
/// Represents the supervision of a [ApplicationIsolateServer]. /// Represents the supervision of a [ApplicationIsolateServer].
/// ///
/// This class, ApplicationIsolateSupervisor, is responsible for supervising and managing
/// an [ApplicationIsolateServer]. It handles the lifecycle of the isolate, including
/// starting, stopping, and communicating with it. The supervisor also manages error
/// handling, message passing between isolates, and ensures proper startup and shutdown
/// of the supervised isolate.
///
/// You should not use this class directly. /// You should not use this class directly.
class ApplicationIsolateSupervisor { class ApplicationIsolateSupervisor {
/// Create an instance of [ApplicationIsolateSupervisor]. /// Creates an instance of [ApplicationIsolateSupervisor].
///
/// This constructor initializes a new [ApplicationIsolateSupervisor] with the provided parameters.
///
/// Parameters:
/// - [supervisingApplication]: The [Application] instance that owns this supervisor.
/// - [isolate]: The [Isolate] being supervised.
/// - [receivePort]: The [ReceivePort] for receiving messages from the supervised isolate.
/// - [identifier]: A numeric identifier for the isolate relative to the [Application].
/// - [logger]: The [Logger] instance used for logging.
/// - [startupTimeout]: Optional. The maximum duration to wait for the isolate to start up.
/// Defaults to 30 seconds.
ApplicationIsolateSupervisor( ApplicationIsolateSupervisor(
this.supervisingApplication, this.supervisingApplication,
this.isolate, this.isolate,
@ -18,34 +44,168 @@ class ApplicationIsolateSupervisor {
this.startupTimeout = const Duration(seconds: 30), this.startupTimeout = const Duration(seconds: 30),
}); });
/// The [Isolate] being supervised. /// The [Isolate] being supervised by this [ApplicationIsolateSupervisor].
///
/// This isolate represents a separate thread of execution where the
/// [ApplicationIsolateServer] runs. The supervisor manages the lifecycle
/// and communication with this isolate.
final Isolate isolate; final Isolate isolate;
/// The [ReceivePort] for which messages coming from [isolate] will be received. /// The [ReceivePort] for receiving messages from the supervised isolate.
///
/// This [ReceivePort] is used to establish a communication channel between
/// the supervisor and the supervised isolate. It allows the supervisor to
/// receive various types of messages, including startup notifications,
/// error reports, and custom messages from the isolate.
///
/// The [receivePort] is crucial for managing the lifecycle of the isolate
/// and handling inter-isolate communication. It is used in conjunction with
/// the [listener] method to process incoming messages from the isolate.
final ReceivePort receivePort; final ReceivePort receivePort;
/// A numeric identifier for the isolate relative to the [Application]. /// A numeric identifier for the isolate relative to the [Application].
///
/// This identifier is unique within the context of the parent [Application].
/// It is used to distinguish between different isolates managed by the same
/// application, facilitating logging, debugging, and isolate management.
/// The identifier is typically assigned sequentially when creating new isolates.
final int identifier; final int identifier;
/// The maximum duration to wait for the isolate to start up.
///
/// This duration specifies the time limit for the supervised isolate to complete its
/// startup process. If the isolate fails to start within this timeout period, an
/// exception will be thrown. This helps prevent indefinite waiting in case of startup
/// issues.
///
/// The default value is typically set in the constructor, often to 30 seconds.
final Duration startupTimeout; final Duration startupTimeout;
/// A reference to the owning [Application] /// A reference to the owning [Application].
///
/// This property holds a reference to the [Application] instance that owns and manages
/// this [ApplicationIsolateSupervisor]. It allows the supervisor to interact with
/// the main application, access shared resources, and coordinate activities across
/// multiple isolates.
///
/// The supervising application is responsible for creating and managing the lifecycle
/// of this supervisor and its associated isolate. It also provides context for
/// operations such as logging, configuration, and inter-isolate communication.
Application supervisingApplication; Application supervisingApplication;
/// A reference to the [Logger] used by the [supervisingApplication]. /// The logger instance used for recording events and errors.
///
/// This [Logger] is typically shared with the [supervisingApplication] and is used
/// to log various events, warnings, and errors related to the isolate supervision
/// process. It helps in debugging and monitoring the behavior of the supervised
/// isolate and the supervisor itself.
///
/// The logger can be used to record information at different severity levels,
/// such as fine, info, warning, and severe, depending on the nature of the event
/// being logged.
Logger logger; Logger logger;
/// A list to store pending [MessageHubMessage] objects.
///
/// This queue is used to temporarily hold messages that are received when the
/// supervising application is not running. Once the application starts running,
/// these messages are processed and sent to other supervisors.
///
/// The queue helps ensure that no messages are lost during the startup phase
/// of the application, maintaining message integrity across isolates.
final List<MessageHubMessage> _pendingMessageQueue = []; final List<MessageHubMessage> _pendingMessageQueue = [];
/// Indicates whether the isolate is currently in the process of launching.
///
/// This getter returns `true` if the isolate is still in the startup phase,
/// and `false` if the launch process has completed.
///
/// It checks the state of the [_launchCompleter] to determine if the
/// launch process is still ongoing. If the completer is not yet completed,
/// it means the isolate is still launching.
///
/// This property is useful for handling different behaviors or error states
/// depending on whether the isolate is in its launch phase or has already
/// started running normally.
bool get _isLaunching => !_launchCompleter.isCompleted; bool get _isLaunching => !_launchCompleter.isCompleted;
/// The [SendPort] used to send messages to the supervised isolate.
///
/// This [SendPort] is initialized when the supervisor receives the corresponding
/// [SendPort] from the supervised isolate during the startup process. It enables
/// bi-directional communication between the supervisor and the supervised isolate.
///
/// The [_serverSendPort] is used to send various messages to the isolate, including
/// stop signals, custom application messages, and other control commands. It plays
/// a crucial role in managing the lifecycle and behavior of the supervised isolate.
late SendPort _serverSendPort; late SendPort _serverSendPort;
/// A [Completer] used to manage the launch process of the supervised isolate.
///
/// This completer is initialized when the isolate is being launched and is completed
/// when the isolate has successfully started up. It's used in conjunction with
/// [resume] method to handle the asynchronous nature of isolate startup.
///
/// The completer allows other parts of the supervisor to wait for the isolate
/// to finish launching before proceeding with further operations. It's also used
/// to implement timeout functionality in case the isolate fails to start within
/// the specified [startupTimeout].
late Completer _launchCompleter; late Completer _launchCompleter;
/// A [Completer] used to manage the stop process of the supervised isolate.
///
/// This nullable [Completer] is initialized when the [stop] method is called
/// and is used to handle the asynchronous nature of stopping the isolate.
/// It allows the supervisor to wait for the isolate to acknowledge the stop
/// message before proceeding with the termination process.
///
/// The completer is set to null after the stop process is complete, indicating
/// that the isolate has been successfully stopped. This helps manage the state
/// of the stop operation and prevents multiple stop attempts from interfering
/// with each other.
Completer? _stopCompleter; Completer? _stopCompleter;
/// A constant string used as a message key to signal the supervised isolate to stop.
///
/// This constant is used in the communication protocol between the supervisor
/// and the supervised isolate. When sent to the isolate, it indicates that
/// the isolate should begin its shutdown process.
///
/// The underscore prefix in the value suggests that this is intended for
/// internal use within the isolate communication system.
static const String messageKeyStop = "_MessageStop"; static const String messageKeyStop = "_MessageStop";
/// A constant string used as a message key to indicate that the supervised isolate is listening.
///
/// This constant is part of the communication protocol between the supervisor
/// and the supervised isolate. When the isolate sends this message to the
/// supervisor, it signals that the isolate has completed its startup process
/// and is ready to receive and process messages.
///
/// The underscore prefix in the value suggests that this is intended for
/// internal use within the isolate communication system.
static const String messageKeyListening = "_MessageListening"; static const String messageKeyListening = "_MessageListening";
/// Resumes the [Isolate] being supervised. /// Resumes the [Isolate] being supervised.
///
/// This method initiates the process of resuming the supervised isolate and
/// sets up the necessary listeners and error handlers. It performs the following steps:
///
/// 1. Initializes a new [Completer] for managing the launch process.
/// 2. Sets up a listener for the [receivePort] to handle incoming messages.
/// 3. Configures the isolate to handle errors non-fatally.
/// 4. Adds an error listener to the isolate.
/// 5. Resumes the isolate from its paused state.
/// 6. Waits for the isolate to complete its startup process.
///
/// If the isolate fails to start within the specified [startupTimeout], a
/// [TimeoutException] is thrown with a detailed error message.
///
/// Returns a [Future] that completes when the isolate has successfully started,
/// or throws an exception if the startup process fails or times out.
///
/// Throws:
/// - [TimeoutException] if the isolate doesn't start within the [startupTimeout].
Future resume() { Future resume() {
_launchCompleter = Completer(); _launchCompleter = Completer();
receivePort.listen(listener); receivePort.listen(listener);
@ -71,6 +231,24 @@ class ApplicationIsolateSupervisor {
} }
/// Stops the [Isolate] being supervised. /// Stops the [Isolate] being supervised.
///
/// This method initiates the process of stopping the supervised isolate. It performs the following steps:
///
/// 1. Creates a new [Completer] to manage the stop process.
/// 2. Sends a stop message to the supervised isolate using [_serverSendPort].
/// 3. Waits for the isolate to acknowledge the stop message.
/// 4. If the isolate doesn't respond within 5 seconds, logs a severe message.
/// 5. Forcefully kills the isolate using [isolate.kill()].
/// 6. Closes the [receivePort] to clean up resources.
///
/// The method uses a timeout of 5 seconds to wait for the isolate's acknowledgment.
/// If the timeout occurs, it assumes the isolate is not responding and proceeds to terminate it.
///
/// This method ensures that the isolate is stopped one way or another, either gracefully
/// or by force, and properly cleans up associated resources.
///
/// Returns a [Future] that completes when the stop process is finished, regardless of
/// whether the isolate responded to the stop message or was forcefully terminated.
Future stop() async { Future stop() async {
_stopCompleter = Completer(); _stopCompleter = Completer();
logger.fine( logger.fine(
@ -91,6 +269,22 @@ class ApplicationIsolateSupervisor {
receivePort.close(); receivePort.close();
} }
/// Handles incoming messages from the supervised isolate.
///
/// This method is the central message processing function for the supervisor.
/// It handles various types of messages:
///
/// - [SendPort]: Stores the send port for communicating with the isolate.
/// - [messageKeyListening]: Indicates the isolate has started and is listening.
/// - [messageKeyStop]: Acknowledges that the isolate has received a stop message.
/// - [List]: Represents an error from the isolate, which is then handled.
/// - [MessageHubMessage]: Inter-isolate communication message.
///
/// For [MessageHubMessage], if the supervising application is not running,
/// the message is queued. Otherwise, it's immediately sent to other supervisors.
///
/// This method is crucial for managing the lifecycle and communication of the
/// supervised isolate, handling startup, shutdown, errors, and inter-isolate messaging.
void listener(dynamic message) { void listener(dynamic message) {
if (message is SendPort) { if (message is SendPort) {
_serverSendPort = message; _serverSendPort = message;
@ -122,12 +316,38 @@ class ApplicationIsolateSupervisor {
} }
} }
/// Sends all pending messages stored in the [_pendingMessageQueue] to other supervisors.
///
/// This method is typically called when the supervising application starts running
/// to process any messages that were received while the application was not active.
/// It performs the following steps:
///
/// 1. Creates a copy of the [_pendingMessageQueue] to safely iterate over it.
/// 2. Clears the original [_pendingMessageQueue].
/// 3. Sends each message in the copied list to other supervisors using [_sendMessageToOtherSupervisors].
///
/// This ensures that no messages are lost during the startup phase of the application
/// and maintains message integrity across isolates.
void sendPendingMessages() { void sendPendingMessages() {
final list = List<MessageHubMessage>.from(_pendingMessageQueue); final list = List<MessageHubMessage>.from(_pendingMessageQueue);
_pendingMessageQueue.clear(); _pendingMessageQueue.clear();
list.forEach(_sendMessageToOtherSupervisors); list.forEach(_sendMessageToOtherSupervisors);
} }
/// Sends a [MessageHubMessage] to all other supervisors managed by the supervising application.
///
/// This method is responsible for propagating messages across different isolates
/// managed by the same application. It performs the following actions:
///
/// 1. Iterates through all supervisors in the supervising application.
/// 2. Excludes the current supervisor from the recipients.
/// 3. Sends the provided [message] to each of the other supervisors' isolates.
///
/// This method is crucial for maintaining communication and synchronization
/// between different isolates within the application.
///
/// Parameters:
/// - [message]: The [MessageHubMessage] to be sent to other supervisors.
void _sendMessageToOtherSupervisors(MessageHubMessage message) { void _sendMessageToOtherSupervisors(MessageHubMessage message) {
supervisingApplication.supervisors supervisingApplication.supervisors
.where((sup) => sup != this) .where((sup) => sup != this)
@ -136,6 +356,25 @@ class ApplicationIsolateSupervisor {
}); });
} }
/// Handles exceptions thrown by the supervised isolate.
///
/// This method is responsible for processing and responding to exceptions
/// that occur within the supervised isolate. It behaves differently depending
/// on whether the isolate is still in the process of launching or not:
///
/// - If the isolate is launching ([_isLaunching] is true):
/// It wraps the error in an [ApplicationStartupException] and completes
/// the [_launchCompleter] with this error, effectively failing the launch process.
///
/// - If the isolate has already launched:
/// It logs the error as a severe uncaught exception using the supervisor's logger.
///
/// Parameters:
/// - [error]: The error or exception object thrown by the isolate.
/// - [stacktrace]: The [StackTrace] associated with the error.
///
/// This method is crucial for maintaining the stability and error handling
/// of the isolate, especially during its startup phase.
void _handleIsolateException(dynamic error, StackTrace stacktrace) { void _handleIsolateException(dynamic error, StackTrace stacktrace) {
if (_isLaunching) { if (_isLaunching) {
final appException = ApplicationStartupException(error); final appException = ApplicationStartupException(error);

View file

@ -1,3 +1,12 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:io'; import 'dart:io';
import 'package:args/args.dart'; import 'package:args/args.dart';
@ -5,45 +14,73 @@ import 'package:protevus_application/application.dart';
/// An object that contains configuration values for an [Application]. /// An object that contains configuration values for an [Application].
/// ///
/// You use this object in an [ApplicationChannel] to manage external configuration data for your application. /// This class provides a set of options that can be used to configure an application,
/// including network settings, SSL configuration, and custom context values.
/// It also includes a static [ArgParser] for parsing command-line arguments.
///
/// Key features:
/// - Configurable address and port for HTTP requests
/// - IPv6 support
/// - SSL/HTTPS configuration options
/// - Client-side certificate usage flag
/// - Custom context for application-specific configuration
/// - Command-line argument parsing for easy configuration
///
/// Usage:
/// This class is typically used in conjunction with [ApplicationChannel] to set up
/// and configure an application based on external inputs or command-line arguments.
class ApplicationOptions { class ApplicationOptions {
/// The absolute path of the configuration file for this application. /// The absolute path of the configuration file for this application.
/// ///
/// This path is provided when an application is started by the `--config-path` option to `conduit serve`. /// This property stores the file path of the configuration file used by the application.
/// You may load the file at this path in [ApplicationChannel] to use configuration values. /// The path is typically set when the application is started using the `--config-path` option
/// with the `conduit serve` command.
///
/// The configuration file can contain application-specific settings and can be loaded
/// in the [ApplicationChannel] to access these configuration values.
///
/// This property may be null if no configuration file path was specified when starting the application.
///
/// Usage:
/// - Access this property to get the path of the configuration file.
/// - Use the path to load and parse the configuration file in your application logic.
/// - Ensure to handle cases where this property might be null.
String? configurationFilePath; String? configurationFilePath;
/// The address to listen for HTTP requests on. /// The address to listen for HTTP requests on.
/// ///
/// By default, this address will default to 'any' address (0.0.0.0). If [isIpv6Only] is true, /// This property specifies the network address on which the application will listen for incoming HTTP requests.
/// 'any' will be any IPv6 address, otherwise, it will be any IPv4 or IPv6 address.
/// ///
/// This value may be an [InternetAddress] or a [String]. /// This value may be an [InternetAddress] or a [String].
dynamic address; dynamic address;
/// The port to listen for HTTP requests on. /// The port number on which the application will listen for HTTP requests.
/// ///
/// Defaults to 8888. /// Defaults to 8888.
int port = 8888; int port = 8888;
/// Whether or not the application should only receive connections over IPv6. /// Whether or not the application should only receive connections over IPv6.
/// ///
/// This flag determines if the application should exclusively use IPv6 for incoming connections.
/// When set to true, the application will only accept IPv6 connections and reject IPv4 connections.
/// This setting can be useful in environments that require IPv6-only communication.
///
/// Defaults to false. This flag impacts the default value of the [address] property. /// Defaults to false. This flag impacts the default value of the [address] property.
bool isIpv6Only = false; bool isIpv6Only = false;
/// Whether or not the application's request controllers should use client-side HTTPS certificates. /// Indicates whether the application's request controllers should use client-side HTTPS certificates.
/// ///
/// Defaults to false. /// Defaults to false.
bool isUsingClientCertificate = false; bool isUsingClientCertificate = false;
/// The path to a SSL certificate. /// The path to a SSL certificate file.
/// ///
/// If specified - along with [privateKeyFilePath] - an [Application] will only allow secure connections over HTTPS. /// If specified - along with [privateKeyFilePath] - an [Application] will only allow secure connections over HTTPS.
/// This value is often set through the `--ssl-certificate-path` command line option of `conduit serve`. For finer control /// This value is often set through the `--ssl-certificate-path` command line option of `conduit serve`. For finer control
/// over how HTTPS is configured for an application, see [ApplicationChannel.securityContext]. /// over how HTTPS is configured for an application, see [ApplicationChannel.securityContext].
String? certificateFilePath; String? certificateFilePath;
/// The path to a private key. /// The path to a private key file for SSL/TLS encryption.
/// ///
/// If specified - along with [certificateFilePath] - an [Application] will only allow secure connections over HTTPS. /// If specified - along with [certificateFilePath] - an [Application] will only allow secure connections over HTTPS.
/// This value is often set through the `--ssl-key-path` command line option of `conduit serve`. For finer control /// This value is often set through the `--ssl-key-path` command line option of `conduit serve`. For finer control
@ -54,8 +91,40 @@ class ApplicationOptions {
/// ///
/// This is a user-specific set of configuration options provided by [ApplicationChannel.initializeApplication]. /// This is a user-specific set of configuration options provided by [ApplicationChannel.initializeApplication].
/// Each instance of [ApplicationChannel] has access to these values if set. /// Each instance of [ApplicationChannel] has access to these values if set.
///
/// This map allows for storing and retrieving custom configuration values that can be used
/// throughout the application. It provides a flexible way to pass application-specific
/// settings to different parts of the system.
///
/// The context can be populated during the application's initialization phase and
/// can contain any type of data that adheres to the dynamic type.
///
/// Usage:
/// - Add configuration values: `context['databaseUrl'] = 'postgres://...';`
/// - Retrieve values: `final dbUrl = options.context['databaseUrl'];`
///
/// Note: It's important to ensure type safety when retrieving values from this map,
/// as it uses dynamic typing.
final Map<String, dynamic> context = {}; final Map<String, dynamic> context = {};
/// A static [ArgParser] for parsing command-line arguments for the application.
///
/// This parser defines several options and flags that can be used to configure
/// the application when it is launched from the command line. The available
/// options include:
///
/// - address: The address to listen on for HTTP requests.
/// - config-path: The path to a configuration file.
/// - isolates: Number of isolates for handling requests.
/// - port: The port number to listen for HTTP requests on.
/// - ipv6-only: Flag to limit listening to IPv6 connections only.
/// - ssl-certificate-path: The path to an SSL certificate file.
/// - ssl-key-path: The path to an SSL private key file.
/// - timeout: Number of seconds to wait to ensure startup succeeded.
/// - help: Flag to display help information.
///
/// Each option is configured with a description, and some include default values
/// or abbreviations for easier command-line usage.
static final parser = ArgParser() static final parser = ArgParser()
..addOption( ..addOption(
"address", "address",

View file

@ -1,3 +1,12 @@
/*
* This file is part of the Protevus Platform.
*
* (C) Protevus <developers@protevus.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import 'dart:async'; import 'dart:async';
import 'dart:isolate'; import 'dart:isolate';
@ -7,6 +16,21 @@ import 'package:protevus_application/application.dart';
Warning: do not remove. This method is invoked by a generated script. Warning: do not remove. This method is invoked by a generated script.
*/ */
/// Starts the application either on the current isolate or across multiple isolates.
///
/// This function initializes and starts the application, setting up communication
/// between isolates using ports. It responds to stop commands and reports the
/// application's status back to the parent isolate.
///
/// Parameters:
/// - app: The Application instance to be started.
/// - isolateCount: The number of isolates to start the application on. If 0, starts on the current isolate.
/// - parentPort: The SendPort of the parent isolate for communication.
///
/// The function sets up a ReceivePort to listen for commands, particularly the "stop" command.
/// It then starts the application either on the current isolate or across multiple isolates
/// based on the isolateCount parameter. Finally, it sends a status message back to the parent isolate.
Future startApplication<T extends ApplicationChannel>( Future startApplication<T extends ApplicationChannel>(
Application<T> app, Application<T> app,
int isolateCount, int isolateCount,