Need help fencing my threads

Here is the situation. I have a container moving between threads, holding a maybe uninitialized value. I keep track of the initialization status using a boolean. It looks like this:

struct Wrap {
	initialized: bool,
	value: MaybeUninit<ManuallyDrop<T>>
}

Don't ask me why I'm doing such nasty stuff, its for the greater good. Now I provide one method to create an uninitialized Wrap and one method to initialize it. Of course the later can be called from any thread.

impl Wrap {
	pub fn new() {
		Wrap {
			initialized: false,
			value: MaybeUninit::uninit()
		}
	}
	
	/// This can be called from any thread
	pub fn init(&mut self, value: T) {
                /// scary section
		self.initialized = true;
		self.value = MaybeUninit::new(ManuallyDrop::new(value))
                /// end of the scary section
	}
}

Now comes the very scary part. I will need to drop the value someday.

impl Drop for Wrap {
	/// This can be called from any thread
	fn drop(&mut self) {
                /// very scary section
		if self.initialized {
			unsafe {
				ManuallyDrop::drop(self.value.get_mut())
			}
		}
                /// end of the very scary section
	}
}

Since init and drop may be called from different threads, and since init may never be called at all, I need to be really sure that the content of the init method happens atomically (so that I cannot free uninitialized memory), and that the value of initialized is propagated to the other threads before the drop function is called (so that I can't have a memory leak).

One very important detail, I want to avoid the use of a Mutex. I was imagining something using AtomicBool. But if I know something about concurrency and the std::sync::atomic module is that 1) things have a great tendency to go wrong 2) I don't understand the semantics of Ordering well enough to do it correctly. I need help.

Two questions then: Is it possible to pull that off without locking? How?

Thanks in advance :slight_smile:

It seems like you're reimplementing OnceCell<T>

1 Like

I recommend using an existing solution, but here's some review of the code: Using a MaybeUninit<T> is all that is necessary. You can use drop_in_place to drop its value.

Additionally the fact that init takes &mut self removes the need for synchronization, because &mut self methods can only be called when the compiler can prove you have exclusive access. The same applies to drop.

Thanks :slight_smile:

It seems that you'r right, and looking at the implementation of OnceCell it is far more complicated than what I had in mind... too bad.

Although there is one difference with OnceCell that I haven't mentioned: I know that the init method will be called at most once. But I don't see how this simplifies my problem.

Oh yeah, indeed, in the actual code init takes a &self.

If you guarantee that the .init() will be called at most once, you don't need to worry to drop the inner T concurrently by reassigning.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.