From d5c02f8459900d2815c9569b6077e478a1d6ad20 Mon Sep 17 00:00:00 2001 From: Narvin Singh Date: Fri, 1 Jan 2021 13:17:08 -0500 Subject: [PATCH] Feat: Use named pipe instead of signals and sleep This is a breaking change and will lead to a new major version. --- README.md | 20 ++++++++--------- avdd | 64 +++++++++++++++++++++++++------------------------------ avds | 53 ++++++++++++++++++++++----------------------- 3 files changed, 65 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 653a915..c5d5897 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,19 @@ The Daemon ---------- The daemon creates a modular status bar by updating the X root window name -when it receives the USR1 signal. It takes an ordered list of modules as a -parameter, and calls a function in each module to compute a section of the -status bar. Each section is cached and only recomputed upon request. These -requests are made by creating empty files with the same names as the modules -in the `/tmp/avdd` directory, then sending the daemon a USR1 signal. +when it receives a request on its named pipe. It takes an ordered list of +modules as a parameter, and calls a function in each module to compute a +section of the status bar. Each section is cached and only recomputed upon +request. These requests are made by writing to a named pipe that the daemon +creates, `/tmp/avdd-fifo`. The Scheduler ------------- -The scheduler creates request files in `/tmp/avdd`, then sends the USR1 -signal to the daemon. It can send one signal to update multiple status bar -sections by creating a request file for each section to update, and can send -the signal immediately, after some delay, or repeatedly at some interval. +The scheduler creates requests by writing a module name to the named pipe for +each module it wants the daemon to run to update that section of the status +bar. It can send requests immediately, after some delay, or repeatedly at +some interval. Installation ------------ @@ -38,7 +38,7 @@ Usage The following examples can be executed manually or by putting them in, e.g., your `.xinitrc` file. Note the `&` after long-running commands to make them run in the background. If the directory that you cloned the appliction into -is not in your path, be sure to include the path when calling `avdd` or `avds`. +is not in your path, be sure to specify the path when calling `avdd` or `avds`. Start the daemon to create a status bar with the default sections, prefix, separators, and suffix. diff --git a/avdd b/avdd index 702d95f..6cf0853 100755 --- a/avdd +++ b/avdd @@ -44,8 +44,7 @@ DEFAULT_SEP_L='| ' DEFAULT_SEP_R=' ' DEFAULT_SUF=' ' MOD_DIR="$(dirname "$0")"/mod -ACTION_DIR=/tmp/avdd -ACTION_DIR_LEN=${#ACTION_DIR} +FIFO=/tmp/avdd-fifo mod_list="${1-${DEFAULT_MOD_LIST}}" pre="${2-${DEFAULT_PRE}}" @@ -96,43 +95,38 @@ draw_status() { # Draw the initial status draw_status -# For each file in the action directory, remove the file, and if a module -# for the action is cached, call the module function and update the cache. If -# any cache entries were updated, redraw the status. -process_signal () { - local -a action_paths - local action_path mod is_changed - readarray -d '' action_paths< \ - <(find "${ACTION_DIR}" -maxdepth 1 -type f -exec rm -f {} + -print0) - for action_path in "${action_paths[@]}"; do - mod="${action_path:$((ACTION_DIR_LEN + 1))}" - if [[ -v stat_cache[${mod}] ]]; then - stat_cache["${mod}"]="$(eval "$(mod_to_fn "${mod}")")" - is_changed=1 +# If the module value is in the cache, indicating that the module controls +# part of the status bar, execute the module function and redraw the status +# bar if that part of the status bar has changed +process_cmd () { + local -r mod="$1" + if [[ -v stat_cache[${mod}] ]]; then + local -r new_val="$(eval "$(mod_to_fn "${mod}")")" + if [[ "${new_val}" != stat_cache["${mod}"] ]]; then + stat_cache["${mod}"]="${new_val}" + draw_status fi - done - if [[ -v is_changed ]]; then draw_status; fi + fi } -# Begin trapping signals -mkdir -p "${ACTION_DIR}" -trap process_signal SIGUSR1 +# Setup the named pipe to receive commands +if [[ ! -p "${FIFO}" ]]; then mkfifo "${FIFO}"; fi +trap "rm -f ${FIFO}" EXIT -# Wait for signals efficiently. In a loop begin a long-running sleep command -# in the background, then wait on it. If we trap a signal before the wait is -# over and sleep is still running, trap will call process_signal, then code -# execution will resume at the line after the wait statement. So on that line -# we kill the (probably) still running sleep command so they don't pile up, -# and loop to sleep and wait for the next signal. If we don't trap a signal -# during the long running sleep, then the wait ends, we try to kill the -# sleep command that has already exited, so it doesn't matter, and loop to -# sleep and wait again. Note that we don't make the sleep too long because -# if the daemon is killed, the sleep will become an orphaned process until -# the sleep period elapses. +# Each time the pipe is emptied out, the inner while loop will finish, so +# wrap it in an infinte loop to keep blocking until there is data on the pipe while :; do - sleep 30m & - sleep_pid="$!" - wait "${sleep_pid}" - kill "${sleep_pid}" 2>/dev/null + while read -r cmd; do + case "${cmd}" in + res_quit) + exit 0 + ;; + res_*) + ;; + *) + process_cmd "${cmd}" + ;; + esac + done < "${FIFO}" done diff --git a/avds b/avds index 3ec128d..0a914e5 100755 --- a/avds +++ b/avds @@ -1,43 +1,44 @@ #!/bin/bash USAGE=" -USAGE: avds [] [] +USAGE: avds [] [] - action A comma or space separated list of actions to associate with the - update signal. + mod_list + A comma or space separated list of modules to request that + the daemon execute. when The integer number of milliseconds to wait before sending the - update signal, or one of the following values: - - m to send the update signal at the top of the next minute - - h to send the update signal at the top of the next hour - - d to send the update signal at the top of the next day - If not present, the update signal will be sent immediately. + request, or one of the following values: + - m to send the update request at the top of the next minute + - h to send the update request at the top of the next hour + - d to send the update request at the top of the next day + If not present, the request will be sent immediately. - repeat If present, the update signal will be sent repeatedly according + repeat If present, the request will be sent repeatedly according to . EXAMPLES: - If the daemon interprets the actions 'vol' and 'bl' to mean update the - volume and backlight statuses, respectively, send a signal to - immediately update both of those statuses. + If there are volume and backlight modules named 'vol' and 'bl' that + update the volume and backlight statuses, send a requst to update + both of those statuses immediately. avds 'vol,bl' - If the daemon interprets the actions 'cpu' and 'mem' to mean update the - cpu and memory usage statuses, respectively, send a signal to update - both of those statuses every 5 seconds. + If there are cpu and memory usage modules named 'cpu' and 'mem' + that update the cpu and memory usage statuses, send a requst to + update both of those statuses every 5 seconds. avds 'cpu,mem' 5000 1 - If the daemon interprets the actions 'bat' and 'dt' to mean update the - battery and date/time statuses, respectively, send a signal to update - both of those statuses at the top of every minute. + If there are battery and date/time modules named 'bat' and 'dt' + that update the battery and date/time statuses, send a requst to + update both of those statuses at the top of every minute. avds 'bat,dt' m true " DAEMON=avdd -ACTION_DIR=/tmp/"${DAEMON}" +FIFO=/tmp/"${DAEMON}"-fifo # Convert integer milliseconds to floating point seconds ms_to_s () { @@ -50,7 +51,7 @@ if [[ "$#" -lt 1 || "$#" -gt 3 ]]; then exit 128 fi -IFS=', ' read -r -a actions <<< "$1" +IFS=', ' read -r -a mods <<< "$1" when="${2:-0}" repeat="$3" @@ -59,12 +60,12 @@ if [[ ! "${when}" =~ ^[0-9]+|[mhd]$ ]]; then exit 128 fi -# Send the signal if this is the first run or if repeat is on +# Write to the pipe if this is the first run or if repeat is on first_run=1 while [[ "${first_run}" -eq 1 || -n "${repeat}" ]]; do first_run=0 - # Sleep until it's time to send the signal + # Sleep until it's time to write to the pipe if [[ "${when}" != '0' ]]; then if [[ "${when}" =~ ^[0-9]+$ ]]; then sleep "$(ms_to_s "${when}")" @@ -87,13 +88,11 @@ while [[ "${first_run}" -eq 1 || -n "${repeat}" ]]; do fi fi - # Create the signal data and send the signal to the daemon - daemon_pid="$(pgrep --newest --exact "${DAEMON}")" - if [[ -z "${daemon_pid}" ]]; then + # Write each command to the pipe + if [[ ! -p "${FIFO}" ]]; then printf 'The daemon %s is not running\n' "${DAEMON}" 1>&2 exit 1 fi - for action in "${actions[@]}"; do touch "${ACTION_DIR}/${action}"; done - kill -USR1 "${daemon_pid}" + for mod in "${mods[@]}"; do printf '%s\n' "${mod}" >> "${FIFO}"; done done