Skip to content

Commit ef12e44

Browse files
committed
crossbeam-epoch
1 parent 06ba89c commit ef12e44

11 files changed

Lines changed: 48 additions & 190 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ serde = { version = "1.0.225", default-features = false }
196196
schannel = "0.1.28"
197197
scoped-tls = "1"
198198
scopeguard = "1"
199+
memoffset = "0.9"
200+
atomic = "0.5"
199201
static_assertions = "1.1"
200202
strum = "0.27"
201203
strum_macros = "0.27"

crates/common/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ parking_lot = { workspace = true, optional = true }
3131
unicode_names2 = { workspace = true }
3232
radium = { workspace = true }
3333

34+
# EBR - Epoch-Based Reclamation
35+
crossbeam-epoch = "0.9"
36+
3437
lock_api = "0.4"
3538
siphasher = "1"
3639
num-complex.workspace = true

crates/common/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod boxvec;
1414
pub mod cformat;
1515
#[cfg(any(unix, windows, target_os = "wasi"))]
1616
pub mod crt_fd;
17+
pub use crossbeam_epoch as epoch;
1718
pub mod encodings;
1819
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
1920
pub mod fileutils;

crates/common/src/refcount.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,26 @@ use std::cell::{Cell, RefCell};
77
use std::sync::atomic::{AtomicU64, Ordering};
88

99
// Re-export EBR types
10-
pub use crate::ebr::{Guard, HIGH_TAG_WIDTH, cs, global_epoch};
10+
pub use crate::epoch::Guard;
11+
12+
/// Epoch tag bit width - crossbeam-epoch doesn't use pointer tagging
13+
pub const HIGH_TAG_WIDTH: u32 = 0;
14+
15+
/// Re-export pin() as cs() for API compatibility
16+
pub use crate::epoch::pin as cs;
1117

1218
// ============================================================================
13-
// Constants from circ::utils - now defined locally
19+
// Constants for state layout
1420
// ============================================================================
1521

