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:: Mutex ;
2023
2124static mut LAST_OUTPUT : String = String :: new ( ) ;
2225
26+ // Global mutex to ensure SAPI tests don't run concurrently. PHP does not allow
27+ // multiple SAPIs to exist at the same time. This prevents the tests from
28+ // overwriting each other's state.
29+ static SAPI_TEST_MUTEX : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
30+
2331extern "C" fn output_tester ( str : * const c_char , str_length : usize ) -> usize {
2432 let char = unsafe { std:: slice:: from_raw_parts ( str. cast :: < u8 > ( ) , str_length) } ;
2533 let string = String :: from_utf8_lossy ( char) ;
@@ -35,6 +43,8 @@ extern "C" fn output_tester(str: *const c_char, str_length: usize) -> usize {
3543
3644#[ test]
3745fn test_sapi ( ) {
46+ let _guard = SAPI_TEST_MUTEX . lock ( ) . unwrap ( ) ;
47+
3848 let mut builder = SapiBuilder :: new ( "test" , "Test" ) ;
3949 builder = builder. ub_write_function ( output_tester) ;
4050
@@ -86,6 +96,10 @@ fn test_sapi() {
8696 unsafe {
8797 sapi_shutdown ( ) ;
8898 }
99+
100+ unsafe {
101+ ext_php_rs_sapi_shutdown ( ) ;
102+ }
89103}
90104
91105/// Gives you a nice greeting!
@@ -102,3 +116,94 @@ pub fn hello_world(name: String) -> String {
102116pub fn module ( module : ModuleBuilder ) -> ModuleBuilder {
103117 module. function ( wrap_function ! ( hello_world) )
104118}
119+
120+ #[ test]
121+ fn test_sapi_multithread ( ) {
122+ let _guard = SAPI_TEST_MUTEX . lock ( ) . unwrap ( ) ;
123+
124+ use std:: sync:: { Arc , Mutex } ;
125+ use std:: thread;
126+
127+ let mut builder = SapiBuilder :: new ( "test-mt" , "Test Multi-threaded" ) ;
128+ builder = builder. ub_write_function ( output_tester) ;
129+
130+ let sapi = builder. build ( ) . unwrap ( ) . into_raw ( ) ;
131+ let module = get_module ( ) ;
132+
133+ unsafe {
134+ ext_php_rs_sapi_startup ( ) ;
135+ }
136+
137+ unsafe {
138+ sapi_startup ( sapi) ;
139+ }
140+
141+ unsafe {
142+ php_module_startup ( sapi, module) ;
143+ }
144+
145+ let results = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
146+ let mut handles = vec ! [ ] ;
147+
148+ for i in 0 ..4 {
149+ let results = Arc :: clone ( & results) ;
150+
151+ let handle = thread:: spawn ( move || {
152+ unsafe {
153+ ext_php_rs_sapi_per_thread_init ( ) ;
154+ }
155+
156+ let result = unsafe { php_request_startup ( ) } ;
157+ assert_eq ! ( result, ZEND_RESULT_CODE_SUCCESS ) ;
158+
159+ let _ = try_catch_first ( || {
160+ let eval_result = Embed :: eval ( & format ! ( "hello_world('thread-{}');" , i) ) ;
161+
162+ match eval_result {
163+ Ok ( zval) => {
164+ assert ! ( zval. is_string( ) ) ;
165+ let string = zval. string ( ) . unwrap ( ) ;
166+ let output = string. to_string ( ) ;
167+ assert_eq ! ( output, format!( "Hello, thread-{}!" , i) ) ;
168+
169+ results. lock ( ) . unwrap ( ) . push ( ( i, output) ) ;
170+ }
171+ Err ( _) => panic ! ( "Evaluation failed in thread {}" , i) ,
172+ }
173+ } ) ;
174+
175+ unsafe {
176+ php_request_shutdown ( std:: ptr:: null_mut ( ) ) ;
177+ }
178+ } ) ;
179+
180+ handles. push ( handle) ;
181+ }
182+
183+ for handle in handles {
184+ handle. join ( ) . expect ( "Thread panicked" ) ;
185+ }
186+
187+ let results = results. lock ( ) . unwrap ( ) ;
188+ assert_eq ! ( results. len( ) , 4 ) ;
189+
190+ for i in 0 ..4 {
191+ assert ! (
192+ results
193+ . iter( )
194+ . any( |( idx, output) | { * idx == i && output == & format!( "Hello, thread-{}!" , i) } )
195+ ) ;
196+ }
197+
198+ unsafe {
199+ php_module_shutdown ( ) ;
200+ }
201+
202+ unsafe {
203+ sapi_shutdown ( ) ;
204+ }
205+
206+ unsafe {
207+ ext_php_rs_sapi_shutdown ( ) ;
208+ }
209+ }
0 commit comments