-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathWaitForSignals.sh
More file actions
executable file
·238 lines (197 loc) · 8.64 KB
/
Copy pathWaitForSignals.sh
File metadata and controls
executable file
·238 lines (197 loc) · 8.64 KB
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#!/bin/bash
# WaitForSignals.sh version 1.04
#
# This script waits for Unix signals to arrive.
#
# You can choose the action for each signal in the script source code below:
# - Print the received signal's number and name, and then exit.
# - Print the received signal's number and name, and then exit after a delay.
# This is useful for testing whether a process pipeline is quitting abruptly
# upon reception of a signal, or is waiting for all process to terminate gracefully.
# - Print the received signal's number and name, and then let that same signal kill
# this script, with or without a delay.
# This is in fact the recommended way of terminating upon reception of a signal,
# after doing any clean-up work.
# This script assumes that the default signal disposition on start-up is set to "terminate process",
# because Bash cannot only reset the signal dispositions to their original value upon entry.
# - Print the received signal's number and name, and then ignore it.
# - Silently ignore the signal.
# - Do not trap a signal at all (upon reception, the default response will then ensue).
#
# This script is mainly useful during development or troubleshooting of Linux processes.
#
# Copyright (c) 2017-2020 R. Diez - Licensed under the GNU AGPLv3
set -o errexit
set -o nounset
set -o pipefail
declare -r SCRIPT_NAME="${BASH_SOURCE[0]##*/}" # This script's filename only, without any path components.
declare -r -i EXIT_DELAY_IN_SECONDS=1
abort ()
{
echo >&2 && echo "Error in script \"$SCRIPT_NAME\": $*" >&2
exit 1
}
trap_signals ()
{
local FUNCTION_NAME="$1"
shift
local SIGNAL_NUMBER
for SIGNAL_NUMBER ; do
# shellcheck disable=SC2064
trap "$FUNCTION_NAME $SIGNAL_NUMBER" "$SIGNAL_NUMBER"
done
}
trap_function ()
{
local SIGNAL_NUMBER="$1"
# If the designator is a name, "kill -l" will return its number, and viceversa.
# Note that we are using Bash' internal 'kill' command, as the external one
# does not know the real-time signals.
local SIGNAL_NAME
SIGNAL_NAME="$(kill -l "$SIGNAL_NUMBER")"
local ACTION
ACTION="${SIGNAL_ACTIONS[$SIGNAL_NUMBER]}"
# We write a new-line character at the beginning because the parent process may be writing to the console at the moment,
# so we want to try to start writing on a fresh line.
case "$ACTION" in
exit) echo $'\n'"Script $0 with PID $$ exiting upon reception of signal $SIGNAL_NUMBER ($SIGNAL_NAME)."
exit;;
delayed-exit) echo $'\n'"Script $0 with PID $$ has received signal $SIGNAL_NUMBER ($SIGNAL_NAME) and is delaying termination by $EXIT_DELAY_IN_SECONDS second(s)."
sleep "$EXIT_DELAY_IN_SECONDS"
echo $'\n'"Script $0 with PID $$ is terminating after the delay upon receiving signal $SIGNAL_NUMBER ($SIGNAL_NAME)."
exit;;
get-killed) echo $'\n'"Script $0 with PID $$ dying upon reception of signal $SIGNAL_NUMBER ($SIGNAL_NAME)."
trap - "$SIGNAL_NUMBER"
kill -n "$SIGNAL_NUMBER" "$$";;
delayed-get-killed) echo $'\n'"Script $0 with PID $$ has received signal $SIGNAL_NUMBER ($SIGNAL_NAME) and is delaying its death by $EXIT_DELAY_IN_SECONDS second(s)."
sleep "$EXIT_DELAY_IN_SECONDS"
echo $'\n'"Script $0 with PID $$ is dying after the delay upon receiving signal $SIGNAL_NUMBER ($SIGNAL_NAME)."
trap - "$SIGNAL_NUMBER"
kill -n "$SIGNAL_NUMBER" "$$";;
ignore) echo $'\n'"Script $0 with PID $$ is ignoring signal $SIGNAL_NUMBER ($SIGNAL_NAME).";;
silently-ignore) : ;;
*) abort $'\n'"Internal error: Process with PID $$ has received signal $SIGNAL_NUMBER ($SIGNAL_NAME), but the configured signal action \"$ACTION\" for this signal is invalid.";;
esac
}
# ---------- Entry point ----------
if (( $# != 0 )); then
abort "This script takes no command-line arguments."
fi
# Numbers 1 to 31 are standard signals. Numbers 32 to 64 are POSIX real-time signals.
# However, glibc reserves signals 32 and 33, so they are usually not available to the user on Linux.
# I could not find a way to retrieve the maximum valid signal number. Command "getconf RTSIG_MAX"
# gets pretty close though.
declare -A SIGNAL_ACTIONS=() # Associative array.
# Possible actions are:
# - exit
# - delayed-exit
# - delayed-get-killed
# - get-killed
# - ignore
# - silently-ignore
#
# If a signal is not to be trapped at all (let the default behaviour ensue),
# do not place it in the associative array below (comment the corresponding line out).
# For example, signal SIGWINCH (28) tends to trigger when you resize your terminal window,
# and is therefore a good candidate to leave out, because its default response is
# normally to ignore it. However, this script ignores it (but prints a message), so that
# you get to see it.
#
# Some signals, like 9 (SIGKILL), cannot actually be trapped. Attempting to trap them
# will have no effect.
DEFAULT_ACTION="exit"
SIGNAL_ACTIONS[1]="$DEFAULT_ACTION" # SIGHUP
SIGNAL_ACTIONS[2]="$DEFAULT_ACTION" # SIGINT
SIGNAL_ACTIONS[3]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[4]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[5]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[6]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[7]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[8]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[9]="$DEFAULT_ACTION" # SIGKILL, cannot actually be trapped.
SIGNAL_ACTIONS[10]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[11]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[12]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[13]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[14]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[15]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[16]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[17]="silently-ignore" # SIGCHLD, we need to ignore it because otherwise, when child process 'sleep' terminates, this scripts exits.
SIGNAL_ACTIONS[18]="$DEFAULT_ACTION" # SIGCONT, counterpart from SIGSTOP, can be trapped.
SIGNAL_ACTIONS[19]="$DEFAULT_ACTION" # SIGSTOP, cannot actually be trapped.
SIGNAL_ACTIONS[20]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[21]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[22]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[23]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[24]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[25]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[26]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[27]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[28]="ignore" # SIGWINCH, a good candidate to ignore (or to not trap at all), see above for more information.
SIGNAL_ACTIONS[29]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[30]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[31]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[32]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[33]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[34]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[35]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[36]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[37]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[38]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[39]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[40]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[41]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[42]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[43]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[44]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[45]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[46]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[47]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[48]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[49]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[50]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[51]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[52]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[53]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[54]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[55]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[56]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[57]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[58]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[59]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[60]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[61]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[62]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[63]="$DEFAULT_ACTION"
SIGNAL_ACTIONS[64]="$DEFAULT_ACTION"
ELEM_COUNT="${#SIGNAL_ACTIONS[@]}"
if false; then
echo "Trapped signal count: $ELEM_COUNT"
fi
if (( ELEM_COUNT < 1 )); then
abort "There are no signals to trap."
fi
trap_signals trap_function "${!SIGNAL_ACTIONS[@]}"
echo "Script $0 with PID $$ is waiting for signals."
if false; then
# Send ourselves a signal, useful for testing this script.
TEST_SIGNAL_NUMBER="$(kill -l SIGINT)"
kill -n "$TEST_SIGNAL_NUMBER" "$$"
fi
# Forever wait.
#
# We cannot sleep for a long time at once, because 'sleep' is usually an external command, and,
# while we are waiting for the 'sleep' child process to finish, we will not realise that
# a signal has arrived in the meantime.
#
# Note that Bash can load external commands, and its source code comes with a 'sleep' example
# to load that way, but building it is way too much hassle.
#
# The work-around implemented here is to sleep for short amounts of time, which does
# waste a little CPU time.
#
# Alternatively, you can probably create a pipe with 'mkfifo' and read from it,
# which should not waste any CPU time at all.
while true; do
sleep 0.1s
done