1622
pub const EPOCH_WIDTH: u32 = HIGH_TAG_WIDTH;
1723
pub const EPOCH_MASK_HEIGHT: u32 = u64::BITS - EPOCH_WIDTH;
18-
pub const EPOCH: u64 = ((1 << EPOCH_WIDTH) - 1) << EPOCH_MASK_HEIGHT;
24+
/// Epoch mask - 0 when EPOCH_WIDTH is 0 (no epoch bits used)
25+
pub const EPOCH: u64 = if EPOCH_WIDTH == 0 {
26+
0
27+
} else {
28+
((1u64 << EPOCH_WIDTH) - 1) << EPOCH_MASK_HEIGHT
29+
};
1930
pub const DESTRUCTED: u64 = 1 << (EPOCH_MASK_HEIGHT - 1);
2031
pub const WEAKED: u64 = 1 << (EPOCH_MASK_HEIGHT - 2);
2132
pub const TOTAL_COUNT_WIDTH: u32 = u64::BITS - EPOCH_WIDTH - 2;
@@ -74,12 +85,20 @@ impl State {
7485

7586
#[inline]
7687
pub fn epoch(self) -> u32 {
77-
((self.inner & EPOCH) >> EPOCH_MASK_HEIGHT) as u32
88+
if EPOCH_WIDTH == 0 {
89+
0
90+
} else {
91+
((self.inner & EPOCH) >> EPOCH_MASK_HEIGHT) as u32
92+
}
7893
}
7994

8095
#[inline]
8196
pub fn with_epoch(self, epoch: usize) -> Self {
82-
Self::from_raw((self.inner & !EPOCH) | (((epoch as u64) << EPOCH_MASK_HEIGHT) & EPOCH))
97+
if EPOCH_WIDTH == 0 {
98+
self
99+
} else {
100+
Self::from_raw((self.inner & !EPOCH) | (((epoch as u64) << EPOCH_MASK_HEIGHT) & EPOCH))
101+
}
83102
}
84103

85104
#[inline]
@@ -192,12 +211,20 @@ impl PyState {
192211

193212
#[inline]
194213
pub fn epoch(self) -> u32 {
195-
((self.inner & EPOCH) >> EPOCH_MASK_HEIGHT) as u32
214+
if EPOCH_WIDTH == 0 {
215+
0
216+
} else {
217+
((self.inner & EPOCH) >> EPOCH_MASK_HEIGHT) as u32
218+
}
196219
}
197220

198221
#[inline]
199222
pub fn with_epoch(self, epoch: usize) -> Self {
200-
Self::from_raw((self.inner & !EPOCH) | (((epoch as u64) << EPOCH_MASK_HEIGHT) & EPOCH))
223+
if EPOCH_WIDTH == 0 {
224+
self
225+
} else {
226+
Self::from_raw((self.inner & !EPOCH) | (((epoch as u64) << EPOCH_MASK_HEIGHT) & EPOCH))
227+
}
201228
}
202229

203230
#[inline]

crates/vm/src/gc_state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ impl GcState {
389389
// This ensures that any objects being freed by other threads won't have
390390
// their memory actually deallocated until we exit this critical section.
391391
// Other threads' deferred deallocations will wait for us to unpin.
392-
let ebr_guard = rustpython_common::ebr::cs();
392+
let ebr_guard = rustpython_common::epoch::pin();
393393

394394
// Memory barrier to ensure visibility of all reference count updates
395395
// from other threads before we start analyzing the object graph.

crates/vm/src/object/core.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -924,18 +924,6 @@ impl PyObject {
924924
self.set_gc_finalized();
925925
call_slot_del(self, slot_del)?;
926926
}
927-
if let Some(wrl) = self.weak_ref_list() && Some(slot_del) = del {
928-
wrl.clear();
929-
// Check if already finalized by GC (prevents double __del__ calls)
930-
let ptr = core::ptr::NonNull::from(self);
931-
let gc = crate::gc_state::gc_state();
932-
if !gc.is_finalized(ptr) {
933-
// Mark as finalized BEFORE calling __del__
934-
// This ensures is_finalized() returns True even if object is resurrected
935-
gc.mark_finalized(ptr);
936-
call_slot_del(self, slot_del)?;
937-
}
938-
}
939927

940928
Ok(())
941929
}

crates/vm/src/stdlib/sys.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ mod sys {
828828

829829
for (thread_id, frame) in frames {
830830
let key = vm.ctx.new_int(thread_id);
831-
dict.set_item(key.as_object(), frame.into(), vm)?;
831+
dict.set_item(key.as_object(), frame.as_object().to_owned(), vm)?;
832832
}
833833

834834
Ok(dict)

crates/vm/src/stdlib/thread.rs

Lines changed: 1 addition & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Implementation of the _thread module
22
#[cfg_attr(target_arch = "wasm32", allow(unused_imports))]
3-
pub(crate) use _thread::{
3+
pub(crate) use self::_thread::{
44
CurrentFrameSlot, HandleEntry, RawRMutex, ShutdownEntry, after_fork_child,
55
get_all_current_frames, get_ident, init_main_thread_ident, make_module,
66
};
@@ -759,136 +759,7 @@ pub(crate) mod _thread {
759759
Ok(())
760760
}
761761

762-
>>>>>>> 884966677 (gc)
763-
#[pyattr]
764-
#[pyclass(module = "_thread", name = "_ExceptHookArgs")]
765-
#[derive(Debug, PyPayload)]
766-
struct ExceptHookArgs {
767-
exc_type: crate::PyObjectRef,
768-
exc_value: crate::PyObjectRef,
769-
exc_traceback: crate::PyObjectRef,
770-
thread: crate::PyObjectRef,
771-
}
772-
773-
#[pyclass(with(Constructor))]
774-
impl ExceptHookArgs {
775-
#[pygetset]
776-
fn exc_type(&self) -> crate::PyObjectRef {
777-
self.exc_type.clone()
778-
}
779-
780-
#[pygetset]
781-
fn exc_value(&self) -> crate::PyObjectRef {
782-
self.exc_value.clone()
783-
}
784-
785-
#[pygetset]
786-
fn exc_traceback(&self) -> crate::PyObjectRef {
787-
self.exc_traceback.clone()
788-
}
789-
790-
#[pygetset]
791-
fn thread(&self) -> crate::PyObjectRef {
792-
self.thread.clone()
793-
}
794-
}
795-
796-
impl Constructor for ExceptHookArgs {
797-
// Takes a single iterable argument like namedtuple
798-
type Args = (crate::PyObjectRef,);
799-
800-
fn py_new(_cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
801-
// Convert the argument to a list/tuple and extract elements
802-
let seq: Vec<crate::PyObjectRef> = args.0.try_to_value(vm)?;
803-
if seq.len() != 4 {
804-
return Err(vm.new_type_error(format!(
805-
"_ExceptHookArgs expected 4 arguments, got {}",
806-
seq.len()
807-
)));
808-
}
809-
Ok(Self {
810-
exc_type: seq[0].clone(),
811-
exc_value: seq[1].clone(),
812-
exc_traceback: seq[2].clone(),
813-
thread: seq[3].clone(),
814-
})
815-
}
816-
}
817-
818-
/// Handle uncaught exception in Thread.run()
819-
#[pyfunction]
820-
fn _excepthook(args: crate::PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
821-
// Type check: args must be _ExceptHookArgs
822-
let args = args.downcast::<ExceptHookArgs>().map_err(|_| {
823-
vm.new_type_error(
824-
"_thread._excepthook argument type must be _ExceptHookArgs".to_owned(),
825-
)
826-
})?;
827-
828-
let exc_type = args.exc_type.clone();
829-
let exc_value = args.exc_value.clone();
830-
let exc_traceback = args.exc_traceback.clone();
831-
let thread = args.thread.clone();
832762

833-
// Silently ignore SystemExit (identity check)
834-
if exc_type.is(vm.ctx.exceptions.system_exit.as_ref()) {
835-
return Ok(());
836-
}
837-
838-
// Get stderr - fall back to thread._stderr if sys.stderr is None
839-
let file = match vm.sys_module.get_attr("stderr", vm) {
840-
Ok(stderr) if !vm.is_none(&stderr) => stderr,
841-
_ => {
842-
if vm.is_none(&thread) {
843-
// do nothing if sys.stderr is None and thread is None
844-
return Ok(());
845-
}
846-
let thread_stderr = thread.get_attr("_stderr", vm)?;
847-
if vm.is_none(&thread_stderr) {
848-
// do nothing if sys.stderr is None and sys.stderr was None
849-
// when the thread was created
850-
return Ok(());
851-
}
852-
thread_stderr
853-
}
854-
};
855-
856-
// Print "Exception in thread {thread.name}:"
857-
let thread_name = if !vm.is_none(&thread) {
858-
thread
859-
.get_attr("name", vm)
860-
.ok()
861-
.and_then(|n| n.str(vm).ok())
862-
.map(|s| s.as_str().to_owned())
863-
} else {
864-
None
865-
};
866-
let name = thread_name.unwrap_or_else(|| format!("{}", get_ident()));
867-
868-
let _ = vm.call_method(
869-
&file,
870-
"write",
871-
(format!("Exception in thread {}:\n", name),),
872-
);
873-
874-
// Display the traceback
875-
if let Ok(traceback_mod) = vm.import("traceback", 0)
876-
&& let Ok(print_exc) = traceback_mod.get_attr("print_exception", vm)
877-
{
878-
use crate::function::KwArgs;
879-
let kwargs: KwArgs = vec![("file".to_owned(), file.clone())]
880-
.into_iter()
881-
.collect();
882-
let _ = print_exc.call_with_args(
883-
crate::function::FuncArgs::new(vec![exc_type, exc_value, exc_traceback], kwargs),
884-
vm,
885-
);
886-
}
887-
888-
// Flush file
889-
let _ = vm.call_method(&file, "flush", ());
890-
Ok(())
891-
}
892763

893764
// Thread-local storage for cleanup guards
894765
// When a thread terminates, the guard is dropped, which triggers cleanup

crates/vm/src/vm/thread.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(feature = "threading")]
22
use crate::frame::FrameRef;
3-
use crate::{AsObject, PyObject, VirtualMachine};
3+
use crate::{AsObject, PyObject, PyObjectRef, VirtualMachine};
44
use core::{
55
cell::{Cell, RefCell},
66
ptr::NonNull,
@@ -27,7 +27,7 @@ thread_local! {
2727

2828
/// Thread-local EBR guard for Coarse-grained pinning strategy.
2929
/// Holds the EBR critical section guard for this thread.
30-
pub(crate) static EBR_GUARD: RefCell<Option<rustpython_common::ebr::Guard>> =
30+
pub(crate) static EBR_GUARD: RefCell<Option<rustpython_common::epoch::Guard>> =
3131
const { RefCell::new(None) };
3232
}
3333

@@ -42,7 +42,7 @@ scoped_tls::scoped_thread_local!(static VM_CURRENT: VirtualMachine);
4242
pub fn ensure_pinned() {
4343
EBR_GUARD.with(|guard| {
4444
if guard.borrow().is_none() {
45-
*guard.borrow_mut() = Some(rustpython_common::ebr::cs());
45+
*guard.borrow_mut() = Some(rustpython_common::epoch::pin());
4646
}
4747
});
4848
}
@@ -56,7 +56,7 @@ pub fn ensure_pinned() {
5656
pub fn reactivate_guard() {
5757
EBR_GUARD.with(|guard| {
5858
if let Some(ref mut g) = *guard.borrow_mut() {
59-
g.reactivate();
59+
g.repin();
6060
}
6161
});
6262
}

0 commit comments

Comments
 (0)