Sorry, but a long and slightly complicated question, for a hypotetical case.
I wanted to serve pages in my blog. The blog doesn't actually exist yet (but works locally, need to find out how I can safely host it later...), but lets assume it becomes viral, and by viral i mean the entire internet has decided to use it. And they are all crazy picky about loading times....
I haven't figued out the structure of the Page objects yet, but for the question they can be like the last question:
#[derive(Clone)]
pub struct Page<'a> {
pub title: &'a str,
pub endpoint: &'a str,
}
I wanted to create a HashMap that held all my pages, and when I updated a source file, the a thread would replace that page in the mapping. It's rather trivial of a problem really. I didnt find out if I could update a mapping from a thread, so I decided to make each value something that could hould a page and have the page object replaced on demand. It made somewhat sense since I don't need to delete a page.
There is a trivial solution. And it's just to have each HashMap value be a RwLock with an Arc holding my large string. No lagre string copies, Arc make it shared, and RwLock is fine since any number of readers can exist. Only when writing is the readers locked. Good enough really.
But I heard about DoubleBuffers, and though, why can't I have a AtomicPointer to my data that always exist? Some work later and I had something holding an AtomicPointer with a reference to an Arc with my Page type. But it didn't work. It actually failed rather confusingly. It crashed as I was trying to read the title on my Page object after getting it from the Arc. It wasn't even any thread stuff going on, reading once works, the next time it crashed.
struct SharedPointer<T> {
data: AtomicPtr<Arc<T>>,
}
impl<T> SharedPointer<T> {
pub fn new(initial_value: T) -> SharedPointer<T> {
SharedPointer {
data: AtomicPtr::new(&mut Arc::new(initial_value)),
}
}
pub fn read(&self) -> Arc<T> {
unsafe { self.data.load(Relaxed).read_unaligned() }.clone()
}
pub fn swap(&self, new_value: T) {
self.data.store(&mut Arc::new(new_value), Relaxed)
}
}
#[test]
pub fn test_swapping_works_2() {
let page2: Page = Page::new("test2", "/test2");
let page: Page = Page::new("test", "/test");
let entry: SharedPointer<Page> = SharedPointer::new(page.clone());
let mut value = entry.read();
assert_eq!(value.title, page.title);
value = entry.read();
assert_eq!(value.title, page.title);
entry.swap(page2.clone());
let value2 = entry.read();
assert_eq!(value2.title, page2.title);
assert_eq!(value.title, page.title);
}
This has undefined behavior, which isn't too surprising since I don't understand pointers that much... and I'm actually calling unsafe code. I have heard it can produce unexpected error outside it's block. I'm just surprised it works a little. This code sometimes fails the second assert with an empty string, crashes with access violation, or one time it gave me a comparison where some of it was lots of question marks! My best understanding is that my Page or it's content is moved or deallocated, but odd that my Arc seems perfectly fine. I just don't see the connection between the pointer and Arcs content causing a crash.
I may just be doing the entire thing wrong, so sticking with RwLock is much better and safer since there is no unsafe code. But I seek to know why this is so bad in the first place. What is wrong here, and is there a remedy? Or is it just fundamentally wrong?