@@ -233,41 +233,47 @@ impl PyBash {
233233 }
234234
235235 /// Execute commands synchronously (blocking).
236- fn execute_sync ( & self , commands : String ) -> PyResult < ExecResult > {
236+ /// Releases GIL before blocking on tokio to prevent deadlock with callbacks.
237+ fn execute_sync ( & self , py : Python < ' _ > , commands : String ) -> PyResult < ExecResult > {
237238 let inner = self . inner . clone ( ) ;
238239 let rt = tokio:: runtime:: Runtime :: new ( )
239240 . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Failed to create runtime: {}" , e) ) ) ?;
240241
241- rt. block_on ( async move {
242- let mut bash = inner. lock ( ) . await ;
243- match bash. exec ( & commands) . await {
244- Ok ( result) => Ok ( ExecResult {
245- stdout : result. stdout ,
246- stderr : result. stderr ,
247- exit_code : result. exit_code ,
248- error : None ,
249- } ) ,
250- Err ( e) => Ok ( ExecResult {
251- stdout : String :: new ( ) ,
252- stderr : String :: new ( ) ,
253- exit_code : 1 ,
254- error : Some ( e. to_string ( ) ) ,
255- } ) ,
256- }
242+ py. detach ( || {
243+ rt. block_on ( async move {
244+ let mut bash = inner. lock ( ) . await ;
245+ match bash. exec ( & commands) . await {
246+ Ok ( result) => Ok ( ExecResult {
247+ stdout : result. stdout ,
248+ stderr : result. stderr ,
249+ exit_code : result. exit_code ,
250+ error : None ,
251+ } ) ,
252+ Err ( e) => Ok ( ExecResult {
253+ stdout : String :: new ( ) ,
254+ stderr : String :: new ( ) ,
255+ exit_code : 1 ,
256+ error : Some ( e. to_string ( ) ) ,
257+ } ) ,
258+ }
259+ } )
257260 } )
258261 }
259262
260263 /// Reset interpreter to fresh state.
261- fn reset ( & self ) -> PyResult < ( ) > {
264+ /// Releases GIL before blocking on tokio to prevent deadlock.
265+ fn reset ( & self , py : Python < ' _ > ) -> PyResult < ( ) > {
262266 let inner = self . inner . clone ( ) ;
263267 let rt = tokio:: runtime:: Runtime :: new ( )
264268 . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Failed to create runtime: {}" , e) ) ) ?;
265269
266- rt. block_on ( async move {
267- let mut bash = inner. lock ( ) . await ;
268- let builder = Bash :: builder ( ) ;
269- * bash = builder. build ( ) ;
270- Ok ( ( ) )
270+ py. detach ( || {
271+ rt. block_on ( async move {
272+ let mut bash = inner. lock ( ) . await ;
273+ let builder = Bash :: builder ( ) ;
274+ * bash = builder. build ( ) ;
275+ Ok ( ( ) )
276+ } )
271277 } )
272278 }
273279
@@ -378,40 +384,46 @@ impl BashTool {
378384 } )
379385 }
380386
381- fn execute_sync ( & self , commands : String ) -> PyResult < ExecResult > {
387+ /// Releases GIL before blocking on tokio to prevent deadlock with callbacks.
388+ fn execute_sync ( & self , py : Python < ' _ > , commands : String ) -> PyResult < ExecResult > {
382389 let inner = self . inner . clone ( ) ;
383390 let rt = tokio:: runtime:: Runtime :: new ( )
384391 . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Failed to create runtime: {}" , e) ) ) ?;
385392
386- rt. block_on ( async move {
387- let mut bash = inner. lock ( ) . await ;
388- match bash. exec ( & commands) . await {
389- Ok ( result) => Ok ( ExecResult {
390- stdout : result. stdout ,
391- stderr : result. stderr ,
392- exit_code : result. exit_code ,
393- error : None ,
394- } ) ,
395- Err ( e) => Ok ( ExecResult {
396- stdout : String :: new ( ) ,
397- stderr : String :: new ( ) ,
398- exit_code : 1 ,
399- error : Some ( e. to_string ( ) ) ,
400- } ) ,
401- }
393+ py. detach ( || {
394+ rt. block_on ( async move {
395+ let mut bash = inner. lock ( ) . await ;
396+ match bash. exec ( & commands) . await {
397+ Ok ( result) => Ok ( ExecResult {
398+ stdout : result. stdout ,
399+ stderr : result. stderr ,
400+ exit_code : result. exit_code ,
401+ error : None ,
402+ } ) ,
403+ Err ( e) => Ok ( ExecResult {
404+ stdout : String :: new ( ) ,
405+ stderr : String :: new ( ) ,
406+ exit_code : 1 ,
407+ error : Some ( e. to_string ( ) ) ,
408+ } ) ,
409+ }
410+ } )
402411 } )
403412 }
404413
405- fn reset ( & self ) -> PyResult < ( ) > {
414+ /// Releases GIL before blocking on tokio to prevent deadlock.
415+ fn reset ( & self , py : Python < ' _ > ) -> PyResult < ( ) > {
406416 let inner = self . inner . clone ( ) ;
407417 let rt = tokio:: runtime:: Runtime :: new ( )
408418 . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Failed to create runtime: {}" , e) ) ) ?;
409419
410- rt. block_on ( async move {
411- let mut bash = inner. lock ( ) . await ;
412- let builder = Bash :: builder ( ) ;
413- * bash = builder. build ( ) ;
414- Ok ( ( ) )
420+ py. detach ( || {
421+ rt. block_on ( async move {
422+ let mut bash = inner. lock ( ) . await ;
423+ let builder = Bash :: builder ( ) ;
424+ * bash = builder. build ( ) ;
425+ Ok ( ( ) )
426+ } )
415427 } )
416428 }
417429
@@ -652,17 +664,20 @@ impl ScriptedTool {
652664 }
653665
654666 /// Execute a bash script synchronously (blocking).
655- fn execute_sync ( & self , commands : String ) -> PyResult < ExecResult > {
667+ /// Releases GIL before blocking on tokio to prevent deadlock with callbacks.
668+ fn execute_sync ( & self , py : Python < ' _ > , commands : String ) -> PyResult < ExecResult > {
656669 let mut tool = self . build_rust_tool ( ) ;
657670 let rt = tokio:: runtime:: Runtime :: new ( )
658671 . map_err ( |e| PyRuntimeError :: new_err ( format ! ( "Failed to create runtime: {}" , e) ) ) ?;
659672
660- let resp = rt. block_on ( async move {
661- tool. execute ( ToolRequest {
662- commands,
663- timeout_ms : None ,
673+ let resp = py. detach ( || {
674+ rt. block_on ( async move {
675+ tool. execute ( ToolRequest {
676+ commands,
677+ timeout_ms : None ,
678+ } )
679+ . await
664680 } )
665- . await
666681 } ) ;
667682 Ok ( ExecResult {
668683 stdout : resp. stdout ,
0 commit comments