Repository: go
Issue: runtime: unexpected failure and garbage data when c-shared Go DLL on Windows receives CtrlBreak signal concurrently with runtime initialization
Labels: help wanted,OS-Windows,NeedsInvestigation,compiler/runtime

In `c-shared` mode, when a Go DLL is loaded, it spawns a new thread that starts runtime initialization. All calls into the DLL block on runtime initialization completing.

While investigating #45638, we discovered that the previous iteration of `TestLibraryCtrlHandler` would often send a CtrlBreak signal to the Go test process before runtime initialization was complete, and this would result in an exception landing in a Go exception handler on a Go thread that wasn't ready to receive it (no G or M).

Then, we'd end up in `badsignal2`. Previously, `badsignal2` would not exit after printing the message, and garbage would spew into the stdout (or Windows equivalent) of the process before `badsignal2` would be called again. Still, this garbage spewing was reproducible after this was fixed.

As an example, this is what the failure looked like:

```
=== RUN   TestLibraryCtrlHandler
    signal_windows_test.go:210: Program exited with error: exit status 1
        runtime: signal received on thread not created by Go.
        �o�<f�N9꙾�&����vl��3U0�{\K���X�س���64oMˡ1��!
                                                    R��n��)�Џ�jG'��T��-�0�i�
                                                                            �!Ρ6�)��,p??�G�j�V���k6˩���G
                                                                                                        �
                                                                                                         �6�runtime: signal received on thread not created by Go.
        �o�<f�N9꙾�&����vl��3U0�{\K���X�س���64oMˡ1��!
                                                    R��n��)�Џ�jG'��T��-�0�i�
                                                                            �!Ρ6�)��,p??�G�j�V���k6˩���G
                                                                                                        �
                                                                                                         �6�h�h�runtime: signal received on thread not created by Go.
        �o�<f�N9꙾�&����vl��3U0�{\K���X�س���64oMˡ1��!
                                                    R��n��)�Џ�jG'��T��-�0�i�
                                                                            �!Ρ6�)��,p??�G�j�V���k6˩���G
                                                                                                        �
                                                                                                         �6�h�h�d@�?@DDD �"""C:\Windows\system32\runtime: signal received on thread not created by Go.
        �o�<f�N9꙾�&����vl��3U0�{\K���X�س���64oMˡ1��!
                                                    R��n��)�Џ�jG'��T��-�0�i�
                                                                            �!Ρ6�)��,p??�G�j�V���k6˩���G
                                                                                                        �
                                                                                                         �6�h�h�d@�?@DDD �"""C:\Windows\system32\FAILURE: No signal received
--- FAIL: TestLibraryCtrlHandler (68.32s)
FAIL
FAIL	runtime	68.350s
FAIL
Error running run: exit status 1
```

Sometimes there would be a lot more garbage (spanning pages and pages of terminal buffer space). Sometimes less.

The failure is rare, maybe once in 30,000 runs. It's still unclear what the root cause is, but we've narrowed it down to the control handler getting installed in the Go runtime. If that is removed, it is not reproducible. It only started showing up in Go 1.17, during which time the way our console control handler worked changed. It's unclear how the exception handler gets invoked, also, because the control handler is generally completely separate from the exception handler on Windows.

Something may be going wrong on a Go-owned thread that hasn't been set up to have an M yet (i.e. between when the first M is properly initialized and when the control handler is installed), however it's still possible to reproduce the issue even if control handler installation is delayed all the way until just before `runtime.main` completes (signalling that runtime initialization is done).

However, it's possible this issue is reproducible under previous Go releases (just that it's more difficult to do so). Notably, it's still possible to reproduce the issue even if one installs a trivial handler written in assembly (much less complex than `callbackasm1`) without any of the `compileCallback` machinery that was added in Go 1.17.

The workaround for this issue is to block the application until the Go runtime has fully initialized by calling any Go function in the DLL before proceeding.

CC @alexbrainman @zx2c4