How to run deferred function in main() on panic() in a goroutine?

663 views
Skip to first unread message

su...@kync.com

unread,
Feb 15, 2017, 8:27:23 AM2/15/17
to golang-nuts
Hi,

I have a situation that need some advice. I have a main loop that is calling some C code (potentially looping forever).  and I need to handle signals like SIGABRT correctly by ensuring a cleanup function was run. 

func main() {

  cleanup := func() {}
  defer cleanup()

  for {
     C.do_something()
  }

}

now , this works for synchronous signal / or anything caused by C code, because they will generate runtime panic on main thread, and deferred cleanup function will run. 

However, if I send signal through kill, the signal arrived in another thread, and apparently , main()'s defered functions  was never called , how do I catch that situation ?   I tried with a custom signal handler, but since I am busy looping in C code, I can't invoke panic() on the main thread.

Ian Lance Taylor

unread,
Feb 15, 2017, 9:56:51 AM2/15/17
to su...@kync.com, golang-nuts
Use the os/signal package.

Ian

Yucong Sun

unread,
Feb 15, 2017, 12:44:53 PM2/15/17
to golang-nuts
hi,

Maybe I wasn't clear, I am indeed using os.signal package. But it doesn't do what I want! I want to trigger a panic in main function , seems there is no way to do it by using a signal channel .

Thanks

Axel Wagner

unread,
Feb 15, 2017, 1:03:31 PM2/15/17
to Yucong Sun, golang-nuts
First, the code you wrote here doesn't use os/signal, so you can't really fault Ian for giving that advice. Here's my understanding:

In general, there are two kinds of signals: synchronous ones, raised e.g. by dereferencing invalid memory. They produce a runtime panic at the site that produced them in the corresponding goroutine. And asynchronous ones, raised e.g. by using the kill command or os.Kill. They do not produce a runtime panic but instead need to be handled via os/signal.

Now, to "catch" asynchronous panic's, you need to use os/signal and if you need them to produce a runtime-panic on the goroutine running main, you need to read from that channel in main and produce a panic whenever you read a value. You could, for example, do this:

func main() {
    ch := make(chan os.Signal)
    signal.Notify(ch, mySignals)
    
    errs := make(chan interface{})

    go func() {
        defer func() {
            errs <- recover()
        }()
        for {
            C.do_something()
        }
    }()

    for {
        select {
        case s := <-ch:
            panic(fmt.Sprintf("received signal: %v", s))
        case v := <-errs:
            panic(fmt.Sprintf("recovered runtime panic in C-code: %v", v))
        }
    }
}

this will get asynchronous signals via os/signal and synchronous signals in the C-code raising runtime-panics via recover.

Now, what is *not* possible, is to somehow recover from runtime panics caused by synchronous signals in *other* goroutines. The spec is pretty clear about that; a panic that isn't recovered will cause the program to crash.

Hope this helps.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Yucong Sun

unread,
Feb 15, 2017, 1:15:02 PM2/15/17
to Axel Wagner, golang-nuts
Thanks axle, but are you sure the code would work? it is essentially
triggering panic on the main go routine while recovering from another
goroutine, isn't that impossible?

If this is possible, why couldn't I trigger a panic on a goroutine
then recover it from main goroutine?
>> email to golang-nuts...@googlegroups.com.

Yucong Sun

unread,
Feb 15, 2017, 1:22:58 PM2/15/17
to Axel Wagner, golang-nuts
https://play.golang.org/p/7Vz0o4ZoQF

shows that your code doesn't really work. My understanding is once
panic reaches the current goroutine's top of callstack, program will
crash, there are no attempt to call other goroutine's deferred
function.

So, in order to do what I want, i must be able to trigger some sort
of inline panic remotely. Note in the C code this is easy, the
signal handler will interrupt current thread, jump directly to the
handler code and resume. If I can do that in golang, i will be able
to trigger a panic in main() and do what I want (or do what the old C
code does, which i was porting into go)

Yucong Sun

unread,
Feb 15, 2017, 1:27:43 PM2/15/17
to Axel Wagner, golang-nuts
Also, my cleanup function must be tied directly with C loop, because
they are sharing the same underlaying memory/state . If I directly
run cleanup() while C loop was running, there could be race.

So that leaves me with no choice, i must be able to panic on the C
loop's go-routine on async signals, which I can't do with current
tools.

roger peppe

unread,
Feb 15, 2017, 1:29:08 PM2/15/17
to Yucong Sun, Axel Wagner, golang-nuts
Could you run the C code in its own OS thread by
using LockOSThread and then send a signal to that thread directly?

