util.bgproc.sh (20271B)
1 # -*- mode: sh; mode: sh-bash -*- 2 3 ## @fn ble/util/bgproc#open prefix command [opts] 4 ## Start a session for a background process. The session can be closed by 5 ## calling "ble/util/bgproc#close PREFIX". The background process is usually 6 ## started on the start of the session and terminated on closing the session. 7 ## In addition, if requested, the background process can be stopped and 8 ## started any time in the session. If the background process is stopped, it 9 ## is automatically restarted when it becomes needed. If "timeout=TIMEOUT" 10 ## is specified in OPTS, the background process is automatically stopped 11 ## where there is no access for the time duration specified by TIMEOUT. 12 ## 13 ## @param[in] prefix 14 ## This names the identifier of the bgproc. This is actually used as the 15 ## prefix of the array names used to store the information of the created 16 ## bgproc, so the value needs to be a valid variable name. 17 ## 18 ## When the bgproc is successfully created, the following array elements 19 ## are set, (where PREFIX in the variable name is replaced by the value of 20 ## prefix). 21 ## 22 ## @var PREFIX_bgproc[0] ... fd_response 23 ## @var PREFIX_bgproc[1] ... fd_request 24 ## @var PREFIX_bgproc[2] ... command 25 ## @var PREFIX_bgproc[3] ... opts 26 ## @var PREFIX_bgproc[4] ... bgpid 27 ## @var PREFIX_bgproc_fname[0] ... fname_response 28 ## @var PREFIX_bgproc_fname[1] ... fname_request 29 ## @var PREFIX_bgproc_fname[2] ... fname_run_pid 30 ## 31 ## To send strings to stdin of the background process, one can write to the 32 ## file descriptor ${PREFIX_bgproc[1]}. To read strings coming from stdout 33 ## of the background process, one can read from the file descriptor 34 ## ${PREFIX_bgproc[0]}. 35 ## 36 ## When any of "timeout=TIMEOUT", "deferred", and "restart" are specified 37 ## to OPTS, one should call "ble/util/bgproc#use PREFIX" just before 38 ## directly accesssing the file descriptors ${PREFIX_bgproc[0]} and 39 ## ${PREFIX_bgproc[1]} to ensure that the background process is running. 40 ## Or, one can use a shorthand "ble/util/bgproc#post PREFIX STRING" to 41 ## ensure the background process and write STRING to it. Immediately after 42 ## "ble/util/bgproc#post PREFIX STRING", one do not need to call 43 ## "ble/util/bgproc#use PREFIX" to read from ${PREFIX_bgproc[0]}. 44 ## 45 ## @param[in] command 46 ## The command to execute. 47 ## 48 ## @remarks Use `exec'--The command is started in a subshell. When an 49 ## external command is started (as the last command of the subshell), 50 ## please start the command by `exec'. This will replace the current 51 ## process with the external process. If the external command is not 52 ## started with `exec', the external command is created as a child process 53 ## of the subshell, and the file descriptors are kept by also the subshell. 54 ## This may cause a deadlock in closing the file descriptors because the 55 ## file descriptors in the external process are still alive even after the 56 ## main Bash subshell closes them because the subshell still keeps the file 57 ## descriptor. 58 ## 59 ## @remarks Reserved variables `bgproc' and `bgproc_fname'--If the command 60 ## wants to access the variable names "bgproc" and "bgproc_fname" defined 61 ## outside the command, please save the values in other names of variables 62 ## before calling "ble/util/bgproc#open" and access those alternative 63 ## variables from inside the command. The variable names "bgproc" and 64 ## "bgproc_fname" are hidden by the local variables used by ble/util/bgproc 65 ## itself. 66 ## 67 ## @param[in,opt] opts 68 ## A colon-separated list of options. The following options can be 69 ## specified. 70 ## 71 ## deferred 72 ## When this option is specified, the background process is initially not 73 ## started. It will be started when it is first required. 74 ## 75 ## restart 76 ## When this option is specified, if the background process died 77 ## unexpectedly, the background process will be restarted when it becomes 78 ## necessary. 79 ## 80 ## Note: Even if this option is unspecified, the background process that 81 ## was intensiontally stopped will be always restarted when it becomes 82 ## necessary. This option only affects the case that the background 83 ## process exited or died outside the management of bgproc. 84 ## 85 ## timeout=TIMEOUT 86 ## When this option is specified, the background process is stopped when 87 ## there are no access to the background process for the time duration 88 ## specified by TIMEOUT. The unit of TIMEOUT is millisecond. 89 ## 90 ## owner-close-on-unload 91 ## This option suppresses the automatic call of "ble/util/bgproc#close" 92 ## from the "unload" blehook. This option is useful when another 93 ## "unload" blehook needs to access to this bgproc. When this option is 94 ## specified, another "unload" blehook needs to manually call 95 ## "ble/util/bgproc#close" for this bgproc. If "ble/util/bgproc#close" 96 ## is not called, the background process may be forcibly terminated by 97 ## the final cleaup stage of ble.sh session. 98 ## 99 ## no-close-on-unload 100 ## This option suppresses the automatic call of "ble/util/bgproc#close" 101 ## and any cleanups of the background process, so that the background 102 ## process survives after Bash terminates or ble.sh has been unloaded. 103 ## 104 ## Note: Nevertheless, file descriptors at the side of the parent shell 105 ## will be closed on the termination of the parent shell, which can cause 106 ## SIGPIPE write error or EOF read error in the background process. 107 ## 108 ## kill-timeout=TIMEOUT 109 ## This option specifies the timeout after the attempt of stopping the 110 ## background process in unit of millisecond. The default is 10000 (10 111 ## seconds). If the background process does not terminate within the 112 ## timeout after closing the file descriptors at the side of the parent 113 ## shell, the background process will receive SIGTERM. If it does not 114 ## terminate even after sending SIGTERM, it will then receive SIGKILL 115 ## after additional timeout specified by "kill9-timeout". 116 ## 117 ## kill9-timeout=TIMEOUT 118 ## This option specifies the additional timeout after sending SIGTERM 119 ## after "kill-timeout". The default is 10000 (10 seconds). If the 120 ## background process does not terminate within the timeout after sending 121 ## SIGTERM, the background process will receive SIGKILL. 122 ## 123 ## @exit 0 if the background process is successfully started or "deferred" is 124 ## specified to OPTS. 2 if an invalid prefix value is specified. 3 if the 125 ## system does not support named pipes. 1 if the background process failed 126 ## to be started. 127 ## 128 ## @remarks No FD_CLOEXEC for bgproc file descriptors--Unlike "coproc", the 129 ## file descriptors opened by bgproc are not closed in subshells. This means 130 ## that if there are other background processes holding the file descriptors, 131 ## even when the main Bash process closes the file descriptors by 132 ## `ble/util/bgproc#stop' or `ble/util/bgproc#close', the file descriptors in 133 ## the bgproc process can still alive. If one wants to make it sure that the 134 ## file descriptors are closed in the other subshells, one needs to close the 135 ## file descriptors 136 ## 137 ## 1) by calling eval "exec ${PREFIX_bgproc[0]}>&- ${PREFIX_bgproc[1]}>&-" 138 ## 139 ## 2) Or by calling `ble/fd#finalize' (Note that `ble/fd#finalize' closes all 140 ## the file descriptors opened by ble.sh including the ones used by 141 ## ble/util/msleep).. 142 ## 143 ## 3) Or another way is to use the loadable builtin "fdflags" to set 144 ## FD_CLOEXEC in `ble/util/bgproc/onstart:PREFIX'. bgproc[] 145 ## 146 ## # The loadable builtin needs to be loaded in advance. Please replace 147 ## # the path /usr/lib/bash/fdflags based on your installation. 148 ## enable -f /usr/lib/bash/fdflags fdflags 149 ## 150 ## function ble/util/bgproc/onstart:PREFIX { 151 ## fdflags -s +cloexec "${PREFIX_bgproc[0]}" 152 ## fdflags -s +cloexec "${PREFIX_bgproc[1]}" 153 ## } 154 ## 155 ## @fn ble/util/bgproc/onstart:PREFIX 156 ## When this function is defined, this function is called after the new 157 ## background process is created. 158 ## 159 ## @fn ble/util/bgproc/onstop:PREFIX 160 ## When this function is defined, this function is called before the 161 ## background process is stopped. 162 ## 163 ## The application can send an intruction to terminate the background process 164 ## in this hook (in case that the background process does not automatically 165 ## end on the close of the file descriptors, or the file descriptors can be 166 ## shared with other background subshells). Note that the background process 167 ## will receive SIGTERM if it does not terminate within the timeout specified 168 ## by "kill-timeout=TIMEOUT" and then will receive SIGKILL if it does not 169 ## even terminate within the additional timeout specified by 170 ## "kill9-timeout=TIMEOUT". 171 ## 172 ## @fn ble/util/bgproc/onclose:PREFIX 173 ## When this function is defined, this function is called before the bgproc 174 ## session is closed. 175 ## 176 ## @fn ble/util/bgproc/ontimeout:PREFIX 177 ## When this function is defined, this function is called before the timeout 178 ## specified by "timeout=TIMEOUT" in OPTS. If this function exits with 179 ## non-zero status, the timeout is canceled. 180 ## 181 function ble/util/bgproc#open { 182 if ! ble/string#match "$1" '^[_a-zA-Z][_a-zA-Z0-9]*$'; then 183 ble/util/print "$FUNCNAME: $1: invalid prefix value." >&2 184 return 2 185 fi 186 187 # If there is an existing bgproc on the same prefix, close it first. 188 ble/util/bgproc#close "$1" 189 190 local -a bgproc=() 191 bgproc[0]= 192 bgproc[1]= 193 bgproc[2]=$2 194 bgproc[3]=${3-} 195 196 local -a bgproc_fname=() 197 bgproc_fname[0]=$_ble_base_run/$$.util.bgproc.$1.response.pipe 198 bgproc_fname[1]=$_ble_base_run/$$.util.bgproc.$1.request.pipe 199 bgproc_fname[2]=$_ble_base_run/$$.util.bgproc.$1.pid 200 201 ble/util/save-vars "${1}_" bgproc bgproc_fname 202 203 [[ :${bgproc[3]}: == *:deferred:* ]] || ble/util/bgproc#start "$1"; local ext=$? 204 if ((ext!=0)); then 205 builtin eval -- "${1}_bgproc=() ${1}_bgproc_fname=()" 206 fi 207 return "$ext" 208 } 209 210 ## @fn ble/util/bgproc#alive prefix 211 ## Test if the bgproc session is active. 212 ## 213 ## @param[in] prefix 214 ## The name to identify the bgproc. 215 ## 216 function ble/util/bgproc#opened { 217 local bgpid_ref=${1}_bgproc[0] 218 [[ ${!bgpid_ref+set} ]] || return 2 219 } 220 221 ## @fn ble/util/bgproc/.alive 222 ## @var[in] bgproc 223 function ble/util/bgproc/.alive { 224 [[ ${bgproc[4]-} ]] && kill -0 "${bgproc[4]}" 2>/dev/null 225 } 226 227 ## @fn ble/util/bgproc/.exec 228 ## @var[in] bgproc 229 function ble/util/bgproc/.exec { 230 # Note: We need to specify the redirections for ${bgproc[0]} and ${bgproc[1]} 231 # on "builtin eval" because of a bash-3.0 bug. In Bash 3.0, the redirections 232 # are not properly set up if one uses a function definition of the form 233 # "function fname { } redirections". 234 builtin eval -- "${bgproc[2]}" <&"${bgproc[1]}" >&"${bgproc[0]}" 235 } 236 237 ## @fn ble/util/bgproc/.mkfifo 238 ## @var[in] bgproc_fname 239 function ble/util/bgproc/.mkfifo { 240 local -a pipe_remove=() pipe_create=() 241 local i 242 for i in 0 1; do 243 [[ -p ${bgproc_fname[i]} ]] && continue 244 ble/array#push pipe_create "${bgproc_fname[i]}" 245 if [[ -e ${bgproc_fname[i]} || -h ${bgproc_fname[i]} ]]; then 246 ble/array#push pipe_remove "${bgproc_fname[i]}" 247 fi 248 done 249 ((${#pipe_remove[@]}==0)) || ble/bin/rm -f "${pipe_remove[@]}" 2>/dev/null 250 ((${#pipe_create[@]}==0)) || ble/bin/mkfifo "${pipe_create[@]}" 2>/dev/null 251 } 252 253 ## @fn ble/util/bgproc#start prefix 254 ## Start the background process. This runs the command specified to 255 ## "ble/util/bgproc#open". 256 ## 257 ## @param[in] prefix 258 ## The name to identify the bgproc. 259 ## 260 ## @exit 0 if the background process is successfully started or was already 261 ## running. 2 if the PREFIX does not corresponds to an existing bgproc. 3 262 ## if the system does not support the named pipes. 1 if the background 263 ## process failed to be started. 264 ## 265 function ble/util/bgproc#start { 266 local bgproc bgproc_fname 267 ble/util/restore-vars "${1}_" bgproc bgproc_fname 268 if ((!${#bgproc[@]})); then 269 ble/util/print "$FUNCNAME: $1: not an existing bgproc name." >&2 270 return 2 271 fi 272 273 if ble/util/bgproc/.alive; then 274 # The background process is already running 275 return 0 276 fi 277 [[ ! ${bgproc[0]-} ]] || ble/fd#close 'bgproc[0]' 278 [[ ! ${bgproc[1]-} ]] || ble/fd#close 'bgproc[1]' 279 280 # Note: mkfifo may fail in MSYS-1 281 local _ble_local_ext=0 _ble_local_bgproc0= _ble_local_bgproc1= 282 if ble/util/bgproc/.mkfifo && 283 ble/fd#alloc _ble_local_bgproc0 '<> "${bgproc_fname[0]}"' && 284 ble/fd#alloc _ble_local_bgproc1 '<> "${bgproc_fname[1]}"' 285 then 286 bgproc[0]=$_ble_local_bgproc0 287 bgproc[1]=$_ble_local_bgproc1 288 # Note: We want to assign a new process group to the background process 289 # without affecting the job table of the main shell so use the subshell 290 # `(...)'. The process group is later used to kill the process tree in 291 # stopping the background process. Note that the command substitutions 292 # $(...) do not create a new process group even if we specify `set -m' so 293 # cannot be used for the present purpose. 294 ble/util/assign 'bgproc[4]' '(set -m; ble/util/bgproc/.exec __ble_suppress_joblist__ >/dev/null & bgpid=$!; ble/util/print "$bgpid")' 295 296 if ble/util/bgproc/.alive; then 297 [[ :${bgproc[3]}: == *:no-close-on-unload:* ]] || 298 ble/util/print "-${bgproc[4]}" >| "${bgproc_fname[2]}" 299 [[ :${bgproc[3]}: == *:no-close-on-unload:* || :${bgproc[3]}: == *:owner-close-on-unload:* ]] || 300 blehook unload!="ble/util/bgproc#close $1" 301 ble/util/bgproc#keepalive "$1" 302 else 303 builtin unset -v 'bgproc[4]' 304 _ble_local_ext=1 305 fi 306 else 307 _ble_local_ext=3 308 fi 309 310 if ((_ble_local_ext!=0)); then 311 [[ ! ${bgproc[0]-} ]] || ble/fd#close 'bgproc[0]' 312 [[ ! ${bgproc[1]-} ]] || ble/fd#close 'bgproc[1]' 313 bgproc[0]= 314 bgproc[1]= 315 builtin unset -v 'bgproc[4]' 316 fi 317 318 ble/util/save-vars "${1}_" bgproc bgproc_fname 319 320 if ((_ble_local_ext==0)); then 321 ble/function#try ble/util/bgproc/onstart:"$1" 322 fi 323 return "$_ble_local_ext" 324 } 325 326 function ble/util/bgproc#stop/.kill { 327 local pid=$1 opts=$2 ret 328 329 # kill -- 330 local timeout=10000 331 if ble/opts#extract-last-optarg "$opts" kill-timeout; then 332 timeout=$ret 333 fi 334 ble/util/conditional-sync '' '((1))' 1000 progressive-weight:pid="$pid":no-wait-pid:timeout="$timeout" 335 kill -0 "$pid" || return 0 336 337 # kill -9 338 local timeout=10000 339 if ble/opts#extract-last-optarg "$opts" kill9-timeout; then 340 timeout=$ret 341 fi 342 ble/util/conditional-sync '' '((1))' 1000 progressive-weight:pid="$pid":no-wait-pid:timeout="$timeout":SIGKILL 343 } 344 345 ## @fn ble/util/bgproc#stop prefix 346 ## Stop the background process. 347 ## 348 ## @param[in] prefix 349 ## The name to identify the bgproc. 350 ## 351 function ble/util/bgproc#stop { 352 local prefix=$1 353 ble/util/bgproc#keepalive/.cancel-timeout "$prefix" 354 355 local bgproc bgproc_fname 356 ble/util/restore-vars "${prefix}_" bgproc bgproc_fname 357 if ((!${#bgproc[@]})); then 358 ble/util/print "$FUNCNAME: $prefix: not an existing bgproc name." >&2 359 return 2 360 fi 361 362 [[ ${bgproc[4]-} ]] || return 1 363 364 if ble/is-function ble/util/bgproc/onstop:"$prefix" && ble/util/bgproc/.alive; then 365 ble/util/bgproc/onstop:"$prefix" 366 fi 367 368 ble/fd#close 'bgproc[0]' 369 ble/fd#close 'bgproc[1]' 370 >| "${bgproc_fname[2]}" 371 372 # When the background process is active, kill the process after waiting for 373 # the time specified by kill-timeout. 374 if ble/util/bgproc/.alive; then 375 (ble/util/nohup 'ble/util/bgproc#stop/.kill "-${bgproc[4]}" "${bgproc[3]}"') 376 fi 377 378 builtin eval -- "${prefix}_bgproc[0]=" 379 builtin eval -- "${prefix}_bgproc[1]=" 380 builtin unset -v "${prefix}_bgproc[4]" 381 return 0 382 } 383 384 ## @fn ble/util/bgproc#alive prefix 385 ## Test if the background process is currently running. 386 ## 387 ## @param[in] prefix 388 ## The name to identify the bgproc. 389 ## 390 ## @exit 2 if the prefix does not define a bgproc. 1 if the bgproc 391 ## process is temporarily stopped. 3 if the bgproc process has 392 ## crashed. 0 if the process is running. 393 function ble/util/bgproc#alive { 394 local prefix=$1 bgproc 395 ble/util/restore-vars "${prefix}_" bgproc 396 ((${#bgproc[@]})) || return 2 397 [[ ${bgproc[4]-} ]] || return 1 398 kill -0 "${bgproc[4]}" 2>/dev/null || return 3 399 return 0 400 } 401 402 function ble/util/bgproc#keepalive/.timeout { 403 local prefix=$1 404 405 # Call ble/util/bgproc/ontimeout:PREFIX if any 406 if ble/is-function ble/util/bgproc/ontimeout:"$prefix"; then 407 if ! ble/util/bgproc/ontimeout:"$prefix"; then 408 ble/util/bgproc#keepalive "$prefix" 409 return 0 410 fi 411 fi 412 413 ble/util/bgproc#stop "$prefix" 414 } 415 416 function ble/util/bgproc#keepalive/.cancel-timeout { 417 local prefix=$1 418 ble/function#try ble/util/idle.cancel "ble/util/bgproc#keepalive/.timeout $prefix" 419 } 420 421 ## @fn ble/util/bgproc#keepalive prefix 422 ## Rest the timeout to stop the background process. 423 ## 424 ## @param[in] prefix 425 ## The name to identify the bgproc. 426 ## 427 function ble/util/bgproc#keepalive { 428 local prefix=$1 bgproc 429 ble/util/restore-vars "${prefix}_" bgproc 430 ((${#bgproc[@]})) || return 2 431 ble/util/bgproc/.alive || return 1 432 433 ble/util/bgproc#keepalive/.cancel-timeout "$prefix" 434 local ret 435 ble/opts#extract-last-optarg "${bgproc[3]}" timeout || return 0; local bgproc_timeout=$ret 436 if ((bgproc_timeout>0)); then 437 local timeout_proc="ble/util/bgproc#keepalive/.timeout $1" 438 ble/function#try ble/util/idle.push --sleep="$bgproc_timeout" "$timeout_proc" 439 fi 440 return 0 441 } 442 443 _ble_util_bgproc_onclose_processing= 444 ## @fn ble/util/bgproc#close prefix 445 ## Close the bgproc session. 446 ## 447 ## @param[in] prefix 448 ## The name to identify the bgproc. 449 ## 450 function ble/util/bgproc#close { 451 # If the bgproc does not exist, do nothing. 452 ble/util/bgproc#opened "$1" || return 2 453 454 local prefix=${1} 455 blehook unload-="ble/util/bgproc#close $prefix" 456 ble/util/bgproc#keepalive/.cancel-timeout "$prefix" 457 458 # When the callback function "ble/util/bgproc/onclose:PREFIX" is defined, we 459 # call the function before starting the closing process. However, we skip 460 # this if the present call of "ble/util/bgproc#close" is already from inside 461 # the callback, we skip it to avoid the infinite recursion. 462 if ble/is-function ble/util/bgproc/onclose:"$prefix"; then 463 if [[ :${_ble_util_bgproc_onclose_processing-}: != *:"$prefix":* ]]; then 464 local _ble_util_bgproc_onclose_processing=${_ble_util_bgproc_onclose_processing-}:$prefix 465 ble/util/bgproc/onclose:"$prefix" 466 fi 467 fi 468 469 ble/util/bgproc#stop "$prefix" 470 builtin eval -- "${prefix}_bgproc=() ${prefix}_bgproc_fname=()" 471 } 472 473 ## @fn ble/util/bgproc#use prefix 474 ## Ensure the file descriptors to be ready for uses. When the background 475 ## process is temporarily stopped, this will restart the background process. 476 ## When the background process was terminated unexpectedly and "restart" is 477 ## specified to the bgproc's OPTS, this will also restart the background 478 ## process. 479 ## 480 ## @param[in] prefix 481 ## The name to identify the bgproc. 482 ## 483 ## @exit 0 if the background process is ready. 2 if the specified PREFIX 484 ## does not correspond to an existing bgproc. 3 if the system does not seem 485 ## to support named pipes. 1 if the background process was stopped and 486 ## failed to restart it. 487 ## 488 function ble/util/bgproc#use { 489 local bgproc 490 ble/util/restore-vars "${1}_" bgproc 491 if ((!${#bgproc[@]})); then 492 ble/util/print "$FUNCNAME: $1: not an existing bgproc name." >&2 493 return 2 494 fi 495 496 if [[ ! ${bgproc[4]-} ]]; then 497 # The background process has been stopped intenstionally. We automatically 498 # restart the background process in this case. 499 ble/util/bgproc#start "$1" || return "$?" 500 elif ! kill -0 "${bgproc[4]-}"; then 501 # The background process died unexpectedly 502 if [[ :${bgproc[3]-}: == *:restart:* ]]; then 503 ble/util/bgproc#start "$1" || return "$?" 504 else 505 return 1 506 fi 507 else 508 ble/util/bgproc#keepalive "$1" 509 return 0 510 fi 511 } 512 513 function ble/util/bgproc#post { 514 ble/util/bgproc#use "$1" || return "$?" 515 local fd1_ref=${1}_bgproc[1] 516 ble/util/print "$2" >&"${!fd1_ref}" 517 }