Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ name = "module_restart"
path = "tests/module_restart.rs"
required-features = ["embed", "closure"]

[[test]]
name = "module_startup_function"
path = "tests/module_startup_function.rs"
required-features = ["embed"]

[[test]]
name = "observer_restart"
path = "tests/observer_restart.rs"
Expand Down
9 changes: 7 additions & 2 deletions crates/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1773,8 +1773,13 @@ fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
/// the crate. The startup function of that entry logs the build error and fails
/// as described above.
///
/// The `startup` function that you name in `#[php_module(startup = ...)]` is
/// your own `extern "C"` function. A panic inside it is not caught.
/// The `startup` function that you name in `#[php(startup = ...)]` is your own
/// `extern "C"` function. A panic inside it is not caught.
///
/// You can also give a startup function to `ModuleBuilder::startup_function`.
/// The generated startup function calls it after the `#[php(startup = ...)]`
/// function. The two functions run before the macro registers the constants,
/// interfaces, classes and enums.
///
/// ## Usage
///
Expand Down
18 changes: 14 additions & 4 deletions crates/macros/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,15 @@ fn parser_impl(input: ItemFn, crate_name: Option<&str>, static_ext: bool) -> Res
::ext_php_rs::zend::StaticModuleEntry::new();
static __EXT_PHP_RS_MODULE_STARTUP: ::ext_php_rs::internal::ModuleStartupMutex =
::ext_php_rs::internal::MODULE_STARTUP_INIT;
static __EXT_PHP_RS_BUILDER_STARTUP: ::std::sync::OnceLock<
unsafe extern "C" fn(i32, i32) -> i32,
> = ::std::sync::OnceLock::new();

extern "C" fn ext_php_rs_startup(ty: i32, mod_num: i32) -> i32 {
let a = unsafe { #startup };
let user = __EXT_PHP_RS_BUILDER_STARTUP
.get()
.map_or(0, |startup| unsafe { startup(ty, mod_num) });
let b = ::ext_php_rs::internal::startup_guard(|| {
// The startup is kept, not taken: a SAPI can shut the module down and
// start it again in the same process (FrankenPHP worker restarts), and
Expand All @@ -90,7 +96,7 @@ fn parser_impl(input: ItemFn, crate_name: Option<&str>, static_ext: bool) -> Res
None => Ok(()),
}
});
a | b
a | user | b
}

static __EXT_PHP_RS_BUILD_ERROR: ::std::sync::OnceLock<::std::string::String> =
Expand All @@ -111,11 +117,15 @@ fn parser_impl(input: ItemFn, crate_name: Option<&str>, static_ext: bool) -> Res
let builder = internal(::ext_php_rs::builders::ModuleBuilder::new(
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
))
.startup_function(ext_php_rs_startup);
));

match builder.try_into() {
Ok((entry, startup, owned)) => {
Ok((mut entry, startup, owned)) => {
if let Some(user_startup) =
entry.module_startup_func.replace(ext_php_rs_startup)
{
let _ = __EXT_PHP_RS_BUILDER_STARTUP.set(user_startup);
}
__EXT_PHP_RS_MODULE_STARTUP.lock().replace(startup);
(entry, owned)
},
Expand Down
9 changes: 7 additions & 2 deletions guide/src/macros/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ NUL byte, the macro returns a placeholder entry with the name of the crate. The
startup function of that entry logs the build error and fails as described
above.

The `startup` function that you name in `#[php_module(startup = ...)]` is your
own `extern "C"` function. A panic inside it is not caught.
The `startup` function that you name in `#[php(startup = ...)]` is your own
`extern "C"` function. A panic inside it is not caught.

You can also give a startup function to `ModuleBuilder::startup_function`. The
generated startup function calls it after the `#[php(startup = ...)]` function.
The two functions run before the macro registers the constants, interfaces,
classes and enums.

## Usage

Expand Down
71 changes: 71 additions & 0 deletions tests/module_startup_function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! A startup function set with `ModuleBuilder::startup_function` inside
//! `#[php_module]` runs at MINIT, next to the registration the macro does.
#![cfg_attr(windows, feature(abi_vectorcall))]
#![cfg(feature = "embed")]
#![allow(missing_docs, clippy::must_use_candidate)]
extern crate ext_php_rs;

use std::sync::atomic::{AtomicU32, Ordering};

use ext_php_rs::builders::SapiBuilder;
use ext_php_rs::embed::{
Embed, cleanup_sapi_allocations, ext_php_rs_sapi_shutdown, ext_php_rs_sapi_startup,
};
use ext_php_rs::ffi::{
ZEND_RESULT_CODE_SUCCESS, php_module_shutdown, php_module_startup, php_request_shutdown,
php_request_startup, sapi_shutdown, sapi_startup,
};
use ext_php_rs::prelude::*;
use ext_php_rs::zend::try_catch_first;

static BUILDER_STARTUPS: AtomicU32 = AtomicU32::new(0);

extern "C" fn builder_startup(_ty: i32, _module_number: i32) -> i32 {
BUILDER_STARTUPS.fetch_add(1, Ordering::SeqCst);
0
}

#[php_function]
pub fn startup_answer() -> i64 {
42
}

#[php_module]
pub fn module(module: ModuleBuilder) -> ModuleBuilder {
module
.startup_function(builder_startup)
.function(wrap_function!(startup_answer))
}

#[test]
fn test_builder_startup_function_runs_at_minit() {
let mut sapi = SapiBuilder::new("startup", "Startup").build().unwrap();
let sapi = &raw mut sapi;

unsafe {
ext_php_rs_sapi_startup();
sapi_startup(sapi);
assert_eq!(
php_module_startup(sapi, get_module()),
ZEND_RESULT_CODE_SUCCESS
);
}

assert_eq!(BUILDER_STARTUPS.load(Ordering::SeqCst), 1);

assert_eq!(unsafe { php_request_startup() }, ZEND_RESULT_CODE_SUCCESS);

try_catch_first(|| {
let answer = Embed::eval("startup_answer();").expect("startup_answer() failed");
assert_eq!(answer.long(), Some(42));
})
.expect("PHP bailed out");

unsafe {
php_request_shutdown(std::ptr::null_mut());
php_module_shutdown();
sapi_shutdown();
ext_php_rs_sapi_shutdown();
cleanup_sapi_allocations(sapi);
}
}
Loading