SIGINT handling in a c-shared library?

608 views
Skip to first unread message

Jason E. Aten

unread,
Oct 22, 2015, 9:00:02 PM10/22/15
to golang-nuts
If I build a c-shared library out of golang code that handles SIGINT, and my host C-program also handles SIGINT, how should they share SIGINT handling duties?

I'm seeing a crash on OSX when I load my c-shared go library under R.  

I'm not seeing a crash under linux, but I do observe that the c-shared golang code appears to hijack the signal handling.  For this last, I filed https://github.com/golang/go/issues/13028

I tried read through the go1.5.1/src/os/signal, but I don't see anywhere were "an already installed signal handler" would be chained-in by a .so loaded signal handler. Is that the design? If not by chaining, how *should* SIGINT handling work?

Thank you!

Ian Lance Taylor

unread,
Oct 23, 2015, 2:48:19 PM10/23/15
to Jason E. Aten, golang-nuts
The Go signal handler will chain the signal to C for a synchronous
signal like SIGSEGV, SIGFPE, SIGBUS. But it will not do so for
SIGINT, because SIGINT is not a synchronous signal--it can only be
generated by a call to kill, possibly via pressing the INTR key on the
terminal. For a non-synchronous signal, it would not make sense to
try to pass the signal to both languages. They need to cooperate
somehow.

Right now the Go runtime will look at the current SIGINT handler. If
it is SIG_IGN, the runtime will leave it ignored. Otherwise the
runtime will install a signal handler for it. On tip see initsig in
runtime/signal1_unix.go. If the signal is received, the program will
exit.

This is probably not the right behaviour when building with
-buildmode=c-shared. We haven't really tried to think through that
case. That said, a key point is that Go runs with very small stacks
and so any signal handler must run on the alternate signal stack. It
will not work for a non-Go program to try to install a signal handler
that does not have SA_ONSTACK set.

Ian

Jason E. Aten

unread,
Oct 23, 2015, 4:20:51 PM10/23/15
to golang-nuts, j.e....@gmail.com
Cool -- this is very helpful.  I'm thinking aloud here... Feel free to let me know if this doesn't make sense... :)

The hosting program (R) can't really know what the guest (c-shared library) is going to do or need. 

* So it makes sense for the guest library to simply install 
   whatever signal handling it needs in order to get its own job done.  

* And then the guest can chain back to the host if needed/desired. 
   The guest signal.Notify() will be sure to be using SA_ONSTACK, so all is good there.

* R does use SA_ONSTACK for SIGSEGV, SIGILL, and SIGBUS signals, but 
   curiously not for SIGINT. Drat! :-)

Once the guest is loaded, with signal.Notify() I can easily add 
signal handling in the guest.  So I could readily and manually 
write code to chain SIGINT back to the host. But by the time 
my go code runs in the guest, the previous SIGINT handler 
has already been lost.  Ouch!

So, if I'm thinking this through right, I just need a way to 
discover what the previously installed signal handler's address. 

Possibilities:

a) It looks like tip/src/runtime/signal1_unix.go already has the runtime.fwdSig array
 which holds this information.  But it is private and unexported, and I don't see an 
API to it. 

And it would be fragile to depend on such an implementation detail.  
But if I was desperately pressed into a corner, reflection could be used to read it, no?

b) If I could run some C code before the Go runtime is initialized 
upon library load, I could save the previous handler in a global 
myself. Perhaps I just need to make a thin wrapper library that 
loads the c-shared library as a dependency. That would keep
 things more portable.

Or other options?

Thanks for the helpful thoughts!

Jason

Ian Lance Taylor

unread,
Oct 23, 2015, 5:46:23 PM10/23/15
to Jason E. Aten, golang-nuts
On Fri, Oct 23, 2015 at 1:20 PM, Jason E. Aten <j.e....@gmail.com> wrote:
>
> Cool -- this is very helpful. I'm thinking aloud here... Feel free to let
> me know if this doesn't make sense... :)
>
> The hosting program (R) can't really know what the guest (c-shared library)
> is going to do or need.
>
> * So it makes sense for the guest library to simply install
> whatever signal handling it needs in order to get its own job done.
>
> * And then the guest can chain back to the host if needed/desired.
> The guest signal.Notify() will be sure to be using SA_ONSTACK, so all is
> good there.
>
> * R does use SA_ONSTACK for SIGSEGV, SIGILL, and SIGBUS signals, but
> curiously not for SIGINT. Drat! :-)

For a signal like SIGINT, it's not obvious to me when you would chain
back and when you would not. Are you suggesting that the Go runtime
should pass SIGINT to any os/signal.Notify channel, and then always
invoke the existing signal handler if there is one?


