99extern crate ext_php_rs;
1010
1111use ext_php_rs:: builders:: SapiBuilder ;
12- use ext_php_rs:: embed:: { Embed , ext_php_rs_sapi_startup} ;
12+ use ext_php_rs:: embed:: {
13+ Embed , ext_php_rs_sapi_per_thread_init, ext_php_rs_sapi_shutdown, ext_php_rs_sapi_startup,
14+ } ;
1315use ext_php_rs:: ffi:: {
1416 ZEND_RESULT_CODE_SUCCESS , php_module_shutdown, php_module_startup, php_request_shutdown,
1517 php_request_startup, sapi_shutdown, sapi_startup,
1618} ;
1719use ext_php_rs:: prelude:: * ;
1820use ext_php_rs:: zend:: try_catch_first;
1921use std:: ffi:: c_char;
22+ use std:: sync:: { Arc , Mutex } ;
23+ use std:: thread;
2024
2125static mut LAST_OUTPUT : String = String :: new ( ) ;
2226
27+ // Global mutex to ensure SAPI tests don't run concurrently. PHP does not allow
28+ // multiple SAPIs to exist at the same time. This prevents the tests from
29+ // overwriting each other's state.
30+ static SAPI_TEST_MUTEX : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
31+
2332extern "C" fn output_tester ( str : * const c_char , str_length : usize ) -> usize {
2433 let char = unsafe { std:: slice:: from_raw_parts ( str. cast :: < u8 > ( ) , str_length) } ;
2534 let string = String :: from_utf8_lossy ( char) ;
@@ -35,6 +44,8 @@ extern "C" fn output_tester(str: *const c_char, str_length: usize) -> usize {
3544
3645#[ test]
3746fn test_sapi ( ) {
47+ let _guard = SAPI_TEST_MUTEX . lock ( ) . unwrap ( ) ;
48+
3849 let mut builder = SapiBuilder :: new ( "test" , "Test" ) ;
3950 builder = builder. ub_write_function ( output_tester) ;
4051
@@ -86,6 +97,10 @@ fn test_sapi() {
8697 unsafe {
8798 sapi_shutdown ( ) ;
8899 }
100+
101+ unsafe {
102+ ext_php_rs_sapi_shutdown ( ) ;
103+ }
89104}
90105
91106/// Gives you a nice greeting!
@@ -102,3 +117,91 @@ pub fn hello_world(name: String) -> String {
102117pub fn module ( module : ModuleBuilder ) -> ModuleBuilder {
103118 module. function ( wrap_function ! ( hello_world) )
104119}
120+
121+ #[ test]
122+ fn test_sapi_multithread ( ) {
123+ let _guard = SAPI_TEST_MUTEX . lock ( ) . unwrap ( ) ;
124+
125+ let mut builder = SapiBuilder :: new ( "test-mt" , "Test Multi-threaded" ) ;
126+ builder = builder. ub_write_function ( output_tester) ;
127+
128+ let sapi = builder. build ( ) . unwrap ( ) . into_raw ( ) ;
129+ let module = get_module ( ) ;
130+
131+ unsafe {
132+ ext_php_rs_sapi_startup ( ) ;
133+ }
134+
135+ unsafe {
136+ sapi_startup ( sapi) ;
137+ }
138+
139+ unsafe {
140+ php_module_startup ( sapi, module) ;
141+ }
142+
143+ let results = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
144+ let mut handles = vec ! [ ] ;
145+
146+ for i in 0 ..4 {
147+ let results = Arc :: clone ( & results) ;
148+
149+ let handle = thread:: spawn ( move || {
150+ unsafe {
151+ ext_php_rs_sapi_per_thread_init ( ) ;
152+ }
153+
154+ let result = unsafe { php_request_startup ( ) } ;
155+ assert_eq ! ( result, ZEND_RESULT_CODE_SUCCESS ) ;
156+
157+ let _ = try_catch_first ( || {
158+ let eval_result = Embed :: eval ( & format ! ( "hello_world('thread-{i}');" ) ) ;
159+
160+ match eval_result {
161+ Ok ( zval) => {
162+ assert ! ( zval. is_string( ) ) ;
163+ let string = zval. string ( ) . unwrap ( ) ;
164+ let output = string. to_string ( ) ;
165+ assert_eq ! ( output, format!( "Hello, thread-{i}!" ) ) ;
166+
167+ results. lock ( ) . unwrap ( ) . push ( ( i, output) ) ;
168+ }
169+ Err ( e) => panic ! ( "Evaluation failed in thread {i}: {e:?}" ) ,
170+ }
171+ } ) ;
172+
173+ unsafe {
174+ php_request_shutdown ( std:: ptr:: null_mut ( ) ) ;
175+ }
176+ } ) ;
177+
178+ handles. push ( handle) ;
179+ }
180+
181+ for handle in handles {
182+ handle. join ( ) . expect ( "Thread panicked" ) ;
183+ }
184+
185+ let results = results. lock ( ) . unwrap ( ) ;
186+ assert_eq ! ( results. len( ) , 4 ) ;
187+
188+ for i in 0 ..4 {
189+ assert ! (
190+ results
191+ . iter( )
192+ . any( |( idx, output) | { * idx == i && output == & format!( "Hello, thread-{i}!" ) } )
193+ ) ;
194+ }
195+
196+ unsafe {
197+ php_module_shutdown ( ) ;
198+ }
199+
200+ unsafe {
201+ sapi_shutdown ( ) ;
202+ }
203+
204+ unsafe {
205+ ext_php_rs_sapi_shutdown ( ) ;
206+ }
207+ }
0 commit comments