complete_write()

I think complete_write is the first function I wrote for the vector's core operations. We execute the WriteDescriptor passed in and set the one stored in the vector to None

Here's what the function signature looks like:

#![allow(unused)]
fn main() {
fn complete_write(&self, pending: &Option<WriteDescriptor<T>>) {

}

The first thing we do is execute the WriteDescriptor, if there is one. We can use if let syntax to concisely express this. The result of the compare_exchange doesn't matter. If it succeeds, we performed the write. If it doesn't, someone else performed it. Also, notice how we are compare_exchangeing an AtomicU64. The data is transmuted into those bytes, allowing us to make atomic modifications to the contents of the vector. Because the data needs to be transmuted into an atomic type, the vector can't support types larger than 8 bytes. Finally, because we are using AcqRel as the success ordering, any subsequent Acquire loads will see the that there is no pending write.

#![allow(unused)]
fn main() {
    #[allow(unused_must_use)]
    if let Some(writedesc) = pending {
        AtomicU64::compare_exchange(
            writedesc.location,
            writedesc.old,
            writedesc.new,
            Ordering::AcqRel,
            Ordering::Relaxed,
        );

}

Now that we've done the store to the contents, we change the WriteDescriptor status of the vector to indicate that there is no pending write (we just took care of it!).

#![allow(unused)]
fn main() {
        let new_writedesc = WriteDescriptor::<T>::new_none_as_ptr();
        // # Safety
        // The pointer is valid to dereference because it started off valid
        // and only pointers made from WriteDescriptor::new_*_as_ptr()
        // (which are valid because of Box) are CAS'd in
        unsafe { &*self.descriptor.load(Ordering::Acquire) } // Loading with `Acquire`
            .pending
            .store(new_writedesc, Ordering::Release); // Storing with `Release`

        // Memory leak alert!
        // What happens to the old pointer stored in the
        // `AtomicPtr<Option<WriteDescriptor<T>>>`?
        // We never reclaim it.
    }
}

}

This is standard. We make a new WriteDescriptor and store it with Release so that all subsequent Acquire loads will see it.

Leaking memory

Leaking memory is when you use memory (allocating) but never free it. This is the first chunk of code that leaks. Our Descriptor has a pointer to an Option<WriteDescriptor>. When we store a different pointer, we lose the old pointer forever. Since we never do anything to deallocate the memory pointed to by the old pointer, like Box::from_raw, that memory will stay allocated until the end of the program.

We can't just directly free the memory right away though, as there could be another thread reading it. Later on, I'm going to show you how we can use a technique called hazard pointers to safely reclaim (deallocate) objects.

For now, the vector will stay leaky, and we'll move on the push.


Complete source for complete_write()

#![allow(unused)]
fn main() {
fn complete_write(&self, pending: &Option<WriteDescriptor<T>>) {
    #[allow(unused_must_use)]
    if let Some(writedesc) = pending {
        AtomicU64::compare_exchange(
            writedesc.location,
            writedesc.old,
            writedesc.new,
            Ordering::AcqRel,
            Ordering::Relaxed,
        );
        let new_writedesc = WriteDescriptor::<T>::new_none_as_ptr();
        // # Safety
        // The pointer is valid to dereference because it started off valid
        // and only pointers made from WriteDescriptor::new_*_as_ptr()
        // (which are valid because of Box) are CAS'd in
        unsafe { &*self.descriptor.load(Ordering::Acquire) }
            .pending
            .store(new_writedesc, Ordering::Release);
    }
}

}