Repository: go
Issue: runtime: SIGQUIT crash loop + startm race condition deadlock
Labels: NeedsDecision,compiler/runtime

Discovered by @bcmills in https://github.com/golang/go/issues/64752#issuecomment-1885242770. Bryan's analysis copied here:

Some analysis of the test log reported by `watchflakes`.

The program ends up running six goroutines in total. When run on my workstation, at the time of the crash the goroutines are parked in the following locations:
   - `main` in [`C.trigger_crash`](https://cs.opensource.google/go/go/+/master:src/runtime/runtime-gdb_unix_test.go;l=248;drc=098f059d077d01d9151e2e76901f62a7f2d52fec)
   - `forcegchelper` in [`goparkunlock`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=326;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
   - `bgsweep` in [`goparkunlock`](https://cs.opensource.google/go/go/+/master:src/runtime/mgcsweep.go;l=317;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
   - `bgscavenge` in [`goparkunlock`](https://cs.opensource.google/go/go/+/master:src/runtime/mgcscavenge.go;l=425;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
   - `runfinq` in [`gopark`](https://cs.opensource.google/go/go/+/master:src/runtime/mfinal.go;l=194;drc=4f55a5af5e5d325a534222050564766c249218aa)
   - `sysmon` in [`notetsleep`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=5994;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
      - (Note that the `sysmon` goroutine is not included in the output from `tracebackothers` — I guess because it doesn't have an associated P or G?)

In a successful run on my local workstation, I see `SIGQUIT: quit` logs for five background threads:
- one in `notetsleep` via `sysmon`
- one in `notesleep` via [`templateThread`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2760;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
- three in `stopm` via `schedule` and [`findRunnable`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=3868;drc=dcbe77246922fe7ef41f07df228f47a37803f360)

In the builder log, we see `SIGQUIT: quit` logs for only four background threads:
- one in `notetsleep` via `sysmon` (as expected)
- one in `notesleep` via `templateThread` (as expected)
- one in [`allocm`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2644;drc=dcbe77246922fe7ef41f07df228f47a37803f360) via `schedule`, [`resetspinning`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=3886;drc=dcbe77246922fe7ef41f07df228f47a37803f360), and [`newm`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2870;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
- one in `stopm` via `schedule` and [`startlockedm`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=3914;drc=dcbe77246922fe7ef41f07df228f47a37803f360)

The thread in `allocm` looks suspiciously like a race in the runtime. In particular:
- `startm` calls [`mReserveID`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2862;drc=dcbe77246922fe7ef41f07df228f47a37803f360) (incrementing `mnext`) before it calls [`newm`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2870;drc=dcbe77246922fe7ef41f07df228f47a37803f360)
- The `newm` thread was interrupted by `SIGQUIT` during [`allocm`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2644;drc=dcbe77246922fe7ef41f07df228f47a37803f360), which is _before_ the new thread is actually started for the M (in [`newm1`](https://cs.opensource.google/go/go/+/master:src/runtime/proc.go;l=2676;drc=dcbe77246922fe7ef41f07df228f47a37803f360)).

So this looks like a genuine watchdog failure: the kernel defers delivery of the final `SIGQUIT` signal because all of the threads that could receive it are already handling a `SIGQUIT` (with that signal presumably masked), and the `main` thread is spinning in the `docrash` loop waiting for acknowledgement from a fifth thread that had an M ID reserved but was never actually started. 

Perhaps `startm` needs to block signals from at some point before calling `mReserveID` until `newm` has returned?

cc @golang/runtime