@@ -2,7 +2,7 @@ use std::panic::AssertUnwindSafe;
22use std:: path:: { Path , PathBuf } ;
33use std:: process:: Stdio ;
44use std:: time:: Duration ;
5- use std:: { io, panic} ;
5+ use std:: { env , io, panic} ;
66
77use async_channel:: { Receiver , SendError } ;
88use tempfile:: tempdir_in;
@@ -20,7 +20,7 @@ use uv_warnings::warn_user;
2020
2121const COMPILEALL_SCRIPT : & str = include_str ! ( "pip_compileall.py" ) ;
2222/// This is longer than any compilation should ever take.
23- const COMPILE_TIMEOUT : Duration = Duration :: from_secs ( 60 ) ;
23+ const DEFAULT_COMPILE_TIMEOUT : Duration = Duration :: from_secs ( 60 ) ;
2424
2525#[ derive( Debug , Error ) ]
2626pub enum CompileError {
@@ -55,6 +55,8 @@ pub enum CompileError {
5555 } ,
5656 #[ error( "Python startup timed out ({}s)" , _0. as_secs_f32( ) ) ]
5757 StartupTimeout ( Duration ) ,
58+ #[ error( "Got invalid value from environment for {var}: {message}." ) ]
59+ EnvironmentError { var : & ' static str , message : String } ,
5860}
5961
6062/// Bytecode compile all file in `dir` using a pool of Python interpreters running a Python script
@@ -88,6 +90,29 @@ pub async fn compile_tree(
8890 let tempdir = tempdir_in ( cache) . map_err ( CompileError :: TempFile ) ?;
8991 let pip_compileall_py = tempdir. path ( ) . join ( "pip_compileall.py" ) ;
9092
93+ let timeout: Option < Duration > = match env:: var ( EnvVars :: UV_COMPILE_BYTECODE_TIMEOUT ) {
94+ Ok ( value) => {
95+ if value == "0" {
96+ debug ! ( "Disabling bytecode compilation timeout" ) ;
97+ None
98+ } else {
99+ if let Ok ( duration) = value. parse :: < u64 > ( ) . map ( Duration :: from_secs) {
100+ debug ! (
101+ "Using bytecode compilation timeout of {}s" ,
102+ duration. as_secs( )
103+ ) ;
104+ Some ( duration)
105+ } else {
106+ return Err ( CompileError :: EnvironmentError {
107+ var : "UV_COMPILE_BYTECODE_TIMEOUT" ,
108+ message : format ! ( "Expected an integer number of seconds, got \" {value}\" " ) ,
109+ } ) ;
110+ }
111+ }
112+ }
113+ Err ( _) => Some ( DEFAULT_COMPILE_TIMEOUT ) ,
114+ } ;
115+
91116 debug ! ( "Starting {} bytecode compilation workers" , worker_count) ;
92117 let mut worker_handles = Vec :: new ( ) ;
93118 for _ in 0 ..worker_count {
@@ -98,6 +123,7 @@ pub async fn compile_tree(
98123 python_executable. to_path_buf ( ) ,
99124 pip_compileall_py. clone ( ) ,
100125 receiver. clone ( ) ,
126+ timeout,
101127 ) ;
102128
103129 // Spawn each worker on a dedicated thread.
@@ -189,6 +215,7 @@ async fn worker(
189215 interpreter : PathBuf ,
190216 pip_compileall_py : PathBuf ,
191217 receiver : Receiver < PathBuf > ,
218+ timeout : Option < Duration > ,
192219) -> Result < ( ) , CompileError > {
193220 fs_err:: tokio:: write ( & pip_compileall_py, COMPILEALL_SCRIPT )
194221 . await
@@ -208,12 +235,17 @@ async fn worker(
208235 }
209236 }
210237 } ;
238+
211239 // Handle a broken `python` by using a timeout, one that's higher than any compilation
212240 // should ever take.
213241 let ( mut bytecode_compiler, child_stdin, mut child_stdout, mut child_stderr) =
214- tokio:: time:: timeout ( COMPILE_TIMEOUT , wait_until_ready)
215- . await
216- . map_err ( |_| CompileError :: StartupTimeout ( COMPILE_TIMEOUT ) ) ??;
242+ if let Some ( duration) = timeout {
243+ tokio:: time:: timeout ( duration, wait_until_ready)
244+ . await
245+ . map_err ( |_| CompileError :: StartupTimeout ( timeout. unwrap ( ) ) ) ??
246+ } else {
247+ wait_until_ready. await ?
248+ } ;
217249
218250 let stderr_reader = tokio:: task:: spawn ( async move {
219251 let mut child_stderr_collected: Vec < u8 > = Vec :: new ( ) ;
@@ -223,7 +255,7 @@ async fn worker(
223255 Ok ( child_stderr_collected)
224256 } ) ;
225257
226- let result = worker_main_loop ( receiver, child_stdin, & mut child_stdout) . await ;
258+ let result = worker_main_loop ( receiver, child_stdin, & mut child_stdout, timeout ) . await ;
227259 // Reap the process to avoid zombies.
228260 let _ = bytecode_compiler. kill ( ) . await ;
229261
@@ -340,6 +372,7 @@ async fn worker_main_loop(
340372 receiver : Receiver < PathBuf > ,
341373 mut child_stdin : ChildStdin ,
342374 child_stdout : & mut BufReader < ChildStdout > ,
375+ timeout : Option < Duration > ,
343376) -> Result < ( ) , CompileError > {
344377 let mut out_line = String :: new ( ) ;
345378 while let Ok ( source_file) = receiver. recv ( ) . await {
@@ -372,12 +405,16 @@ async fn worker_main_loop(
372405
373406 // Handle a broken `python` by using a timeout, one that's higher than any compilation
374407 // should ever take.
375- tokio:: time:: timeout ( COMPILE_TIMEOUT , python_handle)
376- . await
377- . map_err ( |_| CompileError :: CompileTimeout {
378- elapsed : COMPILE_TIMEOUT ,
379- source_file : source_file. clone ( ) ,
380- } ) ??;
408+ if let Some ( duration) = timeout {
409+ tokio:: time:: timeout ( duration, python_handle)
410+ . await
411+ . map_err ( |_| CompileError :: CompileTimeout {
412+ elapsed : duration,
413+ source_file : source_file. clone ( ) ,
414+ } ) ??;
415+ } else {
416+ python_handle. await ?;
417+ }
381418
382419 // This is a sanity check, if we don't get the path back something has gone wrong, e.g.
383420 // we're not actually running a python interpreter.
0 commit comments