diff --git a/packages/app/lib/application.dart b/packages/app/lib/application.dart index 15b974a..10fbea6 100644 --- a/packages/app/lib/application.dart +++ b/packages/app/lib/application.dart @@ -7,6 +7,14 @@ * 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; export 'src/application.dart'; diff --git a/packages/app/lib/src/application.dart b/packages/app/lib/src/application.dart index 20737b1..01db341 100644 --- a/packages/app/lib/src/application.dart +++ b/packages/app/lib/src/application.dart @@ -1,3 +1,12 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:async'; import 'dart:io'; import 'dart:isolate'; @@ -11,56 +20,138 @@ export 'application_server.dart'; export 'options.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]. /// It is unlikely that you need to use this class directly - the `conduit serve` command creates an application object /// on your behalf. class Application { /// 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 supervisors = []; /// The [ApplicationServer] listening for HTTP requests while under test. /// /// 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; /// The [ApplicationChannel] handling requests while under test. /// - /// This property is only valid when an application is started via [startOnCurrentIsolate]. You use - /// this value to access elements of your application channel during testing. + /// This property provides access to the application channel instance when the application + /// 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(); + /// await app.startOnCurrentIsolate(); + /// final myChannel = app.channel; + /// // Now you can interact with myChannel for testing purposes + /// ``` T get channel => server.channel as T; /// The logger that this application will write messages to. /// - /// This logger's name will appear as 'conduit'. - Logger logger = Logger("conduit"); + /// This logger is used throughout the application to record messages, errors, + /// 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. /// - /// 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(); + /// 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(); /// 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. 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 value will return to false after [stop] has completed. 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; + + /// 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; /// Starts this application, allowing it to handle HTTP requests. /// - /// This method spawns [numberOfInstances] isolates, instantiates your application channel - /// for each of these isolates, and opens an HTTP listener that sends requests to these instances. + /// This method initializes the application by spawning a specified number of isolates, + /// 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 /// and are available to handle requests. @@ -110,7 +201,7 @@ class Application { _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. /// Performance is limited when running the application with this method; prefer to use [start]. @@ -139,8 +230,20 @@ class Application { /// Stops the application from running. /// - /// Closes every isolate and their channel and stops listening for HTTP requests. - /// The [ServiceRegistry] will close any of its resources. + /// This method performs the following actions: + /// 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 { _hasFinishedLaunching = false; await Future.wait(supervisors.map((s) => s.stop())) @@ -167,6 +270,26 @@ class Application { /// 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. static Future document( Type type, @@ -188,6 +311,25 @@ class Application { 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 _spawn( Application application, ApplicationOptions config, @@ -224,7 +366,7 @@ class Application { } } -/// 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. class ApplicationStartupException implements Exception { diff --git a/packages/app/lib/src/application_server.dart b/packages/app/lib/src/application_server.dart index ed6279d..f26effb 100644 --- a/packages/app/lib/src/application_server.dart +++ b/packages/app/lib/src/application_server.dart @@ -1,17 +1,38 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:async'; import 'dart:io'; +import 'package:logging/logging.dart'; import 'package:protevus_application/application.dart'; import 'package:protevus_http/http.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 -/// instance of an [ApplicationChannel] subclass. Instances are created by [Application] -/// and shouldn't be created otherwise. +/// The ApplicationServer class is responsible for managing the lifecycle of an HTTP server +/// and its associated [ApplicationChannel]. It handles server creation, starting, and stopping, +/// 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 { - /// Creates a new server. + /// Creates a new server instance. /// /// You should not need to invoke this method directly. ApplicationServer(this.channelType, this.options, this.identifier) { @@ -21,41 +42,145 @@ class ApplicationServer { ..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; - /// 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; /// 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; /// 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; + /// 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; /// 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? 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; + + /// 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; /// The unique identifier of this instance. /// /// Each instance has its own identifier, a numeric value starting at 1, to identify it /// 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; - /// The logger of this instance - Logger get logger => Logger("conduit"); + /// Returns the logger instance for this ApplicationServer. + /// + /// 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. /// - /// 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 { logger.fine("ApplicationServer($identifier).start entry"); @@ -92,7 +217,21 @@ class ApplicationServer { 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 { logger.fine("ApplicationServer($identifier).close Closing HTTP listener"); await server.close(force: true); @@ -104,7 +243,7 @@ class ApplicationServer { 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. Future didOpen() async { @@ -117,6 +256,18 @@ class ApplicationServer { 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) { // By default, do nothing } diff --git a/packages/app/lib/src/channel.dart b/packages/app/lib/src/channel.dart index 8f120f8..7707eb6 100644 --- a/packages/app/lib/src/channel.dart +++ b/packages/app/lib/src/channel.dart @@ -1,3 +1,12 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:async'; import 'dart:io'; @@ -9,7 +18,7 @@ import 'package:protevus_runtime/runtime.dart'; import 'package:logging/logging.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. /// 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 /// is a replica of your application that runs in its own memory isolated thread. 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 /// 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. 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'. - 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; + /// 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) { _server = server; messageHub._outboundController.stream.listen(server.sendApplicationEvent); 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 /// through this object will be received by every other channel in your application (except the one that sent it). 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. /// @@ -93,6 +118,17 @@ abstract class ApplicationChannel implements APIComponentDocumenter { /// /// These options are set when starting the application. Changes to this object have no effect /// 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; /// 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; + /// 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; - /// 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 /// 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. 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. 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 /// 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. /// - /// 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 /// [documentComponents] methods invoked. Any declared property @@ -181,6 +226,24 @@ abstract class ApplicationChannel implements APIComponentDocumenter { 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 @override 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 /// is received by every other channel through its hub. @@ -218,14 +281,44 @@ abstract class ApplicationChannel implements APIComponentDocumenter { /// } /// }); class ApplicationMessageHub extends Stream implements Sink { - 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 _outboundController = StreamController(); + + /// 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 _inboundController = StreamController.broadcast(); /// 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. /// When another hub [add]s a message, this hub will receive it on [onData]. /// @@ -249,7 +342,8 @@ class ApplicationMessageHub extends Stream implements Sink { /// 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] /// is not isolate-safe data, an error is delivered to [listen] on this isolate. @@ -258,6 +352,19 @@ class ApplicationMessageHub extends Stream implements Sink { _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 Future close() async { if (!_outboundController.hasListener) { @@ -273,6 +380,10 @@ class ApplicationMessageHub extends Stream implements Sink { } } +/// 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 { Iterable getDocumentableChannelComponents( ApplicationChannel channel, diff --git a/packages/app/lib/src/isolate_application_server.dart b/packages/app/lib/src/isolate_application_server.dart index 2972d24..9ce7b2a 100644 --- a/packages/app/lib/src/isolate_application_server.dart +++ b/packages/app/lib/src/isolate_application_server.dart @@ -1,10 +1,47 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:async'; import 'dart:isolate'; - -import 'package:protevus_application/application.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 { + /// 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( Type channelType, ApplicationOptions configuration, @@ -26,9 +63,40 @@ class ApplicationIsolateServer extends ApplicationServer { 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; + + /// 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; + /// 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 Future start({bool shareHttpServer = false}) async { final result = await super.start(shareHttpServer: shareHttpServer); @@ -41,6 +109,21 @@ class ApplicationIsolateServer extends ApplicationServer { 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 void sendApplicationEvent(dynamic event) { 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) { if (message == ApplicationIsolateSupervisor.messageKeyStop) { 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 { supervisingReceivePort.close(); 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( 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 { ApplicationInitialServerMessage( this.streamTypeName, @@ -94,6 +238,16 @@ class ApplicationInitialServerMessage { 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 { MessageHubMessage(this.payload); diff --git a/packages/app/lib/src/isolate_supervisor.dart b/packages/app/lib/src/isolate_supervisor.dart index 27a0343..198d585 100644 --- a/packages/app/lib/src/isolate_supervisor.dart +++ b/packages/app/lib/src/isolate_supervisor.dart @@ -1,3 +1,12 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:async'; import 'dart:isolate'; @@ -6,9 +15,26 @@ import 'package:logging/logging.dart'; /// 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. 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( this.supervisingApplication, this.isolate, @@ -18,34 +44,168 @@ class ApplicationIsolateSupervisor { 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; - /// 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; /// 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; + /// 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; - /// 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; - /// 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; + /// 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 _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; + + /// 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; + + /// 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; + + /// 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; + /// 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"; + + /// 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"; /// 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() { _launchCompleter = Completer(); receivePort.listen(listener); @@ -71,6 +231,24 @@ class ApplicationIsolateSupervisor { } /// 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 { _stopCompleter = Completer(); logger.fine( @@ -91,6 +269,22 @@ class ApplicationIsolateSupervisor { 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) { if (message is SendPort) { _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() { final list = List.from(_pendingMessageQueue); _pendingMessageQueue.clear(); 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) { supervisingApplication.supervisors .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) { if (_isLaunching) { final appException = ApplicationStartupException(error); diff --git a/packages/app/lib/src/options.dart b/packages/app/lib/src/options.dart index 6520e25..385a11b 100644 --- a/packages/app/lib/src/options.dart +++ b/packages/app/lib/src/options.dart @@ -1,3 +1,12 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:io'; import 'package:args/args.dart'; @@ -5,45 +14,73 @@ import 'package:protevus_application/application.dart'; /// 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 { /// 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`. - /// You may load the file at this path in [ApplicationChannel] to use configuration values. + /// This property stores the file path of the configuration file used by the application. + /// 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; /// 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, - /// 'any' will be any IPv6 address, otherwise, it will be any IPv4 or IPv6 address. + /// This property specifies the network address on which the application will listen for incoming HTTP requests. /// /// This value may be an [InternetAddress] or a [String]. 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. int port = 8888; /// 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. 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. 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. /// 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]. 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. /// 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]. /// 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 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() ..addOption( "address", diff --git a/packages/app/lib/src/starter.dart b/packages/app/lib/src/starter.dart index 6cb23df..89d6cba 100644 --- a/packages/app/lib/src/starter.dart +++ b/packages/app/lib/src/starter.dart @@ -1,3 +1,12 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + import 'dart:async'; 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. */ + +/// 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( Application app, int isolateCount,