Skip to content

proposal: runtime/mainthread: add mainthread.Do for mediating access to the main thread #70089

Open
@eliasnaur

Description

@eliasnaur

Proposal Details

This is #64777 (comment) in proposal form. It is a reduced and compatible variant of #64777 (comment).

I propose to add a new package, mainthread, with a single function, Do, that allows Go programs to execute a function on the main thread.

// Package mainthread mediates access to the program's main thread.
//
// Most Go programs do not need to run on specific threads 
// and can ignore this package, but some C libraries, often GUI-related libraries,
// only work when invoked from the program's main thread.
//
// [Do] runs a function on the main thread. No other code can run on the main thread
// until that function returns.
//
// Each package's initialization functions always run on the main thread,
// as if by successive calls to Do(init).
//
// For compatibility with earlier versions of Go, if an init function calls [runtime.LockOSThread], 
// then package main's func main also runs on the main thread, as if by Do(main).
package mainthread // imported as "runtime/mainthread"

// Do calls f on the main thread.
// Nothing else runs on the main thread until f returns.
// If f calls Do, the nested call panics.
//
// Package initialization functions run as if by Do(init).
// If an init function calls [runtime.LockOSThread], then package main's func main
// runs as if by Do(main), until the thread is unlocked using [runtime.UnlockOSThread].
//
// Do panics if the Go runtime is not in control of the main thread, such as in build modes
// c-shared and c-archive.
func Do(f func())

The larger proposal (#64777 (comment)) adds Yield and Waiting to support sharing the main thread in a Go program. However, the Go runtime doesn't always have control over the main thread, most notably in c-shared or c-archive mode on platforms such as Android. In those cases, the platform facility for mediating main thread access are strictly superior to mainthread.Do. See #64777 (comment) for a detailed analysis and assumptions.

In short, I believe it's better to accept this simpler proposal to only allow Go programs access to the main thread when the Go runtime has control over it, and let other cases be handled by platform API.

I hope this can be implemented in Go 1.24.

Activity

added this to the Proposal milestone on Oct 29, 2024
gabyhelp

gabyhelp commented on Oct 29, 2024

@gabyhelp
apparentlymart

apparentlymart commented on Oct 29, 2024

@apparentlymart

(I see that earlier versions of this proposal were already discussed at length elsewhere and I did try to catch up on it first, but I apologize if I'm asking a question that's redundant from earlier discussions.)

If this function will panic when called from an environment where the Go runtime does not "own" the main thread, is it justified to also offer a function to test whether a call to this function is possible? That could, for example, allow a caller to choose to treat "I'm running in the wrong mode" as an error to be handled gracefully, rather than as an exception to be handled by panicking.

package mainthread

// CanDo returns true if and only if a subsequent call to [Do] would not panic.
func CanDo() bool

(Another variation of this would be for Do itself to return an error, but the usability of not having to worry about error handling when you know you're running in a context where this should work seems nice... this concern of detecting whether it will succeed seems specific to library developers that want their library to degrade gracefully in c-shared/c-archive/etc build modes.)

Jorropo

Jorropo commented on Oct 29, 2024

@Jorropo
Member

I hope this can be implemented in Go 1.24.

Just so you know, the current merge window closes 21 11, this would be a quick turn around time. There is the option of getting exceptions but theses are rare and usually limited to very low dangerous community impact.

qiulaidongfeng

qiulaidongfeng commented on Oct 29, 2024

@qiulaidongfeng
Member

Does this API mean that if the main package imports a package that calls runtime.LockOSThread in init (for event loop in main thread) ,
like fyne did, calls to mainthread.Do by other packages will block permanently?

If so, that means we may need to modify existing valid code when using the mainthread package, which I don't think is backward-compatible,see #64777 (comment).
Fyne Info:
On Windows:
call LockOSThread in https://github.com/fyne-io/fyne/blob/7d813563712924b381ced18c04869c059e2cb4c6/internal/driver/glfw/loop.go#L35
event loop in https://github.com/fyne-io/fyne/blob/7d813563712924b381ced18c04869c059e2cb4c6/internal/driver/glfw/loop.go#L107

eliasnaur

eliasnaur commented on Oct 31, 2024

@eliasnaur
ContributorAuthor

@qiulaidongfeng I believe your comment is addressed by #64777 (comment). In short, LockOSThread during init does not compose automatically with mainthread.Do, because they both act on a single resource, the main thread. There is no backwards compatibility issue, however, because this proposal doesn't affect the behaviour of LockOSThread during init.

eliasnaur

eliasnaur commented on Oct 31, 2024

@eliasnaur
ContributorAuthor

@apparentlymart the original proposal says to panic in c-shared/c-archive mode, but I'm not against CanDo or the like.

gopherbot

gopherbot commented on Nov 17, 2024

@gopherbot
Contributor

Change https://go.dev/cl/628815 mentions this issue: runtime/mainthread: new package

moved this to Incoming in Proposalson Nov 20, 2024
rsc

rsc commented on Dec 4, 2024

@rsc
Contributor

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

moved this from Incoming to Active in Proposalson Dec 4, 2024
aclements

aclements commented on Dec 19, 2024

@aclements
Member

I think we need to re-ground this discussion in concrete use cases. I'm sure at least some of this will be me asking you to repeat what's already been said in #64777, but I think getting re-consolidating this information will be helpful.

Let's define main thread to mean the OS-created thread that started the process, and define startup thread to mean the thread we run Go init functions on. In typical Go binaries, these are one and the same. In c-shared and c-archive mode, the Go runtime always creates a new thread to run init functions, and exits that thread after init functions are done, so the startup thread is not the main thread. There's also a library load thread, which is the thread that first calls into the Go runtime in c-shared and c-archive mode. This may be the main thread or may be another thread, but the Go runtime relinquishes control of this thread very quickly.

What are the situations where a library needs to be called on the main thread (and not just consistently on some thread, and not just on the startup thread), and the platform doesn't provide a mechanism for calling code on the main thread? Can you give concrete examples so we have something to ground the requirements in?

How are libraries even sensitive to this? Do they behave differently from C if you link against them at build time (statically or dynamically) versus if you dlopen them at run time? (The abstract case we were able to come up with in proposal review is that a native library has global constructors/ELF initializers and is sensitive to other functions running on the same thread. But even that isn't necessarily the main thread if that library gets dlopened.)

63 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Active

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @apparentlymart@neild@rsc@andydotxyz@aarzilli

        Issue actions

          proposal: runtime/mainthread: add mainthread.Do for mediating access to the main thread · Issue #70089 · golang/go