@@ -94,10 +94,35 @@ pub fn extplugin_client(
9494
9595 tx_req. send ( ( tx_res. clone ( ) , request) ) ?;
9696
97- let response = rx_res. recv ( ) ?;
97+ // Wait for either the response or the server process termination
98+ let response = loop {
99+ if let Some ( status) = child. try_wait ( ) ? {
100+ if !status. success ( ) {
101+ anyhow:: bail!( "Server process terminated abnormally" ) ;
102+ } else {
103+ // サーバーは応答を送信せずに正常に終了しました。
104+ // 決して届かないメッセージを無期限に待つのを避けます。
105+ anyhow:: bail!( "Server process exited successfully without sending a response" ) ;
106+ }
107+ }
108+
109+ match rx_res. try_recv_timeout ( std:: time:: Duration :: from_millis ( 100 ) ) {
110+ Ok ( resp) => break resp,
111+ Err ( ipc:: TryRecvError :: Empty ) => continue ,
112+ Err ( ipc:: TryRecvError :: IpcError ( err) ) => anyhow:: bail!( err) ,
113+ }
114+ } ;
98115
99- let _ = child. kill ( ) ;
100- let _ = child. wait ( ) ;
116+ let mut killed = false ;
117+ if child. try_wait ( ) ?. is_none ( ) {
118+ let _ = child. kill ( ) ;
119+ killed = true ;
120+ }
121+ // TODO: Improve error handling for server process termination
122+ let exit_status = child. wait ( ) ?;
123+ if !exit_status. success ( ) && !killed {
124+ anyhow:: bail!( "Server process terminated abnormally: {exit_status:?}" ) ;
125+ }
101126
102127 if let Some ( err) = response. error_message {
103128 anyhow:: bail!( err) ;
@@ -106,7 +131,7 @@ pub fn extplugin_client(
106131 RsJsBridgeReturns :: new_from_str ( & response. returns_json )
107132}
108133
109- pub fn extplugin_server ( server_name : & str ) -> Result < ( ) > {
134+ pub async fn extplugin_server ( server_name : & str ) -> Result < ( ) > {
110135 use crate :: permissions:: permissions_options_from_sapphillon_permissions;
111136
112137 let ( tx_req, rx_req) = ipc:: channel ( ) ?;
@@ -122,10 +147,6 @@ pub fn extplugin_server(server_name: &str) -> Result<()> {
122147 }
123148 std:: mem:: forget ( tx_req) ;
124149
125- let rt = tokio:: runtime:: Builder :: new_current_thread ( )
126- . enable_all ( )
127- . build ( ) ?;
128-
129150 if let Ok ( ( tx_res, request) ) = rx_req. recv ( ) {
130151 // Convert IpcPermission back to proto::Permission
131152 let sapphillon_permissions: Vec < proto:: sapphillon:: v1:: Permission > = request
@@ -142,11 +163,12 @@ pub fn extplugin_server(server_name: &str) -> Result<()> {
142163 Some ( permissions_options)
143164 } ;
144165
145- let result = rt . block_on ( async {
166+ let result = async {
146167 let package = SapphillonPackage :: new_async ( & request. package_js ) . await ?;
147168 let args = RsJsBridgeArgs :: new_from_str ( & request. args_json ) ?;
148169 package. execute ( args, & permissions_options) . await
149- } ) ;
170+ }
171+ . await ;
150172
151173 let response = match result {
152174 Ok ( returns) => ExternalPluginRunResponse {
@@ -169,9 +191,20 @@ pub fn extplugin_server(server_name: &str) -> Result<()> {
169191mod tests {
170192 use super :: * ;
171193 use serde_json:: json;
194+ use std:: sync:: { Mutex , OnceLock } ;
195+
196+ static TEST_SERVER_MUTEX : OnceLock < Mutex < ( ) > > = OnceLock :: new ( ) ;
197+
198+ fn test_server_lock ( ) -> std:: sync:: MutexGuard < ' static , ( ) > {
199+ TEST_SERVER_MUTEX
200+ . get_or_init ( || Mutex :: new ( ( ) ) )
201+ . lock ( )
202+ . unwrap_or_else ( |err| err. into_inner ( ) )
203+ }
172204
173205 #[ test]
174206 fn test_extplugin_runner_process ( ) -> Result < ( ) > {
207+ let _guard = test_server_lock ( ) ;
175208 let package_script = r#"
176209 globalThis.Sapphillon = {
177210 Package: {
@@ -203,6 +236,10 @@ mod tests {
203236 }
204237 };
205238 "# ;
239+ if std:: env:: var ( "SAPPHILLON_TEST_SERVER_ABORT" ) . is_ok ( ) {
240+ // Skip this test if the abort environment variable is set
241+ return Ok ( ( ) ) ;
242+ }
206243
207244 let package = SapphillonPackage :: new ( package_script) ?;
208245
@@ -255,4 +292,100 @@ mod tests {
255292
256293 Ok ( ( ) )
257294 }
295+
296+ #[ test]
297+ fn test_extplugin_runner_process_abnormal_exit_returns_error ( ) -> Result < ( ) > {
298+ let _guard = test_server_lock ( ) ;
299+ let package_script = r#"
300+ globalThis.Sapphillon = {
301+ Package: {
302+ meta: {
303+ name: "test-plugin",
304+ version: "1.0.0",
305+ description: "Test plugin",
306+ author_id: "com.example",
307+ package_id: "com.example.test-plugin"
308+ },
309+ functions: {
310+ echo: {
311+ description: "Echoes the input",
312+ permissions: [],
313+ parameters: [{
314+ idx: 0,
315+ name: "message",
316+ type: "string",
317+ description: "Message to echo"
318+ }],
319+ returns: [{
320+ idx: 0,
321+ type: "string",
322+ description: "Echoed message"
323+ }],
324+ handler: (message) => message
325+ }
326+ }
327+ }
328+ };
329+ "# ;
330+
331+ let package = SapphillonPackage :: new ( package_script) ?;
332+
333+ let args = RsJsBridgeArgs {
334+ func_name : "echo" . to_string ( ) ,
335+ args : vec ! [ ( "message" . to_string( ) , json!( "Hello, World!" ) ) ]
336+ . into_iter ( )
337+ . collect ( ) ,
338+ } ;
339+
340+ let mut server_path_buf = std:: env:: current_exe ( ) ?;
341+ server_path_buf. pop ( ) ;
342+ if server_path_buf. file_name ( ) . and_then ( |s| s. to_str ( ) ) == Some ( "deps" ) {
343+ server_path_buf. pop ( ) ;
344+ }
345+ server_path_buf. push ( "extplugin_test_server" ) ;
346+
347+ if !server_path_buf. exists ( ) {
348+ let manifest_dir = std:: path:: PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) ;
349+ let status = std:: process:: Command :: new ( "cargo" )
350+ . args ( [ "build" , "--bin" , "extplugin_test_server" ] )
351+ . current_dir ( & manifest_dir)
352+ . status ( )
353+ . expect ( "Failed to execute cargo build" ) ;
354+ if !status. success ( ) {
355+ anyhow:: bail!( "Failed to build extplugin_test_server" ) ;
356+ }
357+ }
358+
359+ let server_path = server_path_buf
360+ . to_str ( )
361+ . ok_or_else ( || anyhow:: anyhow!( "Invalid path" ) ) ?;
362+ let server_args = vec ! [ ] ;
363+ let sapphillon_permissions = vec ! [ ] ;
364+
365+ unsafe {
366+ std:: env:: set_var ( "SAPPHILLON_TEST_SERVER_ABORT" , "1" ) ;
367+ }
368+ let result = extplugin_client (
369+ & package,
370+ "echo" ,
371+ & args,
372+ server_path,
373+ server_args,
374+ sapphillon_permissions,
375+ ) ;
376+ unsafe {
377+ std:: env:: remove_var ( "SAPPHILLON_TEST_SERVER_ABORT" ) ;
378+ }
379+
380+ assert ! ( result. is_err( ) , "Expected error on abnormal server exit" ) ;
381+ assert ! (
382+ result
383+ . unwrap_err( )
384+ . to_string( )
385+ . contains( "terminated abnormally" ) ,
386+ "Expected error message to contain 'terminated abnormally'"
387+ ) ;
388+
389+ Ok ( ( ) )
390+ }
258391}
0 commit comments