From 277d401d24438c563deef6aa870987c93ef58961 Mon Sep 17 00:00:00 2001 From: Mike Brady Date: Tue, 4 Dec 2018 17:25:15 +0000 Subject: [PATCH] Ensure watchdog always starts with the starting time of the thread. Add watchdog bark count. On the third bark, execute an unfixable error script if present. --- audio_alsa.c | 23 +++++++++-------------- common.c | 32 ++++++++++++++++++++++++++++++++ common.h | 3 ++- player.h | 5 +++-- rtsp.c | 32 +++++++++++++++++++++++--------- scripts/shairport-sync.conf | 1 + shairport.c | 5 +++++ 7 files changed, 75 insertions(+), 26 deletions(-) diff --git a/audio_alsa.c b/audio_alsa.c index 73f324a5..a93ec1ae 100644 --- a/audio_alsa.c +++ b/audio_alsa.c @@ -136,8 +136,7 @@ static void help(void) { void set_alsa_out_dev(char *dev) { alsa_out_dev = dev; } - -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled int open_mixer() { int response = 0; if (hardware_mixer) { @@ -181,7 +180,7 @@ int open_mixer() { return response; } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled void close_mixer() { if (alsa_mix_handle) { snd_mixer_close(alsa_mix_handle); @@ -189,7 +188,7 @@ void close_mixer() { } } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double vol) { if (snd_mixer_selem_set_playback_dB_all(mix_elem, vol, 0) != 0) { debug(1, "Can't set playback volume accurately to %f dB.", vol); @@ -200,7 +199,7 @@ void do_snd_mixer_selem_set_playback_dB_all(snd_mixer_elem_t *mix_elem, double v } static int init(int argc, char **argv) { - // for debugging + // for debugging snd_output_stdio_attach(&output, stdout, 0); // debug(2,"audio_alsa init called."); @@ -506,8 +505,7 @@ static int init(int argc, char **argv) { } else { debug(1, "alsa: no hardware mixer selected."); } - - usleep(100000); // see if it makes a difference + alsa_mix_handle = NULL; return response; } @@ -517,7 +515,7 @@ static void deinit(void) { stop(); } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled int actual_open_alsa_device(void) { // the alsa mutex is already acquired when this is called const snd_pcm_uframes_t minimal_buffer_headroom = @@ -884,11 +882,10 @@ int actual_open_alsa_device(void) { break; } } - usleep(100000); // see if it makes a difference return 0; } -int open_alsa_device(void) { +int open_alsa_device(void) { int result; int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable @@ -911,10 +908,9 @@ static void start(int i_sample_rate, int i_sample_format) { frame_index = 0; measurement_data_is_valid = 0; - usleep(100000); // see if it makes a difference } -//assuming pthread cancellation is disabled +// assuming pthread cancellation is disabled int my_snd_pcm_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { int ret; snd_pcm_status_t *alsa_snd_pcm_status; @@ -1165,7 +1161,6 @@ static void flush(void) { measurement_data_is_valid = 0; alsa_handle = NULL; } - usleep(100000); // see if it makes a difference debug_mutex_unlock(&alsa_mutex, 3); pthread_cleanup_pop(0); // release the mutex pthread_setcancelstate(oldState, NULL); @@ -1303,6 +1298,6 @@ void do_mute(int mute_state_requested) { close_mixer(); } } - mute_request_pending = 0; + mute_request_pending = 0; pthread_setcancelstate(oldState, NULL); } diff --git a/common.c b/common.c index b2c920ad..264ef499 100644 --- a/common.c +++ b/common.c @@ -739,6 +739,38 @@ void command_start(void) { } } } +void command_execute(const char *command) { + // this has a cancellation point if waiting is enabled + if (command) { + /*Spawn a child to run the program.*/ + pid_t pid = fork(); + if (pid == 0) { /* child process */ + int argC; + char **argV; + if (poptParseArgvString(command, &argC, (const char ***)&argV) != + 0) // note that argV should be free()'d after use, but we expect this fork to exit + // eventually. + debug(1, "Can't decipher command arguments in \"%s\".", command); + else { + // debug(1,"Executing command %s",command); + execv(argV[0], argV); + warn("Execution of command \"%s\" failed to start", command); + debug(1, "Error executing command \"%s\".", command); + exit(127); /* only if execv fails */ + } + } else { + if (config.cmd_blocking) { /* pid!=0 means parent process and if blocking is true, wait for + process to finish */ + pid_t rc = waitpid(pid, 0, 0); /* wait for child to exit */ + if (rc != pid) { + warn("Execution of command \"%s\" returned an error.", command); + debug(1, "Command \"%s\" finished with error %d", command, errno); + } + } + // debug(1,"Continue after on-unfixable command"); + } + } +} void command_stop(void) { // this has a cancellation point if waiting is enabled diff --git a/common.h b/common.h index ece21b1d..a9f4ab7d 100644 --- a/common.h +++ b/common.h @@ -148,7 +148,7 @@ typedef struct { int debugger_show_relative_time; // in the debug message, display the time since the last one int statistics_requested, use_negotiated_latencies; enum playback_mode_type playback_mode; - char *cmd_start, *cmd_stop, *cmd_set_volume; + char *cmd_start, *cmd_stop, *cmd_set_volume, *cmd_unfixable; int cmd_blocking, cmd_start_returns_output; double tolerance; // allow this much drift before attempting to correct it enum stuffing_type packet_stuffing; @@ -296,6 +296,7 @@ int config_set_lookup_bool(config_t *cfg, char *where, int *dst); void command_start(void); void command_stop(void); +void command_execute(const char *command); void command_set_volume(double volume); int mkpath(const char *path, mode_t mode); diff --git a/player.h b/player.h index 7fea41fc..9346ef2b 100644 --- a/player.h +++ b/player.h @@ -84,11 +84,12 @@ typedef struct { SOCKADDR remote, local; volatile int stop; volatile int running; + volatile uint64_t watchdog_bark_time; + volatile int watchdog_barks; // number of times the watchdog has timed out and done something + time_t playstart; pthread_t thread, timer_requester, rtp_audio_thread, rtp_control_thread, rtp_timing_thread, player_watchdog_thread; - volatile int64_t watchdog_bark_time; - // pthread_t *ptp; // buffers to delete on exit signed short *tbuf; diff --git a/rtsp.c b/rtsp.c index 8f0ec8c7..158b3263 100644 --- a/rtsp.c +++ b/rtsp.c @@ -283,11 +283,25 @@ void *player_watchdog_thread_code(void *arg) { uint64_t ct = config.timeout; // go from int to 64-bit int if (time_since_last_bark >= ct) { - debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " - "of the heart\".", - conn->connection_number); - conn->stop = 1; - pthread_cancel(conn->thread); + conn->watchdog_barks++; + if (conn->watchdog_barks == 1) { + debug(1, "Connection %d: As Yeats almost said, \"Too long a silence / can make a stone " + "of the heart\".", + conn->connection_number); + conn->stop = 1; + pthread_cancel(conn->thread); + } else if (conn->watchdog_barks == 3) { + if (config.cmd_unfixable) { + warn("Connection %d: An unfixable error has been detected. Executing the " + "\"run_this_if_an_unfixable_error_is_detected\" command.", + conn->connection_number); + command_execute(config.cmd_unfixable); + } else { + warn("Connection %d: An unfixable error has been detected. \"No " + "run_this_if_an_unfixable_error_is_detected\" command provided -- nothing done.", + conn->connection_number); + } + } } } } @@ -2144,7 +2158,8 @@ void msg_cleanup_function(void *arg) { static void *rtsp_conversation_thread_func(void *pconn) { rtsp_conn_info *conn = pconn; - // create the watchdog mutex and start the watchdog thread; + // create the watchdog mutex, initialise the watchdog time and start the watchdog thread; + conn->watchdog_bark_time = get_absolute_time_in_fp(); pthread_mutex_init(&conn->watchdog_mutex, NULL); pthread_create(&conn->player_watchdog_thread, NULL, &player_watchdog_thread_code, (void *)conn); @@ -2393,12 +2408,11 @@ void rtsp_listen_loop(void) { tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - + tv.tv_sec = 30; // 30 seconds read timeout tv.tv_usec = 0; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1) debug(1, "Error %d setting send timeout for rtsp writeback.", errno); - #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. diff --git a/scripts/shairport-sync.conf b/scripts/shairport-sync.conf index 55ac4cbf..97431d79 100644 --- a/scripts/shairport-sync.conf +++ b/scripts/shairport-sync.conf @@ -53,6 +53,7 @@ sessioncontrol = { // run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line // run_this_after_play_ends = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line +// run_this_if_an_unfixable_error_is_detected = "/full/path/to/application and args"; // if a problem occurs that can't be cleared. It might restart the whole device. // wait_for_completion = "no"; // set to "yes" to get Shairport Sync to wait until the "run_this..." applications have terminated before continuing // allow_session_interruption = "no"; // set to "yes" to allow another device to interrupt Shairport Sync while it's playing from an existing audio source // session_timeout = 120; // wait for this number of seconds after a source disappears before terminating the session and becoming available again. diff --git a/shairport.c b/shairport.c index 11f085b1..65c0bd68 100644 --- a/shairport.c +++ b/shairport.c @@ -721,6 +721,11 @@ int parse_options(int argc, char **argv) { config.cmd_stop = (char *)str; } + if (config_lookup_string(config.cfg, + "sessioncontrol.run_this_if_an_unfixable_error_is_detected", &str)) { + config.cmd_unfixable = (char *)str; + } + if (config_lookup_string(config.cfg, "sessioncontrol.wait_for_completion", &str)) { if (strcasecmp(str, "no") == 0) config.cmd_blocking = 0;