Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python extension written in Go ignores Ctrl+C

Tags:

c

python-3.x

go

I am using a python driver that's written in go, the driver connects to a certain service and do some processing. We found an issue with an "unheanlthy" instance of the service and that was making the driver to get stuck and was impossible to terminate unless we kill the process.

Doing some experimentation I saw that when an extension written in go is executed the program ignores the Ctrl+C commands issued until control comes back to Python, not happening this with a C written extension, in which the KeyboardInterrupt is raised while executing C code. My question is why this happens and if there's a way to circunvent this problem or issue a kind of timeout. Tried to raise an exception to emulate a timeout using the threading.Timer but the problem is that the exception is thrown in it's own thread and doesn't interrupt the main thread. Tested in Python (cpython) 3.9 and 3.10. As for the extensions I wrote as a poc the Go version is 1.20.4 and the C compiler is 9.4.0.

I'll leave a small poc below.

Python code:

import ctypes

import ctypes
go_library = ctypes.cdll.LoadLibrary('./go_library.so')
hello_Go = go_library.helloWorld

c_library = ctypes.cdll.LoadLibrary("./c_library.so")
hello_c = c_library.helloWorld

try:
    print("Calling golang code")
    hello_Go()

    print("Calling C code")
    hello_c()
except KeyboardInterrupt:
    print("Ctrl+C issued DD:")
finally:
    print("Done")

Go extension

package main

import (
   "C"
   "log"
   "time"
)

func helloWorld(){
   log.Println("Hello World")
   time.Sleep(10 * time.Second)
   log.Println("Done sleeping")
}

func main(){
}

C extension

#include <stdio.h>

int helloWorld() {
    printf("Hello from C\n");
    sleep(10);
    printf("Done sleeping from C\n");

    return 0;
}
like image 635
puerkito66 Avatar asked May 28 '26 19:05

puerkito66


1 Answers

I met same case. After some research, I found the solution.

According to this answer:

Python has a signal handler installed on SIGINT which simply sets a flag that is checked by the main interpreter loop. For this handler to work properly, the Python interpreter has to be running Python code.

It seems that when running golang code, the golang call the python installed SIGINT signal handler to set a flag. So when running back to python, a KeyboardInterrupt is deteced.

And from golang doc:

(When Non-Go programs that call Go code) If Notify is called for an asynchronous signal, a Go signal handler will be installed for that signal. If, later, Reset is called for that signal, the original handling for that signal will be reinstalled, restoring the non-Go signal handler if any.

So, the following code will catch SIGINT in go code and return the function immediately when SIGINT.

func helloWorld() {
    c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGINT)

    // the original handling for that signal will be reinstalled, restoring the non-Go signal handler if any.
    defer signal.Reset(syscall.SIGINT)

    go func() {
        log.Println("Hello World")
        time.Sleep(10 * time.Second)
        log.Println("Done sleeping")
        c <- nil

    }()

    // wait go routine to finish or signal received
    <-c
}
like image 111
WangWeimin Avatar answered May 31 '26 08:05

WangWeimin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!