skipUnderDebugger(t)
// This can deadlock if there aren't enough threads or if a GC
- // tries to interrupt an atomic loop (see issue #10958).
- ogomaxprocs := runtime.GOMAXPROCS(2)
+ // tries to interrupt an atomic loop (see issue #10958). We
+ // use 8 Ps so there's room for the debug call worker,
+ // something that's trying to preempt the call worker, and the
+ // goroutine that's trying to stop the call worker.
+ ogomaxprocs := runtime.GOMAXPROCS(8)
ogcpercent := debug.SetGCPercent(-1)
- ready := make(chan *runtime.G)
+ // ready is a buffered channel so debugCallWorker won't block
+ // on sending to it. This makes it less likely we'll catch
+ // debugCallWorker while it's in the runtime.
+ ready := make(chan *runtime.G, 1)
var stop uint32
done := make(chan error)
go debugCallWorker(ready, &stop, done)
close(done)
}
+// Don't inline this function, since we want to test adjusting
+// pointers in the arguments.
+//
+//go:noinline
func debugCallWorker2(stop *uint32, x *int) {
for atomic.LoadUint32(stop) == 0 {
// Strongly encourage x to live in a register so we
// This can deadlock if there aren't enough threads or if a GC
// tries to interrupt an atomic loop (see issue #10958).
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
defer debug.SetGCPercent(debug.SetGCPercent(-1))
// Test that the runtime refuses call injection at unsafe points.
skipUnderDebugger(t)
// This can deadlock if there aren't enough threads.
- defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8))
ready := make(chan *runtime.G)
var stop uint32
h.gp = gp
h.fv, h.argp, h.argSize = fv, argp, argSize
h.handleF = h.handle // Avoid allocating closure during signal
- noteclear(&h.done)
defer func() { testSigtrap = nil }()
- testSigtrap = h.inject
- if err := tkill(tid); err != nil {
- return nil, err
- }
- // Wait for completion.
- notetsleepg(&h.done, -1)
- if len(h.err) != 0 {
- return nil, h.err
+ for i := 0; ; i++ {
+ testSigtrap = h.inject
+ noteclear(&h.done)
+ h.err = ""
+
+ if err := tkill(tid); err != nil {
+ return nil, err
+ }
+ // Wait for completion.
+ notetsleepg(&h.done, -1)
+ if h.err != "" {
+ switch h.err {
+ case "retry _Grunnable", "executing on Go runtime stack":
+ // These are transient states. Try to get out of them.
+ if i < 100 {
+ Gosched()
+ continue
+ }
+ }
+ return nil, h.err
+ }
+ return h.panic, nil
}
- return h.panic, nil
}
type debugCallHandler struct {
h.savedRegs.fpstate = nil
// Set PC to debugCallV1.
ctxt.set_rip(uint64(funcPC(debugCallV1)))
+ // Call injected. Switch to the debugCall protocol.
+ testSigtrap = h.handleF
+ case _Grunnable:
+ // Ask InjectDebugCall to pause for a bit and then try
+ // again to interrupt this goroutine.
+ h.err = plainError("retry _Grunnable")
+ notewakeup(&h.done)
default:
h.err = plainError("goroutine in unexpected state at call inject")
- return true
+ notewakeup(&h.done)
}
- // Switch to the debugCall protocol and resume execution.
- testSigtrap = h.handleF
+ // Resume execution.
return true
}
sp := ctxt.rsp()
reason := *(*string)(unsafe.Pointer(uintptr(sp)))
h.err = plainError(reason)
+ // Don't wake h.done. We need to transition to status 16 first.
case 16:
// Restore all registers except RIP and RSP.
rip, rsp := ctxt.rip(), ctxt.rsp()
notewakeup(&h.done)
default:
h.err = plainError("unexpected debugCallV1 status")
+ notewakeup(&h.done)
}
// Resume execution.
return true