Installing signal handler with Python
Asked Answered
C

2

1

(there is a follow up to this question here)

I am working on trying to write a Python based Init system for Linux but I'm having an issue getting signals to my Python init script. From the 'man 2 kill' page:

The only signals that can be sent to process ID 1, the init process,  are  those for which init has explicitly installed signal handlers.

In my Python based Init, I have a test function and a signal handler setup to call that function:

def SigTest(SIG, FRM):
    print "Caught SIGHUP!"

signal.signal(signal.SIGHUP, SigTest)

From another TTY (the init script executes sh on another tty) if I send a signal, it is completely ignored and the text is never printed. kill -HUP 1

I found this issue because I wrote a reaping function for my Python init to reap its child processes as they die, but they all just zombied, it took awhile to figure out Python was never getting the SIGCHLD signal. Just to ensure my environment is sane, I wrote a C program to fork and have the child send PID 1 a signal and it did register.

How do I install a signal handler the system will acknowledge if signal.signal(SIG, FUNC) isn't working?

Im going to try using ctypes to register my handler with C code and see if that works, but I rather a pure Python answer if at all possible.

Ideas?

( I'm not a programmer, Im really in over my head here :p )

Test code below...

import os
import sys
import time
import signal


def SigTest(SIG, FRM):
    print "SIGINT Caught"

print "forking for ash"
cpid = os.fork()
if cpid == 0:
    os.closerange(0, 4)
    sys.stdin = open('/dev/tty2', 'r')
    sys.stdout = open('/dev/tty2', 'w')
    sys.stderr = open('/dev/tty2', 'w')
    os.execv('/bin/ash', ('ash',))

print "ash started on tty2"

signal.signal(signal.SIGHUP, SigTest)

while True:
    time.sleep(5.0)
Cabrales answered 29/4, 2011 at 1:11 Comment(0)
P
1

Signal handlers mostly work in Python. But there are some problems. One is that your handler won't run until the interpreter re-enters it's bytecode interpreter. if your program is blocked in a C function the signal handler is not called until it returns. You don't show the code where you are waiting. Are you using signal.pause()?

Another is that if you are in a system call you will get an exception after the singal handler returns. You need to wrap all system calls with a retry handler (at least on Linux).

It's interesting that you are writing an init replacement... That's something like a process manager. The proctools code might interest you, since it does handle SIGCHLD.

By the way, this code:

import signal

def SigTest(SIG, FRM):
    print "SIGINT Caught"

signal.signal(signal.SIGHUP, SigTest)

while True:
    signal.pause()

Does work on my system.

Phraseologist answered 29/4, 2011 at 1:58 Comment(14)
I am not using signal.pause(), I'll try it. I edited the post with the test code i was usingCabrales
Aha, the sleep() function is one that will block your signal handlers until it's finish. That's why I had to write an alternative sleep module. It calls the C API PyErr_CheckSignals when interrupted. This enables signal handlers to be called.Phraseologist
changing sleep() to signal.pause() didn't work either. I also tried changing the sleep() to 0.01 (so as to make it seem more instantaneous) and the signal was still never caught. The kernel? must be blocking all signals... there must be a difference between the way a C prog and a Python prog install signal handlers.Cabrales
There is a difference in the way it handles them. As I said, I do use signals with Python, but with the changes I made.Phraseologist
doing a strace on a compiled C program and a python script that both setup signal handlers, it looks like they both use the system call 'rt_sigaction'. however it looks like the python program tries to set it up 2x for the same signal- the first time as a null than later with the function? rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigaction(SIGINT, {0x4d7270, [], SA_RESTORER, 0x7f928fce0b40}, {SIG_DFL, [], 0}, 8) = 0. Maybe it only registers the first one?Cabrales
By the way, your method of changing stdio will not work, since you are only changing out the python objects. You are not affecting the underlying file descriptors.Phraseologist
@keith that code works? are you using as init? Init seems to have special signal filtering around it. the changing stdio is opening a /bin/ash console on tty2- thats how im trying to send signals to PID 1, my init scriptCabrales
No, standalone. First get your program working that way, then try it as init.Phraseologist
it was working stand alone, it wasn't until i made it init that is stopped receiving the SIGCHLD signals and wouldn't reap its dead children, thats when i degreased to the tiny test script. :(Cabrales
Oh. Well, the init man page describes how init will react to a SIGHUP, so there doesn't seem to be any special handling there. Are you sure the Python interpreter is process 1?Phraseologist
yeah im sure. from the ash term i can cd /proc/1 and inspect the status file etc. the init man page is referencing the what the SysV style init program (thats written in C) does. could i skip using the signal module and register the handler with the correct fcnlt? or in the libc with ctypes? does that make any sense?Cabrales
the inits stdout is /dev/console by default i believe- its printing out of tty1, the same screen the kernel boot messages print onCabrales
I'll have to try that out myself, then.Phraseologist
@keith Im testing in a KVM vm, I can share it with you if you like- else just give you the python bin and linked libs (python is built against uClibc)Cabrales
A
-1
import signal

from signal import api_client

# Create an instance of the Signal API client

sig = api_client('YOUR_API_KEY')

# Send a message using the Signal API

sig.send_message(recipient='TOKEN', message='Hello world!')
Angara answered 29/8, 2023 at 2:27 Comment(1)
/help/formattingKylynn

© 2022 - 2024 — McMap. All rights reserved.