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: signalfd is not working after shared library compiled by cgo is loaded #55066

Open
spacewander opened this issue Sep 14, 2022 · 6 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@spacewander
Copy link
Contributor

What version of Go are you using (go version)?

$ go version
go version go1.19 linux/amd64

Does this issue reproduce with the latest release?

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.19"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1290319317=/tmp/go-build -gno-record-gcc-switches"

What did you do?

When I try to call Go code in C, I figure out that the signalfd will stop working. I read https://pkg.go.dev/os/signal#hdr-Non_Go_programs_that_call_Go_code but it doesn't mention signalfd.

What did you expect to see?

Here is a minimal example:

The C code which calls the Go code:

#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include "libcgo.h"

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
    sigset_t mask;
    int sfd;
    struct signalfd_siginfo fdsi;
    ssize_t s;

    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGQUIT);

    /* Block signals so that they aren't handled
        according to their default dispositions */

    if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1)
        handle_error("sigprocmask");

    sfd = signalfd(-1, &mask, 0);
    if (sfd == -1)
        handle_error("signalfd");

    call();
    for (;;) {
        s = read(sfd, &fdsi, sizeof(struct signalfd_siginfo));
        if (s != sizeof(struct signalfd_siginfo))
            handle_error("read");

        if (fdsi.ssi_signo == SIGINT) {
            printf("Got SIGINT\n");
        } else if (fdsi.ssi_signo == SIGQUIT) {
            printf("Got SIGQUIT\n");
            exit(EXIT_SUCCESS);
        } else {
            printf("Read unexpected signal\n");
        }
    }
}

The Go code:

package main

/*
#cgo LDFLAGS: -shared -ldl -lpthread
#include <stdlib.h>
*/
import "C"
import (
        //"time"
)

func main() {
}

//export call
func call() {
        go func() {
                println("AC")
        }()
        //time.Sleep(time.Second)
}

Compile the Go code with go build -o libcgo.so -buildmode=c-shared *.go.
Then compile the C code with clang -L. -lcgo main.c. After that, run ./a.out.

I expected to see:

./a.out
^CGot SIGINT
^CGot SIGINT

SIGINT is captured by signalfd.

What did you see instead?

 $ ./a.out
^C
$ 

The signalfd is not working.

@ianlancetaylor
Copy link
Contributor

What is the output of strace -f PROGRAM?

@ianlancetaylor ianlancetaylor changed the title cgo: signalfd is not working after shared library compiled by cgo is loaded runtime: signalfd is not working after shared library compiled by cgo is loaded Sep 14, 2022
@gopherbot gopherbot added the compiler/runtime Issues related to the Go compiler and/or runtime. label Sep 14, 2022
@ianlancetaylor ianlancetaylor added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. and removed compiler/runtime Issues related to the Go compiler and/or runtime. labels Sep 14, 2022
@ianlancetaylor ianlancetaylor added this to the Backlog milestone Sep 14, 2022
@spacewander
Copy link
Contributor Author

Interesting, when I run it with strace -f ./a.out, SIGINT is captured:

[pid  9963] write(2, "AC\n", 3 <unfinished ...>
AC
[pid  9962] <... rt_sigprocmask resumed>NULL, 8) = 0
[pid  9964] <... nanosleep resumed>NULL) = 0
[pid  9963] <... write resumed>)        = 3
[pid  9962] read(3,  <unfinished ...>
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
[pid  9963] futex(0x7f7cb23ddcc8, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  9964] <... nanosleep resumed>NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=40000}, NULL) = 0
[pid  9964] futex(0x7f7cb23ddcc8, FUTEX_WAKE_PRIVATE, 1) = 1
[pid  9963] <... futex resumed>)        = 0
[pid  9964] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
[pid  9963] futex(0x7f7cb23ddcc8, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  9964] <... nanosleep resumed>NULL) = 0
[pid  9964] futex(0x7f7cb23de078, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0} <unfinished ...>
[pid  9962] <... read resumed>"\2\0\0\0\0\0\0\0\0\0\0\0R\20\0\0\350\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 128) = 128
[pid  9963] <... futex resumed>)        = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
[pid  9962] fstat(1,  <unfinished ...>
[pid  9963] futex(0x7f7cb23ddcc8, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
[pid  9962] <... fstat resumed>{st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
[pid  9962] write(1, "Got SIGINT\n", 11Got SIGINT
) = 11
[pid  9962] read(3, ^C"\2\0\0\0\0\0\0\0\200\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 128) = 128

But when I run it with ./a.out, the signal is escaped...

./a.out
AC
^C

@ianlancetaylor
Copy link
Contributor

Interesting. That suggests that 1) there could be a race between calling signalfd and the Go runtime setting up its own signal handling; 2) that it matters who wins. Maybe.

@spacewander
Copy link
Contributor Author

According to https://pkg.go.dev/os/signal#hdr-Non_Go_programs_that_call_Go_code

For -buildmode=c-shared the Go runtime will initialize signals when the shared library is loaded.

Does this initialization happen async? AFAIK, the shared library loading happens before calling main function, which will call signalfd.

@spacewander
Copy link
Contributor Author

The real-world case I hit is using dlopen to load the shared library.
I used dlopen to load the Go library first, then called signalfd.

@ianlancetaylor
Copy link
Contributor

The Go signal handlers should be installed when you dlopen the shared library. They don't happen asynchronously.

When a Go library is built with -buildmode=c-shared, the Go runtime should not install a signal handler for SIGINT at all.

I don't know what is happening.

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

No branches or pull requests

3 participants