Axel Wagner

unread,
Feb 15, 2017, 1:35:37 PM2/15/17
to Yucong Sun, golang-nuts
You misunderstood what I was trying to do (the recover was supposed to recover from any panic raised by the C-code, not from the panic of the main loop). And just added a new requirement.

I don't think what you want is possible, from what I know about the go runtime. You could approximate it, by running C.do_anything and reading from the signal-channel alternately, but that would still delay any signal handling to after your C-code is done.

But your requirements also seems more and more obscure to me; I could kind of understand why the C code and the cleanup code must run in the same goroutine/thread, but it seems obscure, that the asynchronous signal handling *also* needs to run in that thread.

Anyway. Maybe someone else knows more about all of this than me and can help you :)

Ian Lance Taylor

unread,
Feb 15, 2017, 1:39:03 PM2/15/17
to Yucong Sun, Axel Wagner, golang-nuts
On Wed, Feb 15, 2017 at 10:26 AM, Yucong Sun <suny...@gmail.com> wrote:
> Also, my cleanup function must be tied directly with C loop, because
> they are sharing the same underlaying memory/state . If I directly
> run cleanup() while C loop was running, there could be race.
>
> So that leaves me with no choice, i must be able to panic on the C
> loop's go-routine on async signals, which I can't do with current
> tools.

I sort of feel like you've set yourself an impossible task by saying
"I must induce a panic on the main goroutine." You can't do that,
except by having the main goroutine sit around waiting on a channel
telling it when to panic.

But that is not your real problem. I think you should step back and
figure out what your real problem is. If your real problem is "I want
to do something when the program exits," then that is easy enough to
do by putting that thing into function F, and having a separate
goroutine that listens on an os/signal channel and invokes F and then
os.Exit when a signal arrives.

Ian

Yucong Sun

unread,
Feb 15, 2017, 1:44:29 PM2/15/17
to Axel Wagner, golang-nuts
So, to give more background, i am trying to porting a C based
interpreter to Go. In the single threaded C code, the signal is
always handled by interrupting whatever code was executing and
directly exit after executing some "crash()" cleanup function.

Now, I ported the main function to Go, as I demonstrated above, there
is no way to "interrupt" C code anymore, so I can't run crash()
cleanly as before. This seems to be a limitation of Go, and I couldn't
think of any way to work around it.

On Wed, Feb 15, 2017 at 10:35 AM, Axel Wagner
>> >>>> email to golang-nuts...@googlegroups.com.

Ian Lance Taylor

unread,
Feb 15, 2017, 1:56:30 PM2/15/17
to Yucong Sun, Axel Wagner, golang-nuts
On Wed, Feb 15, 2017 at 10:43 AM, Yucong Sun <suny...@gmail.com> wrote:
> So, to give more background, i am trying to porting a C based
> interpreter to Go. In the single threaded C code, the signal is
> always handled by interrupting whatever code was executing and
> directly exit after executing some "crash()" cleanup function.

Why can't you do exactly that in Go?


> Now, I ported the main function to Go, as I demonstrated above, there
> is no way to "interrupt" C code anymore, so I can't run crash()
> cleanly as before. This seems to be a limitation of Go, and I couldn't
> think of any way to work around it.

If you want to implement the precise behavior of a single-threaded C
program, then you need to write a single-threaded Go program. But
that goal is clearly folly. Don't try to write C code in Go. When
you are using Go, write Go code. That means: arrange your program as
a Go program.

Ian

Yucong Sun

unread,
Feb 15, 2017, 2:12:07 PM2/15/17
to Ian Lance Taylor, Axel Wagner, golang-nuts
In single threaded C code, The interrupter loop is guaranteed stop
when crash() is executing, Now in Go code, however, the interrupter
loop could still be executing when crash() is running. because there
is no way to interrupt a go routine beside a panic(), but like you
said, i can't induce panic on another go routine, so I can't do what i
want.

Now, I get what you are saying, I need to invest some time on C code's
main loop to make it interruptible by other thread . which maybe what
I need to do.

Axel Wagner

unread,
Feb 15, 2017, 2:42:34 PM2/15/17
to Yucong Sun, Ian Lance Taylor, golang-nuts
You might also want to consider running the stuff in a separate process. But honestly, you can't really be sure anyway, for asynchronous signals, because they are - as the name suggest - asynchronous. Between you calling kill and the signal actually being delivered and interrupting the process, it can just continue as it where.
Reply all
Reply to author
Forward
0 new messages