For my thread-local allocator Local, I have made it !Send and !Sync.
I was just thinking, do I only need !Send to make sure something allocated from Local by one thread cannot be deallocated by a different thread?
For my thread-local allocator Local, I have made it !Send and !Sync.
I was just thinking, do I only need !Send to make sure something allocated from Local by one thread cannot be deallocated by a different thread?
Looking here
there is an example
“A nice example where this does not happen is with a MutexGuard: notice how it is not Send. The implementation of MutexGuard uses libraries that require you to ensure you don’t try to free a lock that you acquired in a different thread.“
which I think explains it is Send that matters.
Allocator::deallocate takes &self, not &mut self.
therefore there is nothing stopping it from being called on a different thread than where the allocator was created if the allocator is Sync.
for that to happen, a struct must have a method that allocates by &self, which i don't believe there is in std, but the existence of such a method woud be perfectly sound.
edit : i forgot about the most crucial point :
impl<A> Allocator for &A
where
A: Allocator + ?Sized,
so yeah, definitely make your allocator !Sync
the allocator itself is probably !Send !Sync, the allocations created by it are !Send but could be Sync i think
Here is a short test program to explain a bit what is going on here:
use pstd::localalloc::*;
use pstd::*;
type XBox<T> = BoxA<T, Perm>;
fn main() {
use std::sync::mpsc::channel;
use std::thread;
let (sender, receiver) = channel();
thread::spawn(move || {
sender.send(XBox::new(99)).unwrap();
});
let msg = receiver.recv().unwrap();
assert_eq!(99, *msg);
}
This compiles and runs ok. But if
type XBox<T> = BoxA<T, Perm>;
is changed to
type XBox<T> = BoxA<T, Local>;
it will not compile, giving an error that starts
error[E0277]: `NonNull<()>` cannot be sent between threads safely
In other words an XBox allocated from Local cannot be sent to another thread, which is what is needed. At any rate, I am now fairly convinced that it is Send which matters here, not Sync, and I updated the crate to reflect that.
but that is not correct, as i explained in my comment above. BoxA<T, Local> cannot be sent to another thread, sure, but BoxA<T, &Local>, which is allocated from Local can
But that is ok, because it is borrowed not owned, and you need ownership to drop/deallocate it.
You only need ownership of the Box to deallocate, not ownership of Local.
Box<T,&Local> is an owned type
Hmm, I don’t yet see how it doesn’t work, in other words can you show an example program that compiles, allocates from Local on one thread and deallocates on another?
here is the example
use pstd::localalloc::Local;
use pstd::*;
fn main() {
use std::sync::mpsc::channel;
use std::thread;
let allo = &Local::new();
thread::scope(|s| {
let (sender, receiver) = channel();
s.spawn(move || {
sender.send(BoxA::<i32, &Local>::new_in(99, allo)).unwrap();
});
let msg = receiver.recv().unwrap();
assert_eq!(99, *msg);
});
}
i guess i could have been clearer than
, and i wil be more careful next time. making your allocator Sync is unsound if it can't support allocating/deallocating from different threads.
you prob should yank pstd 1.0.107
I will. I get it now, sorry for being slow!
for similar question next time, i would recommend getting at least one person to agree with you that it's a good idea before taking action(unless no one answers in a really long time). if you are right and the people who disagree with you are wrong, you should be able to convince someone that you are correct, even if it takes some time.
if you can't convince anyone, it's prob a bad sign.
and it can happen that many people instictually disagree with you when you are right tbf
(personal example fn drop_in_place<T>(v : &'static mut T) being safe)