Just use Cell

This commit is contained in:
mejrs 2022-10-04 19:05:44 +02:00
parent 611ea4db49
commit e0602b641c
2 changed files with 13 additions and 10 deletions

View file

@ -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)?;

View file

@ -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.
@ -113,3 +113,4 @@ 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"
[`Cell`]: https://doc.rust-lang.org/std/cell/struct.Cell.html "Cell in std::cell - Rust"