Just use Cell
This commit is contained in:
parent
611ea4db49
commit
e0602b641c
|
@ -1,6 +1,6 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyTuple};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::cell::Cell;
|
||||
|
||||
/// A function decorator that keeps track how often it is called.
|
||||
///
|
||||
|
@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
|||
pub struct PyCounter {
|
||||
// Keeps track of how many calls have gone through.
|
||||
//
|
||||
// See the discussion at the end for why an atomic is used.
|
||||
count: AtomicU64,
|
||||
// See the discussion at the end for why `Cell` is used.
|
||||
count: Cell<u64>,
|
||||
|
||||
// This is the actual function being wrapped.
|
||||
wraps: Py<PyAny>,
|
||||
|
@ -26,14 +26,14 @@ impl PyCounter {
|
|||
#[new]
|
||||
fn __new__(wraps: Py<PyAny>) -> Self {
|
||||
PyCounter {
|
||||
count: AtomicU64::new(0),
|
||||
count: Cell::new(0),
|
||||
wraps,
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn count(&self) -> u64 {
|
||||
self.count.load(Ordering::Relaxed)
|
||||
self.count.get()
|
||||
}
|
||||
|
||||
#[args(args = "*", kwargs = "**")]
|
||||
|
@ -43,10 +43,12 @@ impl PyCounter {
|
|||
args: &PyTuple,
|
||||
kwargs: Option<&PyDict>,
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
let old_count = self.count.fetch_add(1, Ordering::Relaxed);
|
||||
let old_count = self.count.get();
|
||||
let new_count = old_count + 1;
|
||||
self.count.set(new_count);
|
||||
let name = self.wraps.getattr(py, "__name__")?;
|
||||
|
||||
println!("{} has been called {} time(s).", name, old_count + 1);
|
||||
println!("{} has been called {} time(s).", name, new_count);
|
||||
|
||||
// After doing something, we finally forward the call to the wrapped function
|
||||
let ret = self.wraps.call(py, args, kwargs)?;
|
||||
|
|
|
@ -66,7 +66,7 @@ def Counter(wraps):
|
|||
return call
|
||||
```
|
||||
|
||||
### What are the atomics for?
|
||||
### What is the `Cell` for?
|
||||
|
||||
A [previous implementation] used a normal `u64`, which meant it required a `&mut self` receiver to update the count:
|
||||
|
||||
|
@ -103,7 +103,7 @@ say_hello()
|
|||
# RuntimeError: Already borrowed
|
||||
```
|
||||
|
||||
The implementation in this chapter fixes that by never borrowing exclusively; all the methods take `&self` as receivers, of which multiple may exist simultaneously. This requires a shared and threadsafe counter and the easiest way to do that is to use atomics, so that's what is used here.
|
||||
The implementation in this chapter fixes that by never borrowing exclusively; all the methods take `&self` as receivers, of which multiple may exist simultaneously. This requires a shared counter and the easiest way to do that is to use [`Cell`], so that's what is used here.
|
||||
|
||||
This shows the dangers of running arbitrary Python code - note that "running arbitrary Python code" can be far more subtle than the example above:
|
||||
- Python's asynchronous executor may park the current thread in the middle of Python code, even in Python code that *you* control, and let other Python code run.
|
||||
|
@ -112,4 +112,5 @@ This shows the dangers of running arbitrary Python code - note that "running arb
|
|||
|
||||
This especially important if you are writing unsafe code; Python code must never be able to cause undefined behavior. You must ensure that your Rust code is in a consistent state before doing any of the above things.
|
||||
|
||||
[previous implementation]: https://github.com/PyO3/pyo3/discussions/2598 "Thread Safe Decorator <Help Wanted> · Discussion #2598 · PyO3/pyo3"
|
||||
[previous implementation]: https://github.com/PyO3/pyo3/discussions/2598 "Thread Safe Decorator <Help Wanted> · Discussion #2598 · PyO3/pyo3"
|
||||
[`Cell`]: https://doc.rust-lang.org/std/cell/struct.Cell.html "Cell in std::cell - Rust"
|
Loading…
Reference in New Issue