Trait aws_smithy_http_server_python::PyApp

source ·
pub trait PyApp: Clone + IntoPy<PyObject> {
Show 14 methods // Required methods fn workers(&self) -> &Mutex<Vec<PyObject>>; fn context(&self) -> &Option<PyObject>; fn handlers(&mut self) -> &mut HashMap<String, PyHandler>; fn build_service( &mut self, event_loop: &PyAny, ) -> PyResult<BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>>; // Provided methods fn graceful_termination(&self, workers: &Mutex<Vec<PyObject>>) -> ! { ... } fn immediate_termination(&self, workers: &Mutex<Vec<PyObject>>) -> ! { ... } fn block_on_rust_signals(&self) { ... } fn register_python_signals( &self, py: Python<'_>, event_loop: PyObject, ) -> PyResult<()> { ... } fn start_hyper_worker( &mut self, py: Python<'_>, socket: &PyCell<PySocket>, event_loop: &PyAny, service: BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>, worker_number: isize, tls: Option<PyTlsConfig>, ) -> PyResult<()> { ... } fn register_operation( &mut self, py: Python<'_>, name: &str, func: PyObject, ) -> PyResult<()> { ... } fn configure_python_event_loop<'py>( &self, py: Python<'py>, ) -> PyResult<&'py PyAny> { ... } fn run_server( &mut self, py: Python<'_>, address: Option<String>, port: Option<i32>, backlog: Option<i32>, workers: Option<usize>, tls: Option<PyTlsConfig>, ) -> PyResult<()> { ... } fn run_lambda_handler(&mut self, py: Python<'_>) -> PyResult<()> { ... } fn build_and_configure_service( &mut self, py: Python<'_>, event_loop: &PyAny, ) -> PyResult<BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>> { ... }
}
Expand description

Trait defining a Python application.

A Python application requires handling of multiple processes, signals and allows to register Python function that will be executed as business logic by the code generated Rust handlers. To properly function, the application requires some state:

  • workers: the list of child Python worker processes, protected by a Mutex.
  • context: the optional Python object that should be passed inside the Rust state struct.
  • handlers: the mapping between an operation name and its PyHandler representation.

Since the Python application is spawning multiple workers, it also requires signal handling to allow the gracefull termination of multiple Hyper servers. The main Rust process is registering signal and using them to understand when it it time to loop through all the active workers and terminate them. Workers registers their own signal handlers and attaches them to the Python event loop, ensuring all coroutines are cancelled before terminating a worker.

This trait will be implemented by the code generated by the PythonApplicationGenerator Kotlin class.

Required Methods§

source

fn workers(&self) -> &Mutex<Vec<PyObject>>

List of active Python workers registered with this application.

source

fn context(&self) -> &Option<PyObject>

Optional Python context object that will be passed as part of the Rust state.

source

fn handlers(&mut self) -> &mut HashMap<String, PyHandler>

Mapping between operation names and their PyHandler representation.

source

fn build_service( &mut self, event_loop: &PyAny, ) -> PyResult<BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>>

Build the app’s Service using given event_loop.

Provided Methods§

source

fn graceful_termination(&self, workers: &Mutex<Vec<PyObject>>) -> !

Handle the graceful termination of Python workers by looping through all the active workers and calling terminate() on them. If termination fails, this method will try to kill() any failed worker.

source

fn immediate_termination(&self, workers: &Mutex<Vec<PyObject>>) -> !

Handler the immediate termination of Python workers by looping through all the active workers and calling kill() on them.

source

fn block_on_rust_signals(&self)

Register and handler signals of the main Rust thread. Signals not registered in this method are ignored.

Signals supported:

  • SIGTERM|SIGQUIT - graceful termination of all workers.
  • SIGINT - immediate termination of all workers.

Other signals are NOOP.

source

