Global statics: Do I need a fence, mutex or something else?

I'm trying to make a global static COOKIE (a usize value) that's valid for as long as there's at least one instance of a GlobalCookie struct.

So the goal here is that after GlobalCookie::new() returns:

  • The COOKIE value will be valid for at least the lifetime of the GlobalCookie instance.
  • Each and every COOKIE value will need to be cleaned up, but it doesn't really matter when so long as the previous point remains true.

An alternative to using globals here is to create a new cookie for each instance of GlobalCookie and store the value in each struct. But I do want to avoid (potentially expensive) initialization and cleanup hence I'm using a static COOKIE and a COUNTER value to track usage.

It's probably easier if I explain what I mean alongside the code I have so far:

use std::sync::atomic::{AtomicUsize, Ordering};

static COUNTER: AtomicUsize = AtomicUsize::new(0);
static COOKIE: AtomicUsize = AtomicUsize::new(0);

struct GlobalCookie;

// This is the public interface. This ensures that a cookie value exists at
// least as long as there's one instance of `GlobalCookie`. Once every
// `GlobalCookie` has been dropped, then there is no more cookie until a new
// instance is created.
impl GlobalCookie {
    pub fn new() -> Self {
        let old = COUNTER.fetch_add(1, Ordering::Relaxed);
        if old == 0 {
            // Create and store a new `COOKIE` value.
            let cookie = Self::initialize_cookie();
            COOKIE.store(cookie, Ordering::SeqCst);
        }
        Self {}
    }
}
impl Drop for GlobalCookie {
    fn drop(&mut self) {
        // Store the cookie so I can use it for cleanup if COUNTER drops to zero.
        let old_cookie = COOKIE.load(Ordering::SeqCst);
        
        // Decrement the counter and cleanup the cookie if it's zero.
        let old_count = COUNTER.fetch_sub(1, Ordering::Relaxed);
		if old_count == 1 { // COUNTER is now 0
			Self::cleanup_cookie(old_cookie);
		}
    }
}

Playground link.

My question is, do I need a fence, mutex or other lock around creating and dropping the GlobalCookie? Or is there a much better way to do this?

This is just reference counting, isn't it? Why not use Arc<usize> instead? (Or if you need to run custom destruction code, a newtype wrapper around a usize that impls Drop by calling cleanup_cookie().)

Sure, it's just reference counting unless there's a better way. But Arc::new can't be used in statics and I don't particularly need heap allocation (though I guess it wouldn't hurt either).

I'll also need custom initialization and drop code code but, as you say, that could be achieved using a new type wrapper.

It doesn't help with heap allocation but you can put your Arc in a once_cell.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.