X Tutup
Skip to content

Commit fa4f84c

Browse files
authored
Introduce TimeoutSeconds utility type for timeout parameters (RustPython#7271)
* Introduce TimeoutSeconds utility type for timeout parameters Follow-up refactoring from RustPython#7237. Python timeout parameters typically accept both float and int. Several places in the codebase used Either<f64, i64> for this, each repeating the same match-and-convert boilerplate. This extracts that into a TimeoutSeconds type in vm::function::number. Refactored sites: - _sqlite3::ConnectArgs.timeout - _thread::AcquireArgs.timeout - _thread::ThreadHandle::join timeout Either<f64, i64> in time.rs (6 sites) left unchanged: those are timestamp values with per-branch logic (floor, range checks, etc). Either<f64, isize> in select.rs also left unchanged (different type). * Validate timeout in ThreadHandle::join to prevent panic * refactor: move TimeoutSeconds from number to time module
1 parent ead2d98 commit fa4f84c

File tree

4 files changed

+49
-23
lines changed

4 files changed

+49
-23
lines changed

crates/stdlib/src/_sqlite3.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ mod _sqlite3 {
6161
},
6262
convert::IntoObject,
6363
function::{
64-
ArgCallable, ArgIterable, Either, FsPath, FuncArgs, OptionalArg, PyComparisonValue,
65-
PySetterValue,
64+
ArgCallable, ArgIterable, FsPath, FuncArgs, OptionalArg, PyComparisonValue,
65+
PySetterValue, TimeoutSeconds,
6666
},
6767
object::{Traverse, TraverseFn},
6868
protocol::{
@@ -333,8 +333,8 @@ mod _sqlite3 {
333333
struct ConnectArgs {
334334
#[pyarg(any)]
335335
database: FsPath,
336-
#[pyarg(any, default = Either::A(5.0))]
337-
timeout: Either<f64, i64>,
336+
#[pyarg(any, default = TimeoutSeconds::new(5.0))]
337+
timeout: TimeoutSeconds,
338338
#[pyarg(any, default = 0)]
339339
detect_types: c_int,
340340
#[pyarg(any, default = Some(vm.ctx.empty_str.to_owned()))]
@@ -991,10 +991,7 @@ mod _sqlite3 {
991991
fn initialize_db(args: &ConnectArgs, vm: &VirtualMachine) -> PyResult<Sqlite> {
992992
let path = args.database.to_cstring(vm)?;
993993
let db = Sqlite::from(SqliteRaw::open(path.as_ptr(), args.uri, vm)?);
994-
let timeout = (match args.timeout {
995-
Either::A(float) => float,
996-
Either::B(int) => int as f64,
997-
} * 1000.0) as c_int;
994+
let timeout = (args.timeout.to_secs_f64() * 1000.0) as c_int;
998995
db.busy_timeout(timeout);
999996
if let Some(isolation_level) = &args.isolation_level {
1000997
begin_statement_ptr_from_isolation_level(isolation_level, vm)?;

crates/vm/src/function/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod getset;
88
mod method;
99
mod number;
1010
mod protocol;
11+
mod time;
1112

1213
pub use argument::{
1314
ArgumentError, FromArgOptional, FromArgs, FuncArgs, IntoFuncArgs, KwArgs, OptionalArg,
@@ -23,6 +24,7 @@ pub(super) use getset::{IntoPyGetterFunc, IntoPySetterFunc, PyGetterFunc, PySett
2324
pub use method::{HeapMethodDef, PyMethodDef, PyMethodFlags};
2425
pub use number::{ArgIndex, ArgIntoBool, ArgIntoComplex, ArgIntoFloat, ArgPrimitiveIndex, ArgSize};
2526
pub use protocol::{ArgCallable, ArgIterable, ArgMapping, ArgSequence};
27+
pub use time::TimeoutSeconds;
2628

2729
use crate::{PyObject, PyResult, VirtualMachine, builtins::PyStr, convert::TryFromBorrowedObject};
2830
use builtin::{BorrowedParam, OwnedParam, RefParam};

crates/vm/src/function/time.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::{PyObjectRef, PyResult, TryFromObject, VirtualMachine};
2+
3+
/// A Python timeout value that accepts both `float` and `int`.
4+
///
5+
/// `TimeoutSeconds` implements `FromArgs` so that a built-in function can accept
6+
/// timeout parameters given as either `float` or `int`, normalizing them to `f64`.
7+
#[derive(Debug, Clone, Copy, PartialEq)]
8+
pub struct TimeoutSeconds {
9+
value: f64,
10+
}
11+
12+
impl TimeoutSeconds {
13+
pub const fn new(secs: f64) -> Self {
14+
Self { value: secs }
15+
}
16+
17+
#[inline]
18+
pub fn to_secs_f64(self) -> f64 {
19+
self.value
20+
}
21+
}
22+
23+
impl TryFromObject for TimeoutSeconds {
24+
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
25+
let value = match super::Either::<f64, i64>::try_from_object(vm, obj)? {
26+
super::Either::A(f) => f,
27+
super::Either::B(i) => i as f64,
28+
};
29+
if value.is_nan() {
30+
return Err(vm.new_value_error("Invalid value NaN (not a number)".to_owned()));
31+
}
32+
Ok(Self { value })
33+
}
34+
}

crates/vm/src/stdlib/thread.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub(crate) mod _thread {
1515
builtins::{PyDictRef, PyStr, PyTupleRef, PyType, PyTypeRef, PyUtf8StrRef},
1616
common::wtf8::Wtf8Buf,
1717
frame::FrameRef,
18-
function::{ArgCallable, Either, FuncArgs, KwArgs, OptionalArg, PySetterValue},
18+
function::{ArgCallable, FuncArgs, KwArgs, OptionalArg, PySetterValue, TimeoutSeconds},
1919
types::{Constructor, GetAttr, Representable, SetAttr},
2020
};
2121
use alloc::{
@@ -65,33 +65,26 @@ pub(crate) mod _thread {
6565
struct AcquireArgs {
6666
#[pyarg(any, default = true)]
6767
blocking: bool,
68-
#[pyarg(any, default = Either::A(-1.0))]
69-
timeout: Either<f64, i64>,
68+
#[pyarg(any, default = TimeoutSeconds::new(-1.0))]
69+
timeout: TimeoutSeconds,
7070
}
7171

7272
macro_rules! acquire_lock_impl {
7373
($mu:expr, $args:expr, $vm:expr) => {{
7474
let (mu, args, vm) = ($mu, $args, $vm);
75-
let timeout = match args.timeout {
76-
Either::A(f) => f,
77-
Either::B(i) => i as f64,
78-
};
75+
let timeout = args.timeout.to_secs_f64();
7976
match args.blocking {
8077
true if timeout == -1.0 => {
8178
vm.allow_threads(|| mu.lock());
8279
Ok(true)
8380
}
8481
true if timeout < 0.0 => {
85-
Err(vm.new_value_error("timeout value must be positive".to_owned()))
82+
Err(vm
83+
.new_value_error("timeout value must be a non-negative number".to_owned()))
8684
}
8785
true => {
88-
// modified from std::time::Duration::from_secs_f64 to avoid a panic.
89-
// TODO: put this in the Duration::try_from_object impl, maybe?
90-
let nanos = timeout * 1_000_000_000.0;
91-
if timeout > TIMEOUT_MAX as f64 || nanos < 0.0 || !nanos.is_finite() {
92-
return Err(vm.new_overflow_error(
93-
"timestamp too large to convert to Rust Duration".to_owned(),
94-
));
86+
if timeout > TIMEOUT_MAX {
87+
return Err(vm.new_overflow_error("timeout value is too large".to_owned()));
9588
}
9689

9790
Ok(vm.allow_threads(|| mu.try_lock_for(Duration::from_secs_f64(timeout))))

0 commit comments

Comments
 (0)
X Tutup