Skip to content

Conversation

@joseluis
Copy link
Contributor

@joseluis joseluis commented Feb 3, 2025

I wanted to be able to start and stop a miniquad context and after trying several solutions to make NATIVE_DISPLAY droppable (e.g. using RwLock), I believe this one with AtomicPtr plus an static AtomicBool has a very good balance between performance and security.

A simple test:

use miniquad::*;

struct Stage {
    ctx: GlContext,
}
impl EventHandler for Stage {
    fn update(&mut self) {}
    fn draw(&mut self) {}
}
fn main() {
    println!("first context\nclose the window to create a second context.");
    miniquad::start( conf::Conf::default(), || { Box::new(Stage { ctx: GlContext::new(), }) });
    println!("second context");
    miniquad::start( conf::Conf::default(), || { Box::new(Stage { ctx: GlContext::new(), }) });
}

I also got the idea of the native_display function wrappers from #505 to improve ergonomics.

EDIT: This commit would effectively lower MSRV to 1.64. Related: #524.

- reduce locking overhead by replacing `OnceLock<Mutex<T>>` with `AtomicPtr<Mutex<T>>`.
- add `drop_display` to allow safely resetting `NATIVE_DISPLAY` while ensuring proper deallocation.
- implement `Drop` for `GlContext` and `MetalContext` to guarantee cleanup on context destruction.
- ensure memory safety using `Release/Acquire` ordering and explicit deallocation.
- add `native_display_blocking` and `native_display_nonblocking` for safer access.
- update documentation and rustfmt.
@narodnik
Copy link
Contributor

You have a Mutex, so it makes no sense to wrap the Mutex in an AtomicPtr. Why not just do this?

/// This for now is Android specific since the process can continue running but the display
/// is restarted. We support reinitializing the display.
fn set_or_replace_display(display: native::NativeDisplayData) {
    if let Some(m) = NATIVE_DISPLAY.get() {
        // Replace existing display
        *m.lock().unwrap() = display;
    } else {
        // First time initialization
        set_display(display);
    }
}

@joseluis
Copy link
Contributor Author

joseluis commented Jul 8, 2025

@narodnik A single mutex can handle updates but cannot safely manage dropping. The core issue is twofold: holding the mutex during drop risks deadlocks (particularly if the destructor requires synchronization), while releasing it first risks use-after-free as other threads might access the data mid-destruction.

The proposed atomic solution solves this through two key mechanisms: atomic pointer swaps replace data without holding the mutex during destruction, while the ready-flag prevents access during teardown.

Additionally the ready-flag enables lightweight, contention-free initialization checks, while the atomic operations ensure clean state transitions, and the destructors remain safe even when accessing thread-local storage or acquiring other locks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants