99extern crate ext_php_rs;
1010
1111use ext_php_rs:: builders:: SapiBuilder ;
12- use ext_php_rs:: embed:: { ext_php_rs_sapi_startup, Embed } ;
12+ use ext_php_rs:: embed:: { ext_php_rs_sapi_per_thread_init , ext_php_rs_sapi_shutdown , ext_php_rs_sapi_startup, Embed } ;
1313use ext_php_rs:: ffi:: {
1414 php_module_shutdown, php_module_startup, php_request_shutdown, php_request_startup,
1515 sapi_shutdown, sapi_startup, ZEND_RESULT_CODE_SUCCESS ,
1616} ;
1717use ext_php_rs:: prelude:: * ;
1818use ext_php_rs:: zend:: try_catch_first;
1919use std:: ffi:: c_char;
20+ use std:: sync:: Mutex ;
2021
2122static mut LAST_OUTPUT : String = String :: new ( ) ;
2223
24+ // Global mutex to ensure SAPI tests don't run concurrently. PHP does not allow
25+ // multiple SAPIs to exist at the same time. This prevents the tests from
26+ // overwriting each other's state.
27+ static SAPI_TEST_MUTEX : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
28+
2329extern "C" fn output_tester ( str : * const c_char , str_length : usize ) -> usize {
2430 let char = unsafe { std:: slice:: from_raw_parts ( str. cast :: < u8 > ( ) , str_length) } ;
2531 let string = String :: from_utf8_lossy ( char) ;
@@ -35,6 +41,8 @@ extern "C" fn output_tester(str: *const c_char, str_length: usize) -> usize {
3541
3642#[ test]
3743fn test_sapi ( ) {
44+ let _guard = SAPI_TEST_MUTEX . lock ( ) . unwrap ( ) ;
45+
3846 let mut builder = SapiBuilder :: new ( "test" , "Test" ) ;
3947 builder = builder. ub_write_function ( output_tester) ;
4048
@@ -86,6 +94,10 @@ fn test_sapi() {
8694 unsafe {
8795 sapi_shutdown ( ) ;
8896 }
97+
98+ unsafe {
99+ ext_php_rs_sapi_shutdown ( ) ;
100+ }
89101}
90102
91103/// Gives you a nice greeting!
@@ -102,3 +114,92 @@ pub fn hello_world(name: String) -> String {
102114pub fn module ( module : ModuleBuilder ) -> ModuleBuilder {
103115 module. function ( wrap_function ! ( hello_world) )
104116}
117+
118+ #[ test]
119+ fn test_sapi_multithread ( ) {
120+ let _guard = SAPI_TEST_MUTEX . lock ( ) . unwrap ( ) ;
121+
122+ use std:: sync:: { Arc , Mutex } ;
123+ use std:: thread;
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 ( _) => panic ! ( "Evaluation failed in thread {}" , i) ,
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 ! ( results. iter( ) . any( |( idx, output) | {
190+ * idx == i && output == & format!( "Hello, thread-{}!" , i)
191+ } ) ) ;
192+ }
193+
194+ unsafe {
195+ php_module_shutdown ( ) ;
196+ }
197+
198+ unsafe {
199+ sapi_shutdown ( ) ;
200+ }
201+
202+ unsafe {
203+ ext_php_rs_sapi_shutdown ( ) ;
204+ }
205+ }
0 commit comments