fn register_python_signals( &self, py: Python<'_>, event_loop: PyObject, ) -> PyResult<()>

Register and handle termination of all the tasks on the Python asynchronous event loop. We only register SIGQUIT and SIGINT since the main signal handling is done by Rust.

source

fn start_hyper_worker( &mut self, py: Python<'_>, socket: &PyCell<PySocket>, event_loop: &PyAny, service: BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>, worker_number: isize, tls: Option<PyTlsConfig>, ) -> PyResult<()>

Start a single worker with its own Tokio and Python async runtime and provided shared socket.

Python asynchronous loop needs to be started and handled during the lifetime of the process and it is passed to this method by the caller, which can use configure_python_event_loop to properly setup it up.

We retrieve the Python context object, if setup by the user calling PyApp::context method, generate the state structure and build the [aws_smithy_http_server::routing::Router], filling it with the functions generated by PythonServerOperationHandlerGenerator.kt. At last we get a cloned reference to the underlying [socket2::Socket].

Now that all the setup is done, we can start the two runtimes and run the [hyper] server. We spawn a thread with a new [tokio::runtime], setup the middlewares and finally block the thread on Hyper serve() method. The main process continues and at the end it is blocked on Python loop.run_forever().

source

fn register_operation( &mut self, py: Python<'_>, name: &str, func: PyObject, ) -> PyResult<()>

Register a Python function to be executed inside the Smithy Rust handler.

There are some information needed to execute the Python code from a Rust handler, such has if the registered function needs to be awaited (if it is a coroutine) and the number of arguments available, which tells us if the handler wants the state to be passed or not.

source

fn configure_python_event_loop<'py>( &self, py: Python<'py>, ) -> PyResult<&'py PyAny>

Configure the Python asyncio event loop.

First of all we install uvloop as the main Python event loop. Thanks to libuv, uvloop performs ~20% better than Python standard event loop in most benchmarks, while being 100% compatible. If uvloop is not available as a dependency, we just fall back to the standard Python event loop.

source

fn run_server( &mut self, py: Python<'_>, address: Option<String>, port: Option<i32>, backlog: Option<i32>, workers: Option<usize>, tls: Option<PyTlsConfig>, ) -> PyResult<()>

Main entrypoint: start the server on multiple workers.

The multiprocessing server is achieved using the ability of a Python interpreter to clone and start itself as a new process. The shared sockets is created and Using the multiprocessing::Process module, multiple workers with the method self.start_worker() as target are started.

NOTE: this method ends up calling self.start_worker from the Python context, forcing the struct implementing this trait to also implement a start_worker method. This is done to ensure the Python event loop is started in the right child process space before being passed to start_hyper_worker.

PythonApplicationGenerator.kt generates the start_worker method:

    use std::convert::Infallible;
    use std::collections::HashMap;
    use pyo3::prelude::*;
    use aws_smithy_http_server_python::{PyApp, PyHandler};
    use aws_smithy_http_server::body::{Body, BoxBody};
    use parking_lot::Mutex;
    use http::{Request, Response};
    use tower::util::BoxCloneService;

    #[pyclass]
    #[derive(Debug, Clone)]
    pub struct App {};

    impl PyApp for App {
        fn workers(&self) -> &Mutex<Vec<PyObject>> { todo!() }
        fn context(&self) -> &Option<PyObject> { todo!() }
        fn handlers(&mut self) -> &mut HashMap<String, PyHandler> { todo!() }
        fn build_service(&mut self, event_loop: &PyAny) -> PyResult<BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>> { todo!() }
    }

    #[pymethods]
    impl App {
    #[pyo3(text_signature = "($self, socket, worker_number, tls)")]
        pub fn start_worker(
            &mut self,
            py: pyo3::Python,
            socket: &pyo3::PyCell<aws_smithy_http_server_python::PySocket>,
            worker_number: isize,
            tls: Option<aws_smithy_http_server_python::tls::PyTlsConfig>,
        ) -> pyo3::PyResult<()> {
            let event_loop = self.configure_python_event_loop(py)?;
            let service = self.build_service(event_loop)?;
            self.start_hyper_worker(py, socket, event_loop, service, worker_number, tls)
        }
    }
source

fn run_lambda_handler(&mut self, py: Python<'_>) -> PyResult<()>

Lambda main entrypoint: start the handler on Lambda.

source

fn build_and_configure_service( &mut self, py: Python<'_>, event_loop: &PyAny, ) -> PyResult<BoxCloneService<Request<Body>, Response<BoxBody>, Infallible>>

Object Safety§

This trait is not object safe.

Implementors§