-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Mutator-related GCWork and Send/Sync traits #543
Comments
I think |
Yes. That makes sense. Given that Mutator does not contain types like |
Here is a minimum program that can reproduce the problem. use std::sync::{Arc, RwLock};
use std::thread;
trait GCWork: Send {
fn do_work(&mut self);
}
struct MMTK {
work_packets: RwLock<Vec<Box<dyn GCWork>>>,
}
fn main() {
let mmtk = Arc::new(MMTK {
work_packets: RwLock::new(Vec::new()),
});
thread::spawn(move || { // ERROR
println!("Work packets length: {}", mmtk.work_packets.read().unwrap().len());
}).join().unwrap();
} Since A surprising (to me at least) solution is to replace unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {} Since use std::sync::{Arc, Mutex};
use std::thread;
trait GCWork: Send {
fn do_work(&mut self);
}
struct MMTK {
work_packets: Mutex<Vec<Box<dyn GCWork>>>,
}
fn main() {
let mmtk = Arc::new(MMTK {
work_packets: Mutex::new(Vec::new()),
});
thread::spawn(move || { // OK
println!("Work packets length: {}", mmtk.work_packets.lock().unwrap().len());
}).join().unwrap();
} One important difference between |
That is interesting and makes sense. Probably we should use |
Another interesting observation is that Rust doesn't seem to be smart enough about interface abstraction. See: use std::sync::{Arc, RwLock};
use std::thread;
trait GCWork: Send {
fn do_work(&mut self);
}
struct MMTK {
work_packets: RwLock<Vec<Box<dyn GCWork>>>,
}
trait PacketSource {
fn len(&self) -> usize;
fn get(&self) -> Option<Box<dyn GCWork>>;
}
impl PacketSource for MMTK {
fn len(&self) -> usize {
self.work_packets.read().unwrap().len()
}
fn get(&self) -> Option<Box<dyn GCWork>> {
self.work_packets.write().unwrap().pop()
}
}
fn main() {
let mmtk = Arc::new(MMTK {
work_packets: RwLock::new(Vec::new()),
});
let packet_source = mmtk.clone() as Arc<dyn PacketSource>;
thread::spawn(move || { // ERROR: dyn PacketSource cannot be shared between threads
println!("Work packets length: {}", packet_source.len());
if let Some(mut w) = packet_source.get() {
w.do_work();
}
}).join().unwrap();
} I attempted to hide the implementation details of Obviously, Rust is being overly protective. At some point, someone (either us or some library implementers) must use If a data structure is synchronised, and does not allow accessing the elements in place, it should implement unsafe impl<T: Send> Sync for ArrayQueue<T> {}
unsafe impl<T: Send> Send for ArrayQueue<T> {}
unsafe impl<T: Send> Send for SegQueue<T> {}
unsafe impl<T: Send> Sync for SegQueue<T> {} And I confirmed that replacing use std::thread;
use std::sync::Arc;
use crossbeam::queue::SegQueue;
trait GCWork: Send {
fn do_work(&mut self);
}
struct MMTK {
work_packets: SegQueue<Box<dyn GCWork>>,
}
fn main() {
let mmtk = Arc::new(MMTK {
work_packets: SegQueue::new(),
});
thread::spawn(move || {
println!("Work packets length: {}", mmtk.work_packets.len());
while let Some(mut w) = mmtk.work_packets.pop() {
w.do_work();
}
}).join().unwrap();
} |
A related topic on this: Currently We can check if
We got this with the current version (v0.30): error[E0277]: `(dyn barriers::Barrier<VM> + 'static)` cannot be shared between threads safely
--> src/mmtk.rs:211:19
|
211 | is_sync::<crate::Mutator<VM>>();
| ^^^^^^^^^^^^^^^^^^ `(dyn barriers::Barrier<VM> + 'static)` cannot be shared between threads safely
|
= help: the trait `Sync` is not implemented for `(dyn barriers::Barrier<VM> + 'static)`, which is required by `mutator_context::Mutator<VM>: Sync`
= note: required for `std::ptr::Unique<(dyn barriers::Barrier<VM> + 'static)>` to implement `Sync`
note: required because it appears within the type `Box<(dyn barriers::Barrier<VM> + 'static)>`
--> /opt/rust/toolchains/1.83.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:234:12
|
234 | pub struct Box<
| ^^^
note: required because it appears within the type `mutator_context::Mutator<VM>`
--> src/plan/mutator_context.rs:89:12
|
89 | pub struct Mutator<VM: VMBinding> {
| ^^^^^^^
note: required by a bound in `is_sync`
--> src/mmtk.rs:210:23
|
210 | fn is_sync<T: Sync>() {}
| ^^^^ required by this bound in `is_sync`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `mmtk` (lib) due to 1 previous error There are two different things for this issue:
I think maybe we could avoid using |
Although
Mutator
is supposed to be a thread-local data structure of a mutator thread, someGCWork
instances modifies theMutator
instances on behalf of the mutators. These include:PrepareMutator
ReleaseMutator
ScanStackRoot
Remember that
GCWork
implementsSend
but notSync
. (It isSend
because it can be distributed among workers; it is notSync
because aGCWork
is supposed to be visible to only one thread at a time.)Currently, those structs contain
&mut mutator
.PrepareMutator
is aGCWork
, and it needs to implementsSend
. Because it contains a&mut Mutator
, it requiresMutator
to implementSend
, too. Similar is true forReleaseMutator
andScanStackRoot
.p.s. Changing
&mut Mutator
to&Mutator
will still requireMutator
to implementSend
, becausePrepareMutator
implementsSend
.Can we make
Mutator
implementSend
?(Update: As we discussed later in #543 (comment), it probably should implementMutator
currently implementsSend
, but we probably don't wantMutator
to implementSend
, because it is supposed to be local to a mutator thread.Send
after all. TheSend
trait may make sense during initialization.)If
Mutator
implementsSend
(the status quo), there will be other consequences:PrepareMutator
and its friends contain&mut mutator
.Mutator
to beSend
MutatorContext
is declared aspub trait MutatorContext<VM: VMBinding>: Send + 'static
Mutator
contains aBox<dyn Barrier>
which can contain aObjectRememberingBarrier
.Barrier
to implementSend
.Barrier
is declared aspub trait Barrier: 'static + Send
ObjectRememberingBarrier
contains a&MMTK
.&MMTK
to implementSend
, which in turn requiresMMTK
to implementSync
. (In Rust,T
implementsSync
if and only if&T
implementsSend
)MMTK
contains anArc<GCWorkScheduler>
GCWorkScheduler
contains manyWorkBucket
.WorkBucket
contains manyPrioritizedWork
.PrioritizedWork
contains aBox<dyn GCWork<VM>>
. Because it isdyn
, it can be anyGCWork
, includingPrepareMutator
and others.MMTK
implementsSync
,GCWork
needs to implementSync
. This contradicts with our design thatGCWork
does not implementSync
.From a different point of view, having
ObjectRememberingBarrier
inMutator
closes the loop from one work to any other work. Any work that contains&mut mutator
can potentially see any otherGCWork
instances. This allows one worker to peek into another worker's work. Remember thatGCWork
is notSync
. Not implementingSync
means it cannot be visible to two threads at the same time.But why is it working so far?
In
scheduler.rs
, there is a line:That line broke the dependency chain shown above.
Solution?
Let's think about what should implement
Sync
. Shared data structures need to implementSync
.MMTK
needs to implementSync
.GCWorkScheduler
needs to implementSync
.GCWorkerShared
needs to implementSync
.But we shouldn't need to write anything because the Rust compiler can figure out if
MMTK
satisfies the need ofSync
.And what shouldn't implement
Sync
?Mutator
should not implementSync
, because it should be local to a mutator. But GC also needs to mutate it.Mutator::barrier
: This should probably be notSync
, because only the mutator thread ever execute it. GC never touches it. And to some degree, it shouldn't exist at all, becauseMutator
has a reference toPlan
. A barrier is "what to do when reading/writing a reference field". It is the responsibility of thePlan
which describes the GC algorithm.Mutator::allocator
: This probably belongs to the part shared between the mutator thread and GC threads.Should we do the same refactoring as we did for GC workers, i.e. splitting
Mutator
into a part shared with GC threads, and the part local to the mutator thread? Mutator is#[repr(C)]
, and the structure is visible to the VM.The text was updated successfully, but these errors were encountered: