Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: infinite loop in ./src/runtime/proc1.go lockextra() on both Linux & FreeBSD #13926

Closed
Freeaqingme opened this issue Jan 12, 2016 · 30 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-FreeBSD OS-Linux
Milestone

Comments

@Freeaqingme
Copy link

Howdy,

We've been seeing an issue recently on a couple of FreeBSD boxes. It appears as if the application keeps running as should, but CPU usage rises to 100% and the load increases to infinite numbers.

Truss showed that the application was doing a lot of sched_yield syscalls. The following keeps repeating:
49270: sched_yield(0x6aa450,0x7fffd96cbdc8,0xbcbcb0,0xfffffffffffff000,0x0,0x2e) = 0 (0x0)
49270: sched_yield(0x6aa450,0x7fffd9ccedc8,0xbcbcb0,0xfffffffffffff000,0x0,0x2e) = 0 (0x0)
49270: sched_yield(0x6aa450,0x7fffd98ccdc8,0xbcbcb0,0xfffffffffffff000,0x0,0x2e) = 0 (0x0)

Stack trace from the core dump:

#0  runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
306 /usr/local/go/src/runtime/sys_freebsd_amd64.s: No such file or directory.
[Current thread is 1 (Thread 8031ee400 (LWP 101729))]
(gdb) bt full
#0  runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
No locals.
#1  0x0000000000484dcb in runtime.lockextra (nilokay=false, ~r1=0x0) at /usr/local/go/src/runtime/proc1.go:1065
        old = 0
#2  0x00000000004849ef in runtime.needm (x=0 '\000') at /usr/local/go/src/runtime/proc1.go:942
        mp = 0x0
#3  0x00000000004b04f1 in runtime.cgocallback_gofunc () at /usr/local/go/src/runtime/asm_amd64.s:742
No locals.
#4  0x00000000004b041a in runtime.cgocallback () at /usr/local/go/src/runtime/asm_amd64.s:714
No locals.
#5  0x00000000006aa488 in _cgoexp_564f5a6b344f_Go_xxfi_connect (a=0x7fffd7cbedc8, n=32)
        fn = {void (struct github.com/Freeaqingme/gomilter._Ctype_struct_smfi_str *, github.com/Freeaqingme/gomilter._Ctype_char *, struct github.com/Freeaqingme/gomilter._Ctype_struct_sockaddr *, 
    github.com/Freeaqingme/gomilter._Ctype_sfsistat *)} 0x7fffd7cbed50
#6  0x000000000075d23d in crosscall2 () at /usr/local/go/src/runtime/cgo/asm_amd64.s:36
No locals.

I'm not 100% sure whether this thread is also the thread causing all the problems based on the core dump that I have, but given that lockextra() calls osyield() in a for loop it seems very plausible.

Having said that, I also run this app on a few other very similar boxes where this problem does not show. Difference in configuration is that on the systems that do show these symptoms I use cmd := os/exec.Command(); cmd.Run(). But I have no indication that that particular code is having any issues ( https://github.com/Freeaqingme/ClueGetter/blob/61f0089/src/cluegetter/mailQueue.go#L107 ).

So far I have not been able to reproduce or trigger this behavior on Linux. The binary is compiled using go 1.5.1, although the code in proc.go (proc.go & proc1.go were merged) and sys_freebsd_amd64.s haven't been modified since. FreeBSD version: 10.1-RELEASE #0 r274401.

@ianlancetaylor ianlancetaylor changed the title Infinite loop in ./src/runtime/proc1.go lockextra() on FreeBSD runtime: infinite loop in ./src/runtime/proc1.go lockextra() on FreeBSD Jan 12, 2016
@ianlancetaylor ianlancetaylor added this to the Go1.6Maybe milestone Jan 12, 2016
@ianlancetaylor
Copy link
Contributor

Does your program do a lot of callbacks from C to Go?

Doing a web search turned up this: http://mpc.lists.freebsd.bugs.narkive.com/IiDB406k/bug-192926-new-patch-sched-yield-is-a-no-op. Is it possible that that bug applies to your system?

@Freeaqingme
Copy link
Author

Does your program do a lot of callbacks from C to Go?

Yes. That's 90% of the input for my application. I believe line 5 of the stack trace refers to this function, which is invoked from a C library (the sendmail milter library): https://github.com/Freeaqingme/gomilter/blob/master/gomilter.go#L211

Is it possible that that bug applies to your system?

I did see the issue, but hadn't related it because the fix was applied well before this kernel was compiled. I'll see if this particular build included the fix for that bug report.

Ftr, I'm not entirely sure where the bug would be. In theory it could be a locking issue in my application code (or so I was told on IRC), but I'm really not able to find it there. It could of course also be an issue with the kernel, but determining where exactly still goes a little over my head.

@ianlancetaylor
Copy link
Contributor

If that sched_yield bug does exist in your kernel, then you are in trouble because the spin lock in lockextra will burn all your CPU, which I think is approximately what you are seeing.

If your kernel does not have that sched_yield bug, then I think the next step is to verify that this particular loop is the one that is generating all the sched_yield calls.

@Freeaqingme
Copy link
Author

The kernel was built with commit r274401, which includes the fix for the FreeBSD bug linked above.

the next step is to verify that this particular loop is the one that is generating all the sched_yield calls.

Any idea on how to do so?

@Freeaqingme
Copy link
Author

Maybe it's good to mention that the C library (libmilter) spawns a new thread per incoming connection, then does a callback to my go code. The machine this happens on handles a few connections at a time, max.

However, doing 'info threads' on the core dump shows that at the moment the machine shows the high load, there are well over 300 threads:

(gdb) info threads 
  Id   Target Id         Frame 
  320  Thread 802406400 (LWP 101329) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  319  Thread 802406800 (LWP 100443) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  318  Thread 802406c00 (LWP 100451) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  317  Thread 802407000 (LWP 100632) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  316  Thread 802407400 (LWP 100646) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  315  Thread 802810400 (LWP 100924) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  314  Thread 802810800 (LWP 101236) 0x0000000801b613ba in poll () from /lib/libc.so.7
  313  Thread 802c10400 (LWP 101237) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  312  Thread 802c10800 (LWP 101322) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  311  Thread 802c10c00 (LWP 101419) runtime.kevent () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:340
  310  Thread 802c11000 (LWP 101420) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  309  Thread 802c11400 (LWP 101421) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  308  Thread 803013400 (LWP 101422) 0x0000000801af8a8a in _sigwait () from /lib/libc.so.7
  307  Thread 803014800 (LWP 101395) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  306  Thread 803013800 (LWP 100673) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  305  Thread 803015000 (LWP 101277) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  304  Thread 803014000 (LWP 100126) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  303  Thread 803014c00 (LWP 100895) runtime.sys_umtx_op () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:21
  302  Thread 803014400 (LWP 101096) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  301  Thread 803015c00 (LWP 101417) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  300  Thread 803016400 (LWP 101430) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  299  Thread 803016800 (LWP 101431) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  298  Thread 803015400 (LWP 101432) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  297  Thread 803016000 (LWP 101433) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  296  Thread 803015800 (LWP 101434) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  295  Thread 803016c00 (LWP 101435) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  294  Thread 803017000 (LWP 101436) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  293  Thread 803017400 (LWP 101437) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  292  Thread 803017800 (LWP 101438) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
...
  144  Thread 803104000 (LWP 101586) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  143  Thread 803104400 (LWP 101587) 0x0000000000484daf in runtime.lockextra (nilokay=false, ~r1=0x0) at /usr/local/go/src/runtime/proc1.go:1062
  142  Thread 803104800 (LWP 101588) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  141  Thread 803104c00 (LWP 101589) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  140  Thread 803105000 (LWP 101590) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  139  Thread 803105400 (LWP 101591) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  138  Thread 803105800 (LWP 101592) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  137  Thread 803105c00 (LWP 101593) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  136  Thread 803106000 (LWP 101594) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  135  Thread 803106400 (LWP 101595) runtime.lockextra (nilokay=false, ~r1=0x0) at /usr/local/go/src/runtime/proc1.go:1062
  134  Thread 803106800 (LWP 101596) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  133  Thread 803106c00 (LWP 101597) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  132  Thread 803107000 (LWP 101598) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  131  Thread 803107400 (LWP 101599) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  130  Thread 803107800 (LWP 101600) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  129  Thread 803107c00 (LWP 101601) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  128  Thread 803108000 (LWP 101602) runtime.atomicload64 (ptr=0xff4020 <runtime.extram>, ~r1=1) at /usr/local/go/src/runtime/atomic_amd64x.go:30
  127  Thread 803108400 (LWP 101603) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
...
  13   Thread 8031eb400 (LWP 101717) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  12   Thread 8031eb800 (LWP 101718) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  11   Thread 8031ebc00 (LWP 101719) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  10   Thread 8031ec000 (LWP 101720) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  9    Thread 8031ec400 (LWP 101721) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  8    Thread 8031ec800 (LWP 101722) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  7    Thread 8031ecc00 (LWP 101723) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  6    Thread 8031ed000 (LWP 101724) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  5    Thread 8031ed400 (LWP 101725) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  4    Thread 8031ed800 (LWP 101726) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  3    Thread 8031edc00 (LWP 101727) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
  2    Thread 8031ee000 (LWP 101728) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
* 1    Thread 8031ee400 (LWP 101729) runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
(gdb) 

Backtraces all look very similar:

(gdb) thread 238
[Switching to thread 238 (Thread 803083400 (LWP 101492))]
#0  runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
306 in /usr/local/go/src/runtime/sys_freebsd_amd64.s
(gdb) bt full
#0  runtime.osyield () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:306
No locals.
#1  0x0000000000484dcb in runtime.lockextra (nilokay=false, ~r1=0x0) at /usr/local/go/src/runtime/proc1.go:1065
        old = 0
#2  0x00000000004849ef in runtime.needm (x=0 '\000') at /usr/local/go/src/runtime/proc1.go:942
        mp = 0x0
#3  0x00000000004b04f1 in runtime.cgocallback_gofunc () at /usr/local/go/src/runtime/asm_amd64.s:742
No locals.
#4  0x00000000004b041a in runtime.cgocallback () at /usr/local/go/src/runtime/asm_amd64.s:714
No locals.
#5  0x00000000006aa488 in _cgoexp_564f5a6b344f_Go_xxfi_connect (a=0x7ffff57abdc8, n=32)
        fn = {void (struct github.com/Freeaqingme/gomilter._Ctype_struct_smfi_str *, github.com/Freeaqingme/gomilter._Ctype_char *, 
    struct github.com/Freeaqingme/gomilter._Ctype_struct_sockaddr *, github.com/Freeaqingme/gomilter._Ctype_sfsistat *)} 0x7ffff57abd50
#6  0x000000000075d23d in crosscall2 () at /usr/local/go/src/runtime/cgo/asm_amd64.s:36
No locals.
#7  0x00007ffff57abdc8 in ?? ()
No symbol table info available.
#8  0x0000000000000020 in ?? ()
No symbol table info available.
#9  0x0000000000000036 in ?? ()
No symbol table info available.
#10 0x00007ffff57abe00 in ?? ()
No symbol table info available.
#11 0x0000000000403c40 in ?? ()
No symbol table info available.
#12 0x00007ffff57abf28 in ?? ()
No symbol table info available.
#13 0x000000080307c280 in ?? ()
No symbol table info available.
#14 0x0000000801e20ff0 in ?? () from /lib/libc.so.7
No symbol table info available.
#15 0x000000080307c280 in ?? ()
No symbol table info available.
#16 0x0000000801e20ff0 in ?? () from /lib/libc.so.7
No symbol table info available.
#17 0x00007ffff57abe00 in ?? ()
No symbol table info available.
#18 0x0000000000403c88 in Go_xxfi_connect ()
No symbol table info available.
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

@ianlancetaylor
Copy link
Contributor

I don't know what is happening. The fact that it is happening for you on FreeBSD but not on GNU/Linux suggests a FreeBSD specific problem, but I don't know what it could be other than surprising behaviour of sched_yield.

Here is how it is supposed to work. There is a linked list of extra M structures to use for a callback from C to Go on a thread started by C (the structures are not needed on threads started by Go). That linked list starts out with one entry on it.

When there is a callback from C to Go, the code calls needm which calls lockextra(false). The lockextra function spins waiting for an M structure to be available. When one is available, if it is the only one available, the code sets the needextram flag. It then starts the callback.

When the thread gets to Go proper, but before it actually calls the Go callback function, it checks the needextram flag. If it is set, the thread calls newextram. The newextram function allocates a new M structure and adds it to the list (using another call to lockextra, this time passing true).

When the Go callback function completes, the runtime calls dropm. This adds the M structure back on the list, using another call to lockextra(true).

If a bunch of callbacks happen simultaneously, they will all collide getting an M structure. The first one to succeed will create a new M structure, handing it over to the next thread. When the callback completes, the M structure will be put back on the list.

So the behaviour we are likely to see is that the first time there are a bunch of parallel callbacks, there is contention in lockextra until all the M structures are allocated. When all the callbacks complete, the linked list will have a bunch of M structures, so there should be no more issues until there is again a bunch of even more parallel callbacks. Is that the behaviour you see? An initial set of collisions that resolve, followed by normal behaviour?

The parallel callbacks vying for an M can theoretically starve the callback adding a new M to the list. This is not supposed to happen because of the call to osyield, and because getting the lock and failing to find an M to use will call usleep(1), giving time for the new M to appear.

The call to usleep does add another possibility for OS-specific differences. The FreeBSD/AMD64 specific code looks fine to me, though. Does your truss listing show any calls to nanosleep?

@Freeaqingme
Copy link
Author

The fact that it is happening for you on FreeBSD but not on GNU/Linux suggests a FreeBSD specific problem

I agree. But, I haven't been able to 100% reproduce the input these freebsd boxes are getting on linux. So the fact that I haven't seen it on linux is not entirely conclusive. I'm planning to add a linux box to the set of boxes that have been demonstrating this behavior to see if it will occur there as well.

Is that the behaviour you see? An initial set of collisions that resolve, followed by normal behaviour?

Not entirely. I think we can agree on the fact that there's some contention somewhere, resulting in the 300 threads listed above. However, once the load begins to rise, the application doesn't recover. Even when we wait 20 minutes before killing it, no cgo callback is processed since the beginning of the outage. Other goroutines, including ones that invoke os/exec.Command().Run() continue running as should.

I think this last bit must be related somehow, given that I run the application on 10 machines. 3 of these machines run the os/exec.Command().Run() code path, and somehow these are the only instances exhibiting any problems.

Does your truss listing show any calls to nanosleep?

The truss output that we captured is only a 100 lines long. Because 90 lines of these were showing the exact same line we didn't bother capturing anything more. However, of these 100 lines there's no nanosleep() calls.

@ianlancetaylor
Copy link
Contributor

How long does it take this program to run on your FreeBSD system? On your GNU/Linux system?

package main

/*
#include <stddef.h>
#include <time.h>
#include <pthread.h>

#define THREADS (300)

extern void GoFn();

static void* thread(void* darg) {
    struct timespec ts;

    int *arg = (int*)(darg);
    ts.tv_sec = 0;
    ts.tv_nsec = 1000 * (THREADS - *arg);
    nanosleep(&ts, NULL);

    GoFn();

    return NULL;
}

static void CFn() {
    int i;
    pthread_t tids[THREADS];

    for (i = 0; i < THREADS; i++) {
        pthread_create(&tids[i], NULL, thread, (void*)(&i));
    }
    for (i = 0; i < THREADS; i++) {
        pthread_join(tids[i], NULL);
    }
}
*/
import "C"

import (
    "fmt"
    "time"
)

//export GoFn
func GoFn() {
    time.Sleep(time.Millisecond)
}

func main() {
    t := time.Now()
    C.CFn()
    fmt.Println(time.Since(t))
}

@Freeaqingme
Copy link
Author

Below I've got some results. Beware though that the linux machine has 8 cores (including hyperthreads), the freebsd machine only has two:

FreeBSD # GOTRACEBACK=crash GOPATH=`pwd` /usr/local/go/bin/go run -race  threads.go
# runtime/cgo
cc: warning: argument unused during compilation: '-pthread'
# runtime/race
cc: warning: argument unused during compilation: '-pthread'
# command-line-arguments
cc: warning: argument unused during compilation: '-pthread'
1.64223369s
`Linux # time  GOTRACEBACK=crash GOPATH=`pwd` /usr/local/go/bin/go run  -race  threads.go
113.884271ms

I've repeated the above a dozen times or so, all yielding similar results. One time however I again got a load that skyrocketed on FreeBSD. In order to stress the system a little bit more, I upped the THREADS constant to 650.

Linux # while true; do ./threads ; sleep 1; done
1.523410828s
271.040985ms
587.719518ms
681.638101ms
334.02606ms
366.330778ms

All results are similar to the ones posted above, the 1.5s is the highest number observed (even with GOMAXPROCS set to 1, results are not too dissimilar averaging at ~650ms).

Doing the same on FreeBSD though:

FreeBSD # while true; do ./threads ; sleep 1; done
751.868957ms
34.801136295s
3m59.019588714s
1.51715983s
12.29715116s
587.125403ms
4.297773294s
2.025705987s

Every few runs the load spikes. With the run that took >3 minutes, the load went up to 600. The >3 minute run is not an exception, but rather a regular phenomenon. Even though the symptoms are the same (load spikes to semi-infinite amounts), internally it's a little different. Truss shows:

nanosleep({0.000001000 })            = 0 (0x0)
nanosleep({0.000001000 })            = 0 (0x0)
nanosleep({0.000001000 })            = 0 (0x0)
nanosleep({0.000001000 })            = 0 (0x0)
nanosleep({0.000001000 })            = 0 (0x0)
nanosleep({0.000001000 })            = 0 (0x0)

A random backtrace from during such an event:

[Switching to thread 30 (Thread 801522400 (LWP 102052))]
#0  runtime.usleep () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:272
272     RET
(gdb) bt full
#0  runtime.usleep () at /usr/local/go/src/runtime/sys_freebsd_amd64.s:272
No locals.
#1  0x00000000004589b6 in runtime.lockextra (nilokay=false, ~r1=0x0) at /usr/local/go/src/runtime/proc1.go:1069
        old = 0
#2  0x000000000045852f in runtime.needm (x=0 '\000') at /usr/local/go/src/runtime/proc1.go:942
        mp = 0x0
        _g_ = 0x0
#3  0x0000000000480971 in runtime.cgocallback_gofunc () at /usr/local/go/src/runtime/asm_amd64.s:742
No locals.
#4  0x000000000048089a in runtime.cgocallback () at /usr/local/go/src/runtime/asm_amd64.s:714
No locals.
#5  0x000000000042c988 in _cgoexp_454a349e7b7e_GoFn (a=0x7fffb4da7f8f, n=0) at command-line-arguments/_obj/_cgo_gotypes.go:48
        fn = {void (void)} 0x7fffb4da7f10
#6  0x00000000004829dd in crosscall2 () at /usr/local/go/src/runtime/cgo/asm_amd64.s:36
No locals.
#7  0x00007fffb4da7f8f in ?? ()
No symbol table info available.
#8  0x0000000000000000 in ?? ()
No symbol table info available.
(gdb) 

@ianlancetaylor
Copy link
Contributor

According to https://golang.org/cl/18455, a sleep on FreeBSD always sleeps for the remainder of the time slice, which I think increases the sleep time from 1 microsecond to 1 millisecond. That might explain why FreeBSD tends to be slower than GNU/Linux when there is a lot of contention among threads waiting for an M structure.

@Freeaqingme
Copy link
Author

@ianlancetaylor Thanks for the link, hadn't found that one yet. That could surely explain why it's a little slower on FreeBSD than on Linux. But, if performance was a little worse that's something I could live with (in this particular instance performance is hardly a concern).

However, the fact that usleep() gives up the rest of the time slice, is sort of a constant factor I reckon. That wouldn't yet explain why every once in a while it would lock up on sched_yield() right? Or for that matter, why your test program takes 25 times as long in some runs compared to other runs?

@ianlancetaylor
Copy link
Contributor

Yes, there is something here I don't understand. But I'm thinking that I might understand why it's so much worse on FreeBSD. As you say, it's only a constant factor, but it's a constant factor of up to 1000.

Now that you point it out I also see a wide variance running my program on GNU/Linux. In 100 runs, the minimum is 18.227649ms, the maximum is 723.168546ms, the median is 53.283891ms, the mean is 108.02260827ms.

@Freeaqingme
Copy link
Author

I was just able to come up with this little snippet. I ran it ~10 times, and with two runs it showed the same problem almost right away. That is, load soaring, truss showing predominantly sched_yield() calls, but also some nanosleep() calls. A representative snippet:

sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
nanosleep({0.010000000 })            = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff93c9d80,0x5c6620,0x443b2922,0xffffffff8158df70,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff97cbd90,0x5c6620,0x443b28ef,0xffffffff8158e080,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff93c9d80,0x5c6620,0x443b2922,0xffffffff8158df70,0x443b2525) = 0 (0x0)
clock_gettime(4,{11447280.710362564 })       = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
clock_gettime(0,{1452803833.553310724 })     = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff97cbd90,0x5c6620,0x443b28ef,0xffffffff8158e080,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff95cad80,0x5c6620,0xffff807f009315f0,0xffffffff8158fae0,0x443b2075) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff93c9d80,0x5c6620,0x443b2922,0xffffffff8158df70,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff91c8d80,0x5c6620,0x443b2465,0xffffffff8158ef50,0x443b2075) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff95cad80,0x5c6620,0xffff807f009315f0,0xffffffff8158fae0,0x443b2075) = 0 (0x0)
sched_yield(0x14,0x7ffff93c9d80,0x5c6620,0x443b2922,0xffffffff8158df70,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff93c9d80,0x5c6620,0x443b2922,0xffffffff8158df70,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff93c9d80,0x5c6620,0x443b2922,0xffffffff8158df70,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
write(1,"2016-01-14 21:36:37.595712734 +0"...,42) = 42 (0x2a)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff97cbd90,0x5c6620,0x443b28ef,0xffffffff8158e080,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff91c8d80,0x5c6620,0x443b2465,0xffffffff8158ef50,0x443b2075) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
nanosleep({0.010000000 })            = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff95cad80,0x5c6620,0xffff807f009315f0,0xffffffff8158fae0,0x443b2075) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
clock_gettime(4,{11447280.725765584 })       = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8fc7d90,0x5c6620,0x443b291d,0xffffffff81590700,0x443b2525) = 0 (0x0)
sched_yield(0x14,0x7ffff8bc5d80,0x5c6620,0x443b290b,0xffffffff8158edd0,0x443b24c1) = 0 (0x0)
sched_yield(0x14,0x7ffff8dc6d80,0x5c6620,0x443b28fa,0xffffffff8158e020,0x443b2525) = 0 (0x0)

Code:

package main

/*
#include <stddef.h>
#include <time.h>
#include <pthread.h>

#define THREADS (50)

extern void GoFn();

static void* thread(void* darg) {
    struct timespec ts;

    int *arg = (int*)(darg);
    ts.tv_sec = 0;
    ts.tv_nsec = 1000 * (THREADS - *arg);
    nanosleep(&ts, NULL);

    GoFn();

    return NULL;
}

static void CFn() {
    int i;
    pthread_t tids[THREADS];

    for (i = 0; i < THREADS; i++) {
        pthread_create(&tids[i], NULL, thread, (void*)(&i));
    }
    for (i = 0; i < THREADS; i++) {
        pthread_join(tids[i], NULL);
    }
}
*/
import "C"

import (
    "bytes"
    "fmt"
    "os/exec"
    "time"
)

//export GoFn
func GoFn() {
    time.Sleep(time.Millisecond)
}

func main() {

    go cmdTimer()
    ticker := time.NewTicker(time.Millisecond * 1000)
    for _ = range ticker.C {
        C.CFn()
    }
}

func cmdTimer() {
    t := time.Now()

    cmdRun := func(id string) {
        cmd := exec.Command("iostat")
        cmd.Dir = "/"
        var out bytes.Buffer
        cmd.Stdout = &out
        cmd.Run()
        fmt.Println(t.String() + " "  + id)
    }

    ticker := time.NewTicker(time.Millisecond * 500)
    for _ = range ticker.C {
        go cmdRun("1")
        go cmdRun("2")
        go cmdRun("3")
        go cmdRun("4")
    }

}

Using a larger number of threads (e.g. 250 instead of 50) triggers the issue almost immediately.

@ianlancetaylor
Copy link
Contributor

At this point in the release cycle I think we have to postpone to 1.7.

@ianlancetaylor ianlancetaylor modified the milestones: Go1.7, Go1.6Maybe Jan 25, 2016
@Freeaqingme
Copy link
Author

Yeah, makes sense. For what it's worth, we migrated this application off to linux 6 days ago and it hasn't shown since - under an identical load as with which we saw this behavior before within 12 hours. So far so good. knocks on wood

@Freeaqingme
Copy link
Author

@ianlancetaylor What shall we do with this issue? Personally I have no interest in pursuing this issue any further. It appears the exact same set of syscalls is performed on FreeBSD as is on Linux, and it works perfectly fine on the latter. My conclusion would be that it's some sort of race condition in the FreeBSD scheduler

@Freeaqingme
Copy link
Author

@ianlancetaylor Scratch what I said above about FreeBSD. I also just witnessed the very same issue on Linux. Difference with FreeBSD would be that we witnessed the same issue there every day, and so far (3 to 4 months in) it's the first time we've seen it on Linux.

Strace output simply showed lots of sched_yield() calls, I also have a pprof dump. The only thing I wasn't able to attain was a coredump (at least I was able to install gdb with a load of 1000...).

@adamfaulkner
Copy link

adamfaulkner commented May 5, 2016

I think I'm hitting this issue too on linux 3.16. I eventually see all of my threads that call go code from C start spending 100% of their time in runtime.osyield. Strace also shows a ton of sched_yield calls.

I'm currently seeing if using fewer threads makes this less likely to happen. If it happens again I'll see if I can repro with a smaller example.

@Freeaqingme Freeaqingme changed the title runtime: infinite loop in ./src/runtime/proc1.go lockextra() on FreeBSD runtime: infinite loop in ./src/runtime/proc1.go lockextra() on both Linux & FreeBSD May 5, 2016
@quentinmit quentinmit added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label May 19, 2016
@rsc
Copy link
Contributor

rsc commented May 27, 2016

/cc @dvyukov

@dvyukov
Copy link
Member

dvyukov commented May 27, 2016

@adamfaulkner What version of Go? Can you provide a repro?

@adg
Copy link
Contributor

adg commented Jun 7, 2016

This was postponed to 1.7 from 1.6. Will it miss the boat again? We only have a week or so to formulate a fix here.

@dvyukov
Copy link
Member

dvyukov commented Jun 7, 2016

100% CPU consumption is expected effect of a global spin mutex. If more than 1 thread is doing cgo callback on external thread, all but one wait. If average rate of calls is higher than 1/num_threads, then the process locks up as described.

Shared library support added another global mutex on that path. Now it is also used by C traceback support. If we resolve lockextra, the bottleneck will just move to another place.

@Freeaqingme
Copy link
Author

@dvyukov I'm not sure I follow you entirely. I don't think having some sort of bottleneck is a problem, but the fact that everything locks up, and then stays locked up is a problem. If we can prevent the latter I'd be a happy man.

@dvyukov
Copy link
Member

dvyukov commented Jun 7, 2016

The description says "It appears as if the application keeps running as should".

@Freeaqingme
Copy link
Author

That's a valid point. What I meant to say was that because the load keeps on rising, even though my load balancers stop feeding the application new data. So eventually the entire machine becomes unusable, and the application needs to be killed. I wanted to see if things would settle automatically, but it didn't. However, the load by that time had grown to >1000 so eventually the entire machine 'locks' up due to load.

@davecheney
Copy link
Contributor

I don't think a high loadavg will cause an operating system to lock up.
Loadavg is just a measure of the number of processes on the run queue.

What is the resource that causes your machine to lock up, my guess is
available memory. Can you please give some more details.

On Wed, 8 Jun 2016, 05:07 Dolf Schimmel notifications@github.com wrote:

That's a valid point. What I meant to say was that because the load keeps
on rising, even though my load balancers stop feeding the application new
data. So eventually the entire machine becomes unusable, and the
application needs to be killed. I wanted to see if things would settle
automatically, but it didn't. However, the load by that time had grown to

1000 so eventually the entire machine 'locks' up due to load.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#13926 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/AAAcA4JTpjeA_OJ-7zTTOmuTcElmGvTLks5qJcGHgaJpZM4HDelZ
.

@dvyukov
Copy link
Member

dvyukov commented Jun 8, 2016

I've tried to reproduce the issue on tip on linux/amd64 using the program from #13926 (comment) (as is and with various changes that might increase probability of bugs in extram).
Works like a charm. No hangs.

@ianlancetaylor
Copy link
Contributor

We clearly aren't going to do anything here for 1.7.

@ianlancetaylor ianlancetaylor modified the milestones: Go1.8, Go1.7 Jun 14, 2016
@ianlancetaylor
Copy link
Contributor

For 1.8 perhaps we should consider storing extram on the C side, not the Go side. Then we can use C mutexes and condition variables, and avoid the busy wait.

Of course better still would be to avoid the global lock, but it's not clear to me how to do that.

@gopherbot
Copy link

CL https://golang.org/cl/25047 mentions this issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-FreeBSD OS-Linux
Projects
None yet
Development

No branches or pull requests

10 participants