}
pd := &_p_.sysmontick
s := _p_.status
+ sysretake := false
+ if s == _Prunning || s == _Psyscall {
+ // Preempt G if it's running for too long.
+ t := int64(_p_.schedtick)
+ if int64(pd.schedtick) != t {
+ pd.schedtick = uint32(t)
+ pd.schedwhen = now
+ } else if pd.schedwhen+forcePreemptNS <= now {
+ preemptone(_p_)
+ // In case of syscall, preemptone() doesn't
+ // work, because there is no M wired to P.
+ sysretake = true
+ }
+ }
if s == _Psyscall {
// Retake P from syscall if it's there for more than 1 sysmon tick (at least 20us).
t := int64(_p_.syscalltick)
- if int64(pd.syscalltick) != t {
+ if !sysretake && int64(pd.syscalltick) != t {
pd.syscalltick = uint32(t)
pd.syscallwhen = now
continue
}
incidlelocked(1)
lock(&allpLock)
- } else if s == _Prunning {
- // Preempt G if it's running for too long.
- t := int64(_p_.schedtick)
- if int64(pd.schedtick) != t {
- pd.schedtick = uint32(t)
- pd.schedwhen = now
- continue
- }
- if pd.schedwhen+forcePreemptNS > now {
- continue
- }
- preemptone(_p_)
}
}
unlock(&allpLock)
package runtime_test
import (
+ "fmt"
"math"
"net"
"runtime"
t.Errorf("want %q, got %q", want, output)
}
}
+
+// fakeSyscall emulates a system call.
+//go:nosplit
+func fakeSyscall(duration time.Duration) {
+ runtime.Entersyscall()
+ for start := runtime.Nanotime(); runtime.Nanotime()-start < int64(duration); {
+ }
+ runtime.Exitsyscall()
+}
+
+// Check that a goroutine will be preempted if it is calling short system calls.
+func testPreemptionAfterSyscall(t *testing.T, syscallDuration time.Duration) {
+ if runtime.GOARCH == "wasm" {
+ t.Skip("no preemption on wasm yet")
+ }
+
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+
+ interations := 10
+ if testing.Short() {
+ interations = 1
+ }
+ const (
+ maxDuration = 3 * time.Second
+ nroutines = 8
+ )
+
+ for i := 0; i < interations; i++ {
+ c := make(chan bool, nroutines)
+ stop := uint32(0)
+
+ start := time.Now()
+ for g := 0; g < nroutines; g++ {
+ go func(stop *uint32) {
+ c <- true
+ for atomic.LoadUint32(stop) == 0 {
+ fakeSyscall(syscallDuration)
+ }
+ c <- true
+ }(&stop)
+ }
+ // wait until all goroutines have started.
+ for g := 0; g < nroutines; g++ {
+ <-c
+ }
+ atomic.StoreUint32(&stop, 1)
+ // wait until all goroutines have finished.
+ for g := 0; g < nroutines; g++ {
+ <-c
+ }
+ duration := time.Since(start)
+
+ if duration > maxDuration {
+ t.Errorf("timeout exceeded: %v (%v)", duration, maxDuration)
+ }
+ }
+}
+
+func TestPreemptionAfterSyscall(t *testing.T) {
+ for _, i := range []time.Duration{10, 100, 1000} {
+ d := i * time.Microsecond
+ t.Run(fmt.Sprint(d), func(t *testing.T) {
+ testPreemptionAfterSyscall(t, d)
+ })
+ }
+}