-
Notifications
You must be signed in to change notification settings - Fork 920
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
Windows: Changed thread_event_target_callback's WM_DESTROY to WM_NCDESTROY #1780
Conversation
Actually seems exactly the same as #1745 but |
@@ -1939,7 +1939,7 @@ unsafe extern "system" fn thread_event_target_callback<T: 'static>( | |||
// the closure to catch_unwind directly so that the match body indendation wouldn't change and | |||
// the git blame and history would be preserved. | |||
let callback = || match msg { | |||
winuser::WM_DESTROY => { | |||
winuser::WM_NCDESTROY => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation says WM_DESTROY
is for destruction of the main window, while WM_NCDESTROY
is for child windows.
Looking at the docs, it seems like winit should be using WM_DESTROY
, what makes you believe that WM_NCDESTROY
is the better choice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because WM_NCDESTROY
is also emitted if there are no child windows I believe. Another solution would be returning immediatly if a WM_NCDESTROY
event is received and still use WM_DESTROY
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But WM_NCDESTROY
being emitted doesn't seem sufficient as a reason to use it? That just seems like a coincidental fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm (obviously) not @VZout, but...
I think you've misunderstood the docs.
When a window receives WM_DESTROY
, three things are promised:
- The window is being destroyed.
- Any child windows still exist (but will soon be destroyed).
- (implicitly) A
WM_NCDESTROY
message will be dispatched soon.
For WM_NCDESTROY
, the following should hold true:
- Any child windows are now destroyed
- MSDN "This message frees any memory internally allocated for the window."
This would suggest that the window can't receive any further messages after WM_NCDESTROY
since Windows has presumably invalidated the window's handle, message queue, and other associated state (see this SO answer).
EDIT: Yikes, didn't see the activity after looking into this (I apparently started writing this minutes before @VZout answered)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your answer was more detailed anyway ♥
CHANGELOG.md
Outdated
@@ -1,5 +1,6 @@ | |||
# Unreleased | |||
|
|||
- On Windows, fix applications not exiting gracefully. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth mentioning the use-after-free (and perhaps how window destruction triggers it) here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
"On Windows, fix applications not exiting gracefully due to thread_event_target_callback
accessing corrupted memory."
or a more detailed version:
"On Windows, fix applications not exiting gracefully due to WM_DESTROY
events destroying subclass_input
but WM_NCDESTOY
is send after WM_DESTROY
where the former uses subclass_input
."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed it to my first suggestion :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still find it questionable that memory is freed based on an event which hopefully is the last one received, that seems very unsafe to me.
But I have no knowledge of the Windows backend and do not plan to change that.
It is unfortunate that this isn't documented all that well, but it's probably safe to do it this way, provided that no other funny buisness occurs (read: trying to destroy a window after it has received |
I'd say "probable safe" is not good enough personally. As I've stated previously I'm not familiar with the Windows winit internals, but this error seems to be triggered by trying to handle events after we've already decided that the lifetime was over. Would it not be possible to store this information internally and prevent further events from being processed even if they would come in? |
It should be possible to replace the window's "window procedure" (event handler function) with a no-op function after the window receives WM_NCDESTROY => {
SetWindowLongW(hwnd, GWL_WNDPROC, no_op_window_callback);
// deallocate `subclass_input`
} extern "system" fn no_op_window_callback( /* callback args */ ) {
0
} I'll have to look closer at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it's fine to move forward with this change - more sophisticated cleanup can be done as followup as well. This would include removing the subclass, which is supposed to be done in WM_NCDESTROY
anyway due to reverse ordering.
Our application was not exiting gracefully but crashed on
platform_impl/windows/event_loop.rs:1927
.WM_DESTROY
destroyssubclass_input
butWM_NCDESTOY
is send afterWM_DESTROY
(See MS docs) which causessubclass_input.event_loop_runner.clone();
to fail.Examples don't repro, are the they just lucky that the memory didn't change?
Creating a minimal test case from our project would be quite a lot of work.
cargo fmt
has been run on this branchcargo doc
builds successfullyCHANGELOG.md
if knowledge of this change could be valuable to users