> Once the guest is loaded, with signal.Notify() I can easily add
> signal handling in the guest. So I could readily and manually
> write code to chain SIGINT back to the host. But by the time
> my go code runs in the guest, the previous SIGINT handler
> has already been lost. Ouch!
>
> So, if I'm thinking this through right, I just need a way to
> discover what the previously installed signal handler's address.
>
> Possibilities:
>
> a) It looks like tip/src/runtime/signal1_unix.go already has the
> runtime.fwdSig array
> which holds this information. But it is private and unexported, and I
> don't see an
> API to it.
>
> And it would be fragile to depend on such an implementation detail.
> But if I was desperately pressed into a corner, reflection could be used to
> read it, no?

No, the reflect package doesn't let you get around what the language
permits. You would have to modify the runtime package.


> b) If I could run some C code before the Go runtime is initialized
> upon library load, I could save the previous handler in a global
> myself. Perhaps I just need to make a thin wrapper library that
> loads the c-shared library as a dependency. That would keep
> things more portable.

Yes, that should work.

Ian

Jason E. Aten

unread,
Oct 23, 2015, 6:47:33 PM10/23/15
to Ian Lance Taylor, golang-nuts

> * R does use SA_ONSTACK for SIGSEGV, SIGILL, and SIGBUS signals, but
>    curiously not for SIGINT. Drat! :-)

For a signal like SIGINT, it's not obvious to me when you would chain
back and when you would not. 

Agreed!
 
Are you suggesting that the Go runtime
should pass SIGINT to any os/signal.Notify channel, and then always
invoke the existing signal handler if there is one?

Oh, I didn't mean to suggest that. I don't think "always" makes much sense, would it?

Each host:guest pairing may differ in their needs, after all.

I only would like to be able to easily write the small boilerplate init() function in my guest c-shared library that did the chaining if required.  Something like
 
func init() {
  go func() {
     previousSigIntHandler := os.FwdSig(os.SIGINT)  // there's a better API than this, likely!

    ctrlC_Chan := make(chan os.Signal, 1);   signal.Notify(ctrlC_Chan, os.Interrupt) 

    for { select { case <-ctrlC_Chan: doLocalShutdown(); previousSigIntHandler(0); } } } // chain it
  }
}

...

>
> And it would be fragile to depend on such an implementation detail.
> But if I was desperately pressed into a corner, reflection could be used to
> read it, no?

No, the reflect package doesn't let you get around what the language
permits.  You would have to modify the runtime package.

Ah okay, good to know.

> b) If I could run some C code before the Go runtime is initialized
> upon library load, I could save the previous handler in a global
> myself. Perhaps I just need to make a thin wrapper library that
> loads the c-shared library as a dependency. That would keep
>  things more portable.

Yes, that should work.

Prototyping that seems to work! Yess!  In fact I find I can avoid a 2nd library by using the constructor attribute in my CGO C code to grab the existing host SIGINT handler before the c-shared go runtime gets started by doing this:

struct sigaction starting_act;

void __attribute__ ((constructor)) my_init(void) {  sigaction(SIGINT, NULL, &starting_act);}

The only downside here is thtat this is likely gcc or gcc-and-clang only, this ((constructor)) gnu attribute.

So excellent progress!  I'm still stuck on OSX because there seems to be some kind of segfaulting bug in the c-shared signal handling on OSX ( I filed https://github.com/golang/go/issues/13034 for this), but I'm almost there(!)  If you have thoughts on how I might work around 13034, I would be most happy to try them out.

Jason

Jason E. Aten

unread,
Oct 24, 2015, 12:10:14 AM10/24/15
to Ian Lance Taylor, golang-nuts
I note for anyone else searching for similar solutions in the future that I successfully worked around the OSX issue https://github.com/golang/go/issues/13034 by using the following strategy.

I observed that any workaround needed to avoid go code ever receiving SIGINT; else per #13034 the process will crash. This leads to the following approach:

1.  In the C code that is linked with my CGO go code to make the cshared library, I define a ((constructor)) function that saves the R SIGINT handler, and calls sigaction to set the SIGINT handle to SIG_IGN. This means that the go runtime won't install its own handler.  Note that on other platforms, an ELF .init approach may be needed as this is a gnu extension. Michael Ambrus's answer o http://stackoverflow.com/questions/2053029/how-exactly-does-attribute-constructor-work/2053078#2053078 has some useful discussion of alternatives here.

2. In a go init() routine (i.e. after the runtime has initialized), I restore the R SIGINT handler with another sigaction() call.

The result:

Now Ctrl-C (SIGINT) goes to R consistently, and never to the go runtime inside the cshared library. My library go code doesn't need to use signal.Notify() at all; it just polls back to R at regular intervals using R_CheckUserInterrupt() per the writing R extensions docs; https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Allowing-interrupts

Jason
Reply all
Reply to author
Forward
0 new messages