How does isAsyncSafePoint check preemptable?

104 views
Skip to first unread message

HailangGe

unread,
Aug 6, 2020, 10:48:36 AM8/6/20
to golang-nuts
Recently I was trying to understand how asynchronous preemption is implemented in Go 1.14 and
basically figured out the call chain.

    sysmon
    ↓
    retake
    ↓
    preemptone
    ↓
    preemptM
    ↓
    signalM(mp, sigPreempt)    //send SIGURG to mp
    ↓
    doSigPreempt(signal handler)   //if isAsyncSafePoint returns true, it will go on to preempt
    ↓
    ctxt.pushCall(funcPC(asyncPreempt))
    ↓
    asyncPreempt
    ↓ 
    asyncPreempt2
    ↓ 
    mcall(preemptPark) or mcall(gopreempt_m)

And I wrote a simple program to verify:
```Go
func main() {
        var wg sync.WaitGroup
        wg.Add(30)
        for i := 0; i < 30; i++ {
               go func() {
                       defer wg.Done()
                       t := 0
                       for i := 0; i < 1e8; i++ {
                              t += 2
                       }
                 }()
         }
         wg.Wait()
}
```

As we know general purpose registers RAX/RBX/... are not included in goroutine context.

```
type runtime.gobuf struct {
uintptr sp;
uintptr pc;
runtime.guintptr g;
void *ctxt;
runtime/internal/sys.Uintreg ret;
uintptr lr;
uintptr bp;
}
```

So when there are temporary values in these registers, it should not be good time to preempt. In following example, the loop takes enough long  time to make it be preempted.

`t+=2` and `i++` use RAX as temporay register so it should't be asynchronous safe point. 

My problem is how Go judges which instruction is preemptable? is it determined at compile time?

```
//from disassemble /sr main.main.func1
17 t := 0
0x000000000047be7d <+93>: 48 c7 44 24 08 00 00 00 00 movq $0x0,0x8(%rsp)

18 for i:=0; i < 1e8; i++ {
0x000000000047be86 <+102>: 48 c7 44 24 10 00 00 00 00 movq $0x0,0x10(%rsp)
0x000000000047be8f <+111>: eb 00 jmp 0x47be91 <main.main.func1+113>
0x000000000047be91 <+113>: 48 81 7c 24 10 00 ca 9a 3b cmpq $0x3b9aca00,0x10(%rsp)
0x000000000047be9a <+122>: 7c 02 jl 0x47be9e <main.main.func1+126>
0x000000000047be9c <+124>: eb 1f jmp 0x47bebd <main.main.func1+157>

19 t += 2
0x000000000047be9e <+126>: 48 8b 44 24 08 mov 0x8(%rsp),%rax
0x000000000047bea3 <+131>: 48 83 c0 02 add $0x2,%rax
0x000000000047bea7 <+135>: 48 89 44 24 08 mov %rax,0x8(%rsp)
0x000000000047beac <+140>: eb 00 jmp 0x47beae <main.main.func1+142>

18 for i:=0; i < 1e8; i++ {
0x000000000047beae <+142>: 48 8b 44 24 10 mov 0x10(%rsp),%rax
0x000000000047beb3 <+147>: 48 ff c0 inc %rax
0x000000000047beb6 <+150>: 48 89 44 24 10 mov %rax,0x10(%rsp)
0x000000000047bebb <+155>: eb d4 jmp 0x47be91 <main.main.func1+113>
```


Ian Lance Taylor

unread,
Aug 6, 2020, 1:00:51 PM8/6/20
to HailangGe, golang-nuts
That is true but it doesn't matter. asyncPreempt is a GOARCH-specific
routine that saves all registers. When the preempted goroutine is
resumed, it will return through asyncPreempt and the registers will be
restored before continuing with execution.




> My problem is how Go judges which instruction is preemptable? is it determined at compile time?

There are other, less common, cases where preemption is not permitted,
and, yes, those are marked by the compiler and checked by
isAsyncSafePoint.

Ian

Ge

unread,
Aug 7, 2020, 11:56:29 AM8/7/20
to golang-nuts

Thanks. clear now.
Reply all
Reply to author
Forward
0 new messages