Skip to content

Commit a6afd9e

Browse files
committed
Add _thread._count, repr(lock), and respect TIMEOUT_MAX
1 parent fc2f2d1 commit a6afd9e

File tree

2 files changed

+61
-11
lines changed

2 files changed

+61
-11
lines changed

vm/src/stdlib/thread.rs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs};
44
use crate::obj::objdict::PyDictRef;
55
use crate::obj::objtuple::PyTupleRef;
66
use crate::obj::objtype::PyClassRef;
7-
use crate::pyobject::{Either, PyCallable, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue};
7+
use crate::pyobject::{
8+
Either, IdProtocol, PyCallable, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue,
9+
TypeProtocol,
10+
};
811
use crate::vm::VirtualMachine;
912

1013
use parking_lot::{
@@ -16,18 +19,20 @@ use std::io::Write;
1619
use std::time::Duration;
1720
use std::{fmt, thread};
1821

22+
// PY_TIMEOUT_MAX is a value in microseconds
1923
#[cfg(not(target_os = "windows"))]
20-
const PY_TIMEOUT_MAX: isize = std::isize::MAX;
24+
const PY_TIMEOUT_MAX: isize = std::isize::MAX / 1_000;
2125

2226
#[cfg(target_os = "windows")]
23-
const PY_TIMEOUT_MAX: isize = 0xffffffff * 1_000_000;
27+
const PY_TIMEOUT_MAX: isize = 0xffffffff * 1_000;
2428

25-
const TIMEOUT_MAX: f64 = (PY_TIMEOUT_MAX / 1_000_000_000) as f64;
29+
// this is a value in seconds
30+
const TIMEOUT_MAX: f64 = (PY_TIMEOUT_MAX / 1_000_000) as f64;
2631

2732
#[derive(FromArgs)]
2833
struct AcquireArgs {
2934
#[pyarg(positional_or_keyword, default = "true")]
30-
waitflag: bool,
35+
blocking: bool,
3136
#[pyarg(positional_or_keyword, default = "Either::A(-1.0)")]
3237
timeout: Either<f64, isize>,
3338
}
@@ -39,7 +44,7 @@ macro_rules! acquire_lock_impl {
3944
Either::A(f) => f,
4045
Either::B(i) => i as f64,
4146
};
42-
match args.waitflag {
47+
match args.blocking {
4348
true if timeout == -1.0 => {
4449
mu.lock();
4550
Ok(true)
@@ -48,7 +53,16 @@ macro_rules! acquire_lock_impl {
4853
Err(vm.new_value_error("timeout value must be positive".to_owned()))
4954
}
5055
true => {
51-
// TODO: respect TIMEOUT_MAX here
56+
// modified from std::time::Duration::from_secs_f64 to avoid a panic.
57+
// TODO: put this in the Duration::try_from_object impl, maybe?
58+
let micros = timeout * 1_000_000.0;
59+
let nanos = timeout * 1_000_000_000.0;
60+
if micros > PY_TIMEOUT_MAX as f64 || nanos < 0.0 || !nanos.is_finite() {
61+
return Err(vm.new_overflow_error(
62+
"timestamp too large to convert to Rust Duration".to_owned(),
63+
));
64+
}
65+
5266
Ok(mu.try_lock_for(Duration::from_secs_f64(timeout)))
5367
}
5468
false if timeout != -1.0 => {
@@ -59,6 +73,21 @@ macro_rules! acquire_lock_impl {
5973
}
6074
}};
6175
}
76+
macro_rules! repr_lock_impl {
77+
($zelf:expr) => {{
78+
let status = if $zelf.mu.is_locked() {
79+
"locked"
80+
} else {
81+
"unlocked"
82+
};
83+
format!(
84+
"<{} {} object at {}>",
85+
status,
86+
$zelf.class().name,
87+
$zelf.get_id()
88+
)
89+
}};
90+
}
6291

6392
#[pyclass(name = "lock")]
6493
struct PyLock {
@@ -102,6 +131,11 @@ impl PyLock {
102131
fn locked(&self) -> bool {
103132
self.mu.is_locked()
104133
}
134+
135+
#[pymethod(magic)]
136+
fn repr(zelf: PyRef<Self>) -> String {
137+
repr_lock_impl!(zelf)
138+
}
105139
}
106140

107141
type RawRMutex = RawReentrantMutex<RawMutex, RawThreadId>;
@@ -149,6 +183,11 @@ impl PyRLock {
149183
fn exit(&self, _args: PyFuncArgs) {
150184
self.release()
151185
}
186+
187+
#[pymethod(magic)]
188+
fn repr(zelf: PyRef<Self>) -> String {
189+
repr_lock_impl!(zelf)
190+
}
152191
}
153192

154193
fn thread_get_ident() -> u64 {
@@ -195,12 +234,16 @@ fn thread_start_new_thread(
195234
}
196235
SENTINELS.with(|sents| {
197236
for lock in sents.replace(Default::default()) {
198-
lock.release()
237+
lock.mu.unlock()
199238
}
200-
})
239+
});
240+
vm.state.thread_count.fetch_sub(1);
201241
});
202-
res.map(|handle| thread_to_id(&handle.thread()))
203-
.map_err(|err| super::os::convert_io_error(vm, err))
242+
res.map(|handle| {
243+
vm.state.thread_count.fetch_add(1);
244+
thread_to_id(&handle.thread())
245+
})
246+
.map_err(|err| super::os::convert_io_error(vm, err))
204247
}
205248

206249
thread_local!(static SENTINELS: RefCell<Vec<PyLockRef>> = RefCell::default());
@@ -217,6 +260,10 @@ fn thread_stack_size(size: OptionalArg<usize>, vm: &VirtualMachine) -> usize {
217260
vm.state.stacksize.swap(size)
218261
}
219262

263+
fn thread_count(vm: &VirtualMachine) -> usize {
264+
vm.state.thread_count.load()
265+
}
266+
220267
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
221268
let ctx = &vm.ctx;
222269

@@ -228,6 +275,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
228275
"start_new_thread" => ctx.new_function(thread_start_new_thread),
229276
"_set_sentinel" => ctx.new_function(thread_set_sentinel),
230277
"stack_size" => ctx.new_function(thread_stack_size),
278+
"_count" => ctx.new_function(thread_count),
231279
"error" => ctx.exceptions.runtime_error.clone(),
232280
"TIMEOUT_MAX" => ctx.new_float(TIMEOUT_MAX),
233281
})

vm/src/vm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub struct PyGlobalState {
7676
pub stdlib_inits: HashMap<String, stdlib::StdlibInitFunc>,
7777
pub frozen: HashMap<String, bytecode::FrozenModule>,
7878
pub stacksize: AtomicCell<usize>,
79+
pub thread_count: AtomicCell<usize>,
7980
}
8081

8182
pub const NSIG: usize = 64;
@@ -207,6 +208,7 @@ impl VirtualMachine {
207208
stdlib_inits,
208209
frozen,
209210
stacksize: AtomicCell::new(0),
211+
thread_count: AtomicCell::new(0),
210212
}),
211213
initialized: false,
212214
};

0 commit comments

Comments
 (0)