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

Fixed memory management issue while closing native Win32 window #1126

Merged
merged 1 commit into from
Jul 14, 2023

Conversation

nicorac
Copy link
Contributor

@nicorac nicorac commented Jun 13, 2023

Description

Intercepting window close in JS leads to wrong memory management when native window is closed (possibly invalid window handle).
I've seen it in debug-builds within VS2019, but it could possibly happen on release.

These are the steps to describe what happens:

  • native window X button is clicked by the user
  • JS receives the windowClose event, does its own tasks and then calls app.exit()
  • app.exit() calls window::_close(code);

window::_close() function contains the misbehavior:

// window.cpp
void _close(int exitCode) {
    if(nativeWindow) {
        nativeWindow->terminate(exitCode);  // <-- THIS CALL IS NON-BLOCKING!
        delete nativeWindow;
    }
}

// webview.h
void terminate(int exitCode = 0) {
  processExitCode = exitCode;
  dispatch([=]() {
      DestroyWindow(m_window);
  });
}
void dispatch(dispatch_fn_t f) {
  PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));  // <-- NON BLOCKING!
}

The marked line contains "non blocking" code, since it only calls PostThreadMessage(), so it returns immediately.
The delete nativeWindow; line is so executed immediately (before the Window message posted by PostThreadMessage has been handled) and clears nativeWindow instance data.

Once the message is handled, DestroyWindow(m_window) is executed but the value of m_window field could be invalid because its owning class was already deleted!

It actually works just because... luckily no other code had ever reused/overwritten the same memory block previously used by nativeWindow..

Changes proposed

We need to ensure that terminate() won't return (and so delete won't be called) till native window has been destroyed.
To do this I've used an event to synchronize the two threads:

  void terminate(int exitCode = 0) {

    // event to wait for window close completion
    auto evtWindowClosed = CreateEvent(
        NULL,               // default security attributes
        TRUE,               // manual-reset event
        FALSE,              // initial state is nonsignaled
        TEXT("WindowClosedEvent")  // object name
    );
    processExitCode = exitCode;

    dispatch([=]() {
        DestroyWindow(m_window);
        SetEvent(evtWindowClosed);
    });

    // wait for dispatch() to complete
    WaitForSingleObject(evtWindowClosed, 10000);
    CloseHandle(evtWindowClosed);

  }

@nicorac nicorac force-pushed the native-win-close-fix branch 2 times, most recently from bf4e2df to 0e44721 Compare June 27, 2023 14:33
@nicorac
Copy link
Contributor Author

nicorac commented Jun 27, 2023

Still wondering why Windows tests fail; they work correctly on my side (Win10-22H2).
The failure is completely unrelated with my changes...

Copy link
Member

@shalithasuranga shalithasuranga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi.. this looks great :) Thanks so much for sending this PR with a detailed explanation about window events

@shalithasuranga shalithasuranga merged commit 6029bcd into neutralinojs:main Jul 14, 2023
13 of 19 checks passed
@nicorac nicorac deleted the native-win-close-fix branch July 18, 2023 14:14
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.

None yet

2 participants