tools: rewrite v timeout, support killing the child process on timeout by default (#24367)

This commit is contained in:
kbkpbot 2025-05-01 01:10:16 +08:00 committed by GitHub
parent f8c35b43f6
commit 6d0eaa5328
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,43 +1,71 @@
import os import os
import time import time
import flag import flag
import strconv
struct Context { struct Context {
mut: mut:
show_help bool
cmd_line_opts []string
full_cmd string
timeout f64 timeout f64
cmd_args []string
} }
fn main() { fn main() {
mut ctx := Context{} mut fp := flag.new_flag_parser(os.args[1..])
args := arguments()
mut fp := flag.new_flag_parser(args#[1..])
fp.application('v timeout') fp.application('v timeout')
fp.version('0.0.1') fp.version('0.0.2')
fp.description('Run a command with a time limit. Example: `v timeout 0.3 v run examples/hello_world.v`') fp.description('Run a command with a time limit. Example: `v timeout 0.3 v run examples/hello_world.v`')
fp.arguments_description('timeout_in_seconds CMD [ARGS]') fp.arguments_description('timeout_in_seconds CMD [ARGS]')
fp.skip_executable() fp.skip_executable()
fp.limit_free_args_to_at_least(2)! fp.limit_free_args_to_at_least(2)!
ctx.show_help = fp.bool('help', `h`, false, 'Show this help screen.')
if ctx.show_help { if fp.bool('help', `h`, false, 'Show this help screen.') {
println(fp.usage()) println(fp.usage())
exit(0) exit(0)
} }
ctx.cmd_line_opts = fp.finalize() or {
eprintln('> error: ${err}') args := fp.finalize() or {
eprintln('Argument error: ${err}')
exit(125) // mimic the exit codes of `timeout` in coreutils exit(125) // mimic the exit codes of `timeout` in coreutils
} }
ctx.timeout = ctx.cmd_line_opts[0].f64()
ctx.cmd_line_opts = ctx.cmd_line_opts#[1..] ctx := Context{
ctx.full_cmd = ctx.cmd_line_opts.join(' ') timeout: strconv.atof64(args[0]) or {
spawn fn (ctx Context) { eprintln('Invalid timeout: ${args[0]}')
tperiod := time.Duration(i64(ctx.timeout * time.second)) exit(125)
time.sleep(tperiod) }
// eprintln('> error: timeout of ${tperiod.seconds():5.3f}s reached, before command finished; command was: `${ctx.full_cmd}`') cmd_args: args[1..].clone()
exit(124) }
}(ctx)
ecode := os.system(ctx.full_cmd) mut p := os.new_process(ctx.cmd_args[0])
exit(ecode) p.set_args(ctx.cmd_args[1..])
p.run()
if p.err != '' {
eprintln('Cannot execute: ${ctx.cmd_args.join(' ')}')
exit(if os.exists(ctx.cmd_args[0]) { 126 } else { 127 })
}
child_exit := chan int{}
spawn fn (mut p os.Process, ch chan int) {
p.wait()
ch <- p.code
ch.close()
}(mut p, child_exit)
mut exit_code := 0
select {
i64(ctx.timeout * time.second) {
p.signal_term()
time.sleep(2 * time.millisecond)
if p.is_alive() {
p.signal_kill()
}
p.wait()
exit_code = 124 // timeout
}
code := <-child_exit {
exit_code = code
}
}
exit(exit_code)
} }