-
Notifications
You must be signed in to change notification settings - Fork 893
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
Proposal: Allow walk to run on a thread of the caller's choice #601
Comments
Implements: lxn#601 This commit does not properly handle SetOwner() and SetParent() calls where the old and new parents have different tool tip controls.
Nice writeup! Have you tried this with multiple forms? What about stack size? Isn't the main goroutine the only one guaranteed to run on a thread with a nice big stack? Would it be possible to prevent re-parenting from one thread to another? Windows should already do that I assume? This probably is another issue with concurrent |
Implements: lxn#601 This commit does not properly handle SetOwner() and SetParent() calls where the old and new parents have different tool tip controls.
This code spawns 5 windows and causes 5 message loops to run concurrently:
When running against my feature branch each window has a functional message loop—the sliders respond to user input and to the close button. It's not an exhaustive test and I'm sure it races with
Hrm, I hadn't considered that. I looked at #261 and #290 and I assume you're talking about the expectation some controls have about the stack size that's available to them, in particular the
We could use GetCurrentThreadId to store the creator thread ID in
I hadn't noticed the |
Each window now has a WindowGroup assigned to it as part of its InitWindow process. All windows created on a common thread will share the same window group. Widgets now access their ToolTip controls through their window group. The tool tip control is created on first use and is destroyed when the group is destroyed. Window groups are destroyed once all windows that refer to the group are destroyed. Window group data should always be accessed from the same thread. Despite this, the data within each group is protected by an atomic counter and a mutex as a safety measure. This is subject to change in the future. This is an alternative solution for lxn#601
Each window now has a WindowGroup assigned to it as part of its InitWindow process. All windows created on a common thread will share the same window group. Widgets now access their ToolTip controls through their window group. The tool tip control is created on first use and is destroyed when the group is destroyed. Window groups are destroyed once all windows that refer to the group are destroyed. Window group data should always be accessed from the same thread. Despite this, the data within each group is protected by an atomic counter and a mutex as a safety measure. This is subject to change in the future. This is an alternative solution for lxn#601
Each window now has a WindowGroup assigned to it as part of its InitWindow process. All windows created on a common thread will share the same window group. Widgets now access their ToolTip controls through their window group. The tool tip control is created on first use and is destroyed when the group is destroyed. Window groups are destroyed once all windows that refer to the group are destroyed. Window group data should always be accessed from the same thread. Despite this, the data within each group is protected by an atomic counter and a mutex as a safety measure. This is subject to change in the future. This is an alternative solution for lxn#601
|
Walk currently requires that the message loop runs on the main thread—the thread that called
init()
andmain()
. While it is important that window creation and the message loop are thread-locked, running it on the main thread is not a requirement of Windows.This requirement has led to issues like #297, #337 and #555. The workaround for #297 and #337 was to ensure that
Run()
took place on the main thread.I propose making a few changes to allow window creation and
Run()
functions to work on a thread of the caller's choice.Code like this should run and exit quickly:
Right now this code deadlocks. This is due to the current design for widget tool tip handling. I believe this can be fixed without breaking existing applications or changing walk's API.
Current Design
The existing tool tip implementation relies on a global instance of the
ToolTip
control calledglobalToolTip. Widgets add themselves to and remove themselves from this global instance. The instance is prepared in an
init()
call (and has been since since 2012):Note that NewToolTip() calls newToolTip() which calls InitWindow(). This results in a
CreateWindowEx()
call on the main thread which results in the creation of a Windows message queue. Walk's tool tip implementation is the thing that's creating the Windows message queue and it's doing this in aninit()
call.Widgets ultimately add and remove themselves by calling SendMessage() and sending
TTM_ADDTOOL
andTTM_DELTOOL
messages to theglobalToolTip
control. SendMessage completely skips the message loop if the recipient was created by the calling thread, and walk relies on this fact to avoid deadlocks during initialization.In the current design widgets must be added to a tool tip control on the same thread or they'll deadlock on
SendMessage()
. TheglobalToolTip
is created on the main thread, therefore all widgets must also be created on the main thread.Proposal Objective
Only create a
ToolTip
instance when a call toInitWindow()
happens for some other reason first. This should be the result of the caller taking some explicit action like calling Run(), NewDialog() or NewMainWindow(). The caller makes the call from a goroutine of his or her choice. That's the thread where the message loop has to live so that's where the tool tip control should live too.Proposed Design A
Instead of a global
ToolTip
control, keep a pointer to a localToolTip
instance inFormBase or ContainerBase. Functions like
NewMainWindow()
would set this value when they prepare a window or form that has no parent.Widgets add themselves to and and remove themselves from the
ToolTip
of their parent (or closest ancestor with a non-nilToolTip
).Concerns
The SetParent() and SetOwner() functions pose a challenge. A widget could be re-parented to a window with a different tool tip control. Removing the tool from the old parent's control and adding it to the new parent's control might be sufficient.
Walking up the window hierarchy to locate an appropriate parent adds complexity. Every interaction with the local tool tip has an edge case where the ToolTip may not exist.
Status
Proposed Design A was not merged.
Proposed Design B
Create a new per-thread storage structure called a
WindowGroup
. EachWindow
will have aWindowGroup
assigned to it as part of itsInitWindow()
process. All windows created on a common thread will share the same window group.Widgets will access their
ToolTip
controls through their window group. The tool tip control is created on first use and is destroyed when the group is destroyed.The life cycle of a window group is managed by reference counting. Window groups are destroyed once all windows that refer to the group are destroyed. The reference is incremented by InitWindow() and decremented by WindowBase.Dispose().
The current thread can be identified by a call to GetCurrentThreadId().
Concerns
Assessing the liveness of a window group relies on reference counting. If we miscount we could dispose of the tool tip prematurely or leak system resources.
Status
Proposed Design B was merged in #610.
Pros
Callers can create windows and run message loops in a goroutine of their choice.
These designs could pave the way for concurrent
Run()
invocations on different threads, so long as the caller doesn't pass widgets between threads. The runSynchronized() function also needs to stop relying on global state. That can go in a separate issue.Cons
Callers must still be careful to create and run a related set of windows within a single thread.
The text was updated successfully, but these errors were encountered: