PluginManager's async-only APIs force synchronous hosts to bridge into async with throwaway runtimes / block_in_place. Two related sync entry points would remove those workarounds.
1. Synchronous initialize
PluginManager::initialize is async-only (cpex-core/src/manager.rs). In Praxis, the filter factory is sync (fn from_config(...) -> Result<Box<dyn HttpFilter>, _>), so to drive init we spawn a dedicated OS thread, build a throwaway current-thread tokio runtime, and block_on(initialize()) — block_on on the current thread panics when a runtime is already attached (e.g. under #[tokio::test]).
A blocking variant — e.g. PluginManager::initialize_blocking(), or a builder that initializes synchronously — would let sync hosts initialize without the OS-thread + throwaway-runtime workaround.
2. Synchronous hook dispatch
invoke_named / the hook-dispatch APIs are async too. Praxis's response-body filter callback is sync (Pingora's response_body_filter is not async), so the policy filter drives the CMF post-invoke hooks with tokio::task::block_in_place(|| Handle::current().block_on(...)). That requires a multi-threaded tokio runtime and permanently rejects single-threaded (work_stealing: false) deployments for the feature.
A sync hook-dispatch entry point — e.g. invoke_named_blocking() — would let sync host callbacks run hooks without block_in_place and without dictating the host's runtime flavor. For hooks that perform async I/O, cpex would own how that blocking is driven internally rather than pushing the multi-threaded-runtime requirement onto every host.
Context: both raised in a Praxis review of the in-process policy filter (the second is the sync response-body phase vs async hooks comment).
PluginManager's async-only APIs force synchronous hosts to bridge into async with throwaway runtimes /block_in_place. Two related sync entry points would remove those workarounds.1. Synchronous
initializePluginManager::initializeis async-only (cpex-core/src/manager.rs). In Praxis, the filter factory is sync (fn from_config(...) -> Result<Box<dyn HttpFilter>, _>), so to drive init we spawn a dedicated OS thread, build a throwaway current-thread tokio runtime, andblock_on(initialize())—block_onon the current thread panics when a runtime is already attached (e.g. under#[tokio::test]).A blocking variant — e.g.
PluginManager::initialize_blocking(), or a builder that initializes synchronously — would let sync hosts initialize without the OS-thread + throwaway-runtime workaround.2. Synchronous hook dispatch
invoke_named/ the hook-dispatch APIs are async too. Praxis's response-body filter callback is sync (Pingora'sresponse_body_filteris not async), so the policy filter drives the CMF post-invoke hooks withtokio::task::block_in_place(|| Handle::current().block_on(...)). That requires a multi-threaded tokio runtime and permanently rejects single-threaded (work_stealing: false) deployments for the feature.A sync hook-dispatch entry point — e.g.
invoke_named_blocking()— would let sync host callbacks run hooks withoutblock_in_placeand without dictating the host's runtime flavor. For hooks that perform async I/O, cpex would own how that blocking is driven internally rather than pushing the multi-threaded-runtime requirement onto every host.Context: both raised in a Praxis review of the in-process policy filter (the second is the sync response-body phase vs async hooks comment).