Written by yaochenzhi2024年8月23日
Linux signal信号处理机制与信号掩码
问题现象:
服务配置变更,下发节点通过signal触发本地缓存更新时。日志显示信号正常接收,执行缓存更新入口函数,但无缓存更新成功结日志。本地缓存实际未更新,无异常日志。首次更新失败后,后续均更新失败。
问题分析:
- 分析代码:缓存更新过程,主要流程为配置读取、解密、缓存写入,在缓存写入过程中会使用文件锁。如果存在文件锁未释放,合理解释后续均更新失败(后续无法拿到文件锁,导致失败),合理解释更新失败无异常日志(含首次)。
- 分析进程:查看问题进程/proc/PID/stack 信息,发现flock
- 分析文件:通过查看缓存更新过程中的锁文件的文件句柄打开情况和锁定测试,找出具体未被释放的锁文件。参考命令: lsof lock.file和flock lock.file -c ‘echo ok’。
- 分析原因:通过lsof lock.file已经确定,仅问题进程占用该文件,排除其他进程影响。针对文件锁,考虑是否存在并发影响,查看日志,发现前后2秒钟内,下发两次变更,两次signal,很有可能第一次执行过程中,文件锁未释放,触发第二次执行。
- 构造验证:使用如下代码验证,符合预期。
问题根因:
第一次signal处理过程中,获取到文件锁后,未释放文件锁前,接收到第二次signal,第一次handle代码直接结束,文件锁被当前进程占用后未释放,导致第二次也无法获取文件锁
Linux signal信号处理机制与信号掩码:
信号掩码(Signal Mask):在信号处理中,掩码用于控制进程是否接收某些信号。信号掩码实际上“掩盖”了信号,即在某些情况下阻止信号的传递,直到掩码被更改或解除。
参考:pthread_sigmask() — Examine or change a thread blocked signals format – IBM Documentation
#!/bin/env python3
import os
import time
import fcntl
import signal
import argparse
SIGNAL = signal.SIGUSR1
SIGNAL_BLOCKING = False
# Define signal block
def signal_block(func):
def _inner(*args, **kwargs):
if not SIGNAL_BLOCKING:
print(f"Non signal blocking function handling")
return func(*args, **kwargs)
# Block SIGUSR1 while handling the signal
print(f"Signal blocking function handling")
old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, {SIGNAL})
try:
func(*args, **kwargs)
finally:
# Restore the old signal mask
signal.pthread_sigmask(signal.SIG_SETMASK, old_mask)
return _inner
# Define the signal handler
@signal_block
def signal_handler(signum, frame):
print(f"Signal {signum} received.")
lock_file_hello()
# Set up the signal handling
def setup_signal_handling():
signal.signal(SIGNAL, signal_handler)
def lock_file_hello(file_path='hello.lock'):
with open(file_path, 'a') as file:
try:
# Acquire an exclusive lock
fcntl.flock(file, fcntl.LOCK_EX)
print(f"Locked {file_path} ")
print(f"Work start ")
# Simulate work
time.sleep(10)
print(f"Work done")
finally:
# Release the lock
fcntl.flock(file, fcntl.LOCK_UN)
print(f"Unlocked {file_path}")
# Parse signal blocking option
def parse_option():
global SIGNAL_BLOCKING
parser = argparse.ArgumentParser(description="Example script with blocks of options")
parser.add_argument('--block', action='store_true', help='Enable signal blocking')
args = parser.parse_args()
SIGNAL_BLOCKING = args.block
# Main function to run the program
def main():
parse_option()
setup_signal_handling()
print(f"Waiting for a signal, sending using 'kill -s {SIGNAL} {os.getpid()}'")
while True:
time.sleep(10)
if __name__ == "__main__":
main()
Calendar
一 | 二 | 三 | 四 | 五 | 六 | 日 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
发表回复