Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should RawWindowHandle implement Send? #85

Closed
maroider opened this issue Dec 5, 2021 · 5 comments
Closed

Should RawWindowHandle implement Send? #85

maroider opened this issue Dec 5, 2021 · 5 comments

Comments

@maroider
Copy link
Member

maroider commented Dec 5, 2021

Windows are apparently effectively !Send on Windows and macOS, and the lifetime issues that Android's window handles have make marking window handles Send perhaps a bit suspect.

I believe we should treat RawWindowHandles somewhat like pointers, in that they can be constructed by safe code, but can't be used directly without unsafe. Thus, we should make the handles Send, but provide enough information in order for user code to tell if they're using the handle on the correct thread. Additionally, any API creating ready-made RawWindowHandles must be unsafe, as is already the case with HasRawWindowHandle. This all assumes we can find a workable solution to #84.

@Lokathor
Copy link
Contributor

Lokathor commented Dec 6, 2021

I believe we should treat RawWindowHandles somewhat like pointers, in that they can be constructed by safe code, but can't be used directly without unsafe. Thus, we should make the handles Send, but provide enough information in order for user code to tell if they're using the handle on the correct thread.

Except pointers are !Send

And this isn't a super big deal because (unlike Copy) a type can always just force Send onto whatever data it wants.

@MaulingMonkey
Copy link

I can't speak to macOS, and I don't particularly have a need for RawWindowHandle to be Send, but I want to chime in on the
windows side of things conceptually. I'm going to call a Windows Window a WND, even though no such win32 type exists.

  • WND: !Send, as you say.
  • WND: Sync, however, per Raymond Chen / The Old New Thing.
  • HWND ~ &WND / Option<Weak<WND>>, where WND has a ton of interior mutability:
    • Some Mutex-based (message queues?)
    • Some atomic
    • Some closer to UnsafeCell, caller beware.
  • &T: Send if T: Sync
  • ergo it'd be sane for HWND: Send given WND: Sync (although winapi doesn't implement Send for HWND currently.)
  • ergo it'd be sane for RawWindowHandle: Send, even though HasRawWindowHandle likely can't be.

Methods like PostMessageW etc. exist explicitly to interact with HWNDs belonging to another thread, and plenty of APIs exist to access HWNDs referencing other processes you don't control (GetDesktopWindow, GetForegroundWindow, GetShellWindow, GetConsoleWindow, ...).

This isn't to say windows don't have thread affinity - they do. A window is owned by a thread and cannot be transfered. But they can be referenced by other threads, and the HWND is effecitvely a weak ptr/reference to said window.

You might wish to argue HWND ~ &mut WND, given the mutable nature of the underlying window. I disagree: &mut should be pronounced "exclusive reference", and there's nothing even remotely exclusive about a HWND reference, and trying to deny that reality has caused me nothing but suffering and pain.

HWNDs are fundamentally unlockable, weak, data-racey, shared, and fragile, even in single threaded code, due to the incredibly recursion-prone nature of wndprocs. A not-uncommon bug in C++ codebases is for something to create a dialog (perhaps to open a file, perhaps to show an assertion message). These dialogs pump the message loop despite being in a wndproc. Any windows not specified as the parent of the dialog (of which there will always be at least 1 in a multi-window application) may be closed out from underneath their currently executing wndprocs or render loops! Any attempt to impose static lifetimes on general HWNDs is a fool's errand.

That leaves runtime enforcement of lifetimes. Fortunately there are many options:

  • You can detect when a window is destroyed via message hooks, or by overriding an hwnd's wndproc with a wrapper, and:
    • Destroy Direct3D devices etc. depending on said window before it's destroyed
    • Invalidate any weak HWND references you might be holding onto
    • Panic/bug/failfast/abort/hang before undefined behavior can occur if something still insists on "locking" the window.
  • Window classes you author can defer destruction (to e.g. your main loop?) and merely hide the window when "closed".
    • This won't prevent some random jerk from calling DestroyWindow on your window.
    • This will prevent triggering any panic/bug/failfast/abort s in the regular execution of reasonable codebases.
  • You can validate a given HWND is valid, and belongs to the current thread:
    • Check GetWindowThreadProcessId(hwnd, nullptr) == GetCurrentThreadId()
    • Seems fine even with never-valid hwnds in testing
  • Much of Win32 already guards against invalid HWNDs without any need for extra checks by Rust code, returning ERROR_INVALID_WINDOW_HANDLE (or ERROR_ACCESS_DENIED for hwnds referencing other threads) and not crashing no matter how much I attempt to break the functions in question.

@kchibisov
Copy link
Member

On Wayland the raw window handle is Send + Sync for example.

However I do have a question on how it interacts with the graphics API usually? For example if I want to render a window on a different thread, I must pass wl_surface to that thread and create EGLSurface for it, what should be done here on e.g. Windows and macOS? Can I pass handle to simple create a EGLSurface on windows from different thread? Since I'm pretty sure that you can't just send EGLSurface (on Wayland you'll likely segfault later if you do that)?

@notgull
Copy link
Member

notgull commented Mar 17, 2023

The thing with X11 is that whether or not the windows/displays are thread safe depends. libxcb is thread safe, but Xlib's thread safety depends on whether you have called XInitThreads. In addition, I'm also relatively sure that macOS's primitives are thread-unsafe.

My answer would be "no", since, even if the user wants to send a Raw[Display/Window]Handle to another thread, they can just wrap it in a structure that implements unsafe Send and then make sure they adhere to whatever the platform-specific guidelines are.

@notgull
Copy link
Member

notgull commented May 20, 2024

Closing this issue as I believe the long-standing consensus is now "window handles are !Send/!Sync".

@notgull notgull closed this as completed May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

5 participants