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

runtime: document that calling LockOSThread in init will lock main to m0 (the first OS thread) #23112

Closed
andlabs opened this issue Dec 13, 2017 · 2 comments
Labels
Documentation FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Milestone

Comments

@andlabs
Copy link
Contributor

andlabs commented Dec 13, 2017

Continued from a brief in-person discussion with Ian and Keith earlier today.

A lot of people (including myself) want to write GUI programs in Go; we even had shiny to provide our own solution to this desire. Most, if not all OSs, require that all GUI code runs on a single OS thread, but most don't care which OS thread is used. macOS is the exception — macOS consigns the idea that the very first thread created by the OS is the only one allowed to do GUI stuff, and it has a few ways to do this:

  • it provides the pthread_main_np() function, which returns true if the current thread is the first thread the OS creates (and in the Darwin kernel, pthreads are the kernel threading model, in essence); WebKit calls this function directly
  • libSystem.dylib, the shared library that all programs eventually link to (think kernel32.dll on Windows), uses __attribute__((constructor)) to call a variety of thread-specific initialization functions in other libraries (such as libdispatch, which is important for modern Cocoa programming)
  • Core Foundation, which Cocoa is built on top of, uses a variety of undocumented variables to control its own data structures that, while they can be overridden with the correct extern statements, will not catch all possible cases
  • this list is probably incomplete

Various components in Cocoa have internal consistency checks to make sure they are only ever called on the "main thread"; a program will abort if these checks fail.

In runtime-speak, the first thread created by the OS is called m0 (or was called m0 back when the runtime was written in C), so I will call it that from here on out.

On other platforms, we could just insert a runtime.LockOSThread() in a goroutine to tie the goroutine that will do all GUI stuff to an OS thread, but for macOS this alone wouldn't work. I have found a number of ways to bypass some of the above bullet points, but things just wind up breaking later (and I don't think the libdispatch thing can be hacked without hard-coding its internal data structures...).

And this doesn't even consider things like transpiled JavaScript (in which I don't even know what the requirements are) or mobile platforms (I'm sure iOS has the same requirements as macOS but I'm not sure about any other platform).

So far, the most widely used workaround has been to call runtime.LockOSThread() in init. I didn't think this was a good solution because there was no guarantee that init will always stay on m0, especially once init was allowed to run concurrently with other goroutines. However, after talking with Ian and Keith earlier today, I learned that this is already guaranteed, just not documented. If this was documented, then we could all safely rely on the behavior.

I wouldn't know if this should be documented in package runtime (under LockOSThread) or in the spec (as part of the definition of initialization behavior).

Furthermore, there's the potential that a different package's init will call runtime.UnlockOSThread(), most likely to undo its own runtime.LockOSThread() — in which case, LockOSThread should be nested. Nesting will handle the case of a later package calling UnlockOSThread, but not of an earlier one doing so too, so perhaps UnlockOSThread should also be made a no-op in init functions, if that's even possible.

And of course, all bets are off if another function in main calls UnlockOSThread. Even if this is also made a no-op (which is probably not a good idea), package authors will need to document what functions must be run in main. This isn't a problem, though; it's something package authors should know, though.

@ianlancetaylor
Copy link
Member

The guarantee in question is provided by https://golang.org/cl/5309070. In the discussion on that CL there were suggestions that should only be done in the implementation, not the spec. At this point I think that perhaps we can document it in the runtime package. CC @rsc .

Nesting LockOSThread is #20458, which has been fixed for 1.10 (see the beta release notes: https://tip.golang.org/doc/go1.10#runtime).

@ianlancetaylor ianlancetaylor added the NeedsFix The path to resolution is known, but the work has not been done. label Mar 29, 2018
@ianlancetaylor ianlancetaylor added this to the Go1.11 milestone Mar 29, 2018
@gopherbot
Copy link
Contributor

Change https://golang.org/cl/103399 mentions this issue: runtime: document that LockOSThread in init locks to thread

@golang golang locked and limited conversation to collaborators Apr 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Documentation FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

3 participants