-
Notifications
You must be signed in to change notification settings - Fork 892
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] Improve handling of window destruction #1798
[Windows] Improve handling of window destruction #1798
Conversation
drop(subclass_input); | ||
Box::from_raw(subclass_input_ptr as *mut SubclassInput<T>); | ||
remove_window_subclass( | ||
window, | ||
Box::from_raw(subclass_input_ptr as *mut SubclassInput<T>), | ||
); |
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.
Dropping subclass input first and then trying to do something with a pointer to it seems strange to me.
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 subclass_input
that's dropped has a type of &SubclassInput<T>
(introduced here). Dropping it is necessary to avoid UB.
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.
hmm I don't believe drop
will do provide anything in this case as references are copy types, but I'm not sure about the subtile aspects of lifetimes in this case. It's even a bit trickier as we are in a callback executed on the runner inside the subclass input. Explicit calls to Box::from_raw
and Box::into_raw
should lead to a safer approach in this case without relying on the dereference+borrow from outside the callback.
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.
references are copy types
Ah, I had completely forgotten, but that then makes me wonder why it was ever there in the first place.
It's even a bit trickier as we are in a callback executed on the runner inside the subclass input
Yeah, I'm kind of at a loss here. The EventLoopRunner<T>
needs to be kept alive until the catch_unwind
method returns, but the Rc
we have to it is dropped before we return. This is admittedly worse in thread_event_target_callback
, since that function's subclass_input
is seemlingly the last thing that will hold on to an Rc
to the EventLoopRunner<T>
.
EDIT: Actually, it may be fine since thread_event_target_callback
clones the Rc
before calling catch_unwind
and returning. Windows which use public_window_callback
seem to then rely on thread_event_target_callback
to not drop its Rc
to the EventLoopRunner<T>
before the windows get destroyed.
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've dug into the code some more, and it seems like there isn't anything guaranteeing that the event target window gets destroyed after all other windows. This means that catch_unwind
will try to access the contents of the EventLoopRunner<T>
after its destructor has been run. Fixing this would require temporarily cloning and holding onto the Rc
until we don't need it.
The slightly more ambiguous issue is to do with pointer aliasing. It feels like having a &
reference to something while also having a *mut
pointer to it is insta-UB, but I'll have to consult with someone more knowledgeable on this before reaching a conclusion.
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 slightly more ambiguous issue is to do with pointer aliasing. It feels like having a
&
reference to something while also having a*mut
pointer to it is insta-UB, but I'll have to consult with someone more knowledgeable on this before reaching a conclusion.
I talked with someone on the Rust community discord, and it seems like this is indeed UB.
WINDOW_SUBCLASS_ID, | ||
) | ||
}; | ||
assert_eq!(removal_result, 1); |
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 not sure how I feel about these assertions. I feel like the most common way for this to fail is if the subclass is already gone and I'm not sure if we should tear down the entire application in that case.
With applications running multiple windows this could lead to a crash of the entire application, assuming this subclass info is added separately for each window (I'm not sure about that?).
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 subclass is registered individually for every window, so I don't see how it could be "already gone" when event_loop
is the only module with enough information (the callback function pointer and subclass ID) to remove it.
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.
Additionally, it seems like the only "always safe" place to deregister subclasses is during window destruction.
3e9681e
to
3d00b63
Compare
The code in question now relies on I realized as I was writing this that the potential panics due to the added |
3d00b63
to
ed32d57
Compare
Panicky bits are now inside |
.catch_unwind(callback) | ||
.unwrap_or(-1); | ||
if ncdestroy { | ||
remove_event_target_window_subclass(window, 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.
I'm not really happy with that solution tbh - unfortunately the topic is quite complex to get right due to the recursive call of the window proc (e.g DestroyWindow will invoke the callback again for th NCDESTROY case etc) and this approach makes it harder to maintain imo.
To be fair I currently don't have a clear opinion on the best way either. I played a bit around with local dereferencing but it doesn't feel robust either: msiglreith@9fd2cee
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.
Would returning (LRESULT, Option<Box<SubclassInput<T>>>)
from the callback
closure and calling Box::into_raw
in the (_, Some(_))
case be better? WM_NCDESTROY
would then get to take ownership of the box and deregister the subclass.
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.
Or (assuming that I've understood the recursive nature of WndProc
correctly): The subclass_input: SubclassInput<T>
could be changed to be Rc<SubclassInput<T>>
which always has one "fake" reference. At the beginning of each call to *_callback
we'd deref the subclass_input_ptr
and clone the Rc
, which would ensure that we never drop the subclass_input
before all calls to the callback are done. WM_NCDESTROY
would then deregister the subclass and remove the "fake" reference, in that order.
8b89c94
to
c44fb62
Compare
The window subclass removal happens immediately after matching |
c44fb62
to
4c76594
Compare
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.
Thanks!
cargo fmt
has been run on this branchcargo doc
builds successfullyCHANGELOG.md
if knowledge of this change could be valuable to usersThis is a follow-up to #1780 and #1746.
Fixes #1795.