=encoding utf8

=head1 NAME

PAGI::Spec::Lifespan - PAGI Specification Documentation

=head1 NOTICE

This documentation is auto-generated from the PAGI specification
markdown files. For the authoritative source, see:

L<https://github.com/jjn1056/PAGI/tree/main/docs/specs>

=head1 Lifespan Protocol

B<Version>: 0.2 (Draft)

The Lifespan B<PAGI> sub-specification outlines how to communicate lifespan events such as startup and shutdown within PAGI.

The lifespan messages allow for an application to initialise and shutdown in the context of a running event loop. An example of this would be creating a connection pool and subsequently closing the connection pool to release the connections.

=head2 Execution Model

Lifespan events B<must be sent by every PAGI server instance or worker> that manages an application event loop. This ensures that application-level resources are initialized and cleaned up in the context in which they run.

=over 4

=item *

In B<multi-process deployments>, each worker process must independently send C<lifespan.startup> on init and C<lifespan.shutdown> on exit.

=item *

In B<in-process async mode>, the main event loop will send the events.

=back

This matches the ASGI model and ensures that applications have the opportunity to manage state correctly in async-safe scopes.

Lifespans and requests B<must occur on the same event loop>. This guarantees that async-local state (such as DB pools or background tasks) is not shared unsafely across threads or processes.

A possible implementation of this protocol in B<modern Perl> using L<Future::AsyncAwait> could look like this:

    use strict;
    use warnings;
    use Future::AsyncAwait;

    async sub app ($scope, $receive, $send) {
        # Only handle 'lifespan' protocol
        if ($scope->{type} eq 'lifespan') {

            while (1) {
                my $message = await $receive->();

                if ($message->{type} eq 'lifespan.startup') {
                    # ... Do some startup actions here ...
                    await $send->({
                        type => 'lifespan.startup.complete',
                    });
                }
                elsif ($message->{type} eq 'lifespan.shutdown') {
                    # ... Do some shutdown/cleanup actions here ...
                    await $send->({
                        type => 'lifespan.shutdown.complete',
                    });
                    return; # End the loop
                }
                else {
                    # Unknown or additional message type; you can handle it or ignore it
                }
            }

        }
        else {
            die "Unsupported protocol type: $scope->{type}";
        }
    }

=head2 Scope

The lifespan scope exists for the duration of the event loop.

The scope information passed in C<scope> contains basic metadata:

=over 4

=item *

C<type> (String) - C<"lifespan">.

=item *

C<pagi["version"]> (String) - The version of the PAGI core spec that governs the worker.

=item *

C<pagi["spec_version"]> (String) - The version of this lifespan sub-specification. Optional; if missing it defaults to C<"0.2">.

=item *

C<pagi["is_worker"]> (Int, optional) - C<1> if running as a worker in a multi-process deployment, C<0> or absent for single-process mode. Applications can use this to avoid duplicate initialization (e.g., only print startup messages from worker 1).

=item *

C<pagi["worker_num"]> (Int, optional) - Worker identifier (1, 2, 3, ...) in multi-process mode. Absent or C<undef> for single-process mode. Combined with C<is_worker>, applications can identify which worker they are running in.

=item *

C<state> (HashRef, optional) - A namespace where the application can persist values to be copied into subsequent request scopes. Servers omit this key if the feature is unsupported.

=back

If an exception is raised when calling the application coderef with a C<lifespan.startup> message (or when a C<lifespan> scope is created), the server must cease sending further lifespan events for that worker. The server should log the error and may either exit or continue starting without lifespan support, depending on operator configuration. Applications that wish to prevent startup entirely should explicitly send C<lifespan.startup.failed> with a message.

=head2 Lifespan State

Applications often want to persist data from the lifespan cycle to request/response handling. For example, a database connection can be established in the lifespan cycle and persisted to the request/response cycle.

The C<scope["state"]> namespace provides a place to store these sorts of things. The server will ensure that a I<shallow copy> of the namespace is passed into each subsequent request/response call into the application. Since the server manages the application lifespan and often the event loop as well this ensures that the application is always accessing the database connection (or other stored object) that corresponds to the right event loop and lifecycle, without using context variables, global mutable state or having to worry about references to stale/closed connections.

PAGI servers that implement this feature will provide C<state> as part of the C<lifespan> scope:

    "scope": {
        ...
        "state": {},
    }

The namespace is controlled completely by the PAGI application; the server will not interact with it other than to copy it. Nonetheless, applications should be cooperative by properly naming their keys such that they will not collide with other frameworks or middleware.

=head2 Startup - C<receive> event

Sent to the application when the server is ready to startup and receive connections, but before it has started to do so.

B<Keys>:

=over 4

=item *

C<type> (String) - C<"lifespan.startup">.

=back

=head2 Startup Complete - C<send> event

Sent by the application when it has completed its startup. A server must wait for this message before it starts processing connections.

B<Keys>:

=over 4

=item *

C<type> (String) - C<"lifespan.startup.complete">.

=back

=head2 Startup Failed - C<send> event

Sent by the application when it has failed to complete its startup. If a server sees this it should log/print the message provided and then exit.

B<Keys>:

=over 4

=item *

C<type> (String) - C<"lifespan.startup.failed">.

=item *

C<message> (String) - Optional; if missing defaults to C<"">.

=back

=head2 Shutdown - C<receive> event

Sent to the application when the server has stopped accepting connections and closed all active connections.

B<Keys>:

=over 4

=item *

C<type> (String) -  C<"lifespan.shutdown">.

=back

=head2 Shutdown Complete - C<send> event

Sent by the application when it has completed its cleanup. A server must wait for this message before terminating.

B<Keys>:

=over 4

=item *

C<type> (String) - C<"lifespan.shutdown.complete">.

=back

=head2 Shutdown Failed - C<send> event

Sent by the application when it has failed to complete its cleanup. If a server sees this it should log/print the message provided and then terminate.

B<Keys>:

=over 4

=item *

C<type> (String) - C<"lifespan.shutdown.failed">.

=item *

C<message> (String) - Optional; if missing defaults to C<"">.

=back

=head2 Version History

=over 4

=item *

0.2 (Draft): Added isI<worker and worker>num fields for multi-worker support

=item *

0.1 (Draft): Initial lifespan specification

=back

=head2 Copyright

This document has been placed in the public domain.

