1 /*
2 * Activity Monitor
3 *
4 * Contains code to run an activity flag and associated timer
5 * A pthread implements a simple state machine with three states,
6 * "idle", "active" and "timing out".
7 *
8 *
9 * This file is part of Shairport Sync.
10 * Copyright (c) Mike Brady 2019
11 * All rights reserved.
12 *
13 * Permission is hereby granted, free of charge, to any person
14 * obtaining a copy of this software and associated documentation
15 * files (the "Software"), to deal in the Software without
16 * restriction, including without limitation the rights to use,
17 * copy, modify, merge, publish, distribute, sublicense, and/or
18 * sell copies of the Software, and to permit persons to whom the
19 * Software is furnished to do so, subject to the following conditions:
20 *
21 * The above copyright notice and this permission notice shall be
22 * included in all copies or substantial portions of the Software.
23 *
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31 * OTHER DEALINGS IN THE SOFTWARE.
32 */
33
34 #include <errno.h>
35 #include <inttypes.h>
36 #include <stdlib.h>
37 #include <sys/types.h>
38
39 #include "config.h"
40
41 #include "activity_monitor.h"
42 #include "common.h"
43 #include "rtsp.h"
44
45 #ifdef CONFIG_DBUS_INTERFACE
46 #include "dbus-service.h"
47 #endif
48
49 enum am_state state;
50 enum ps_state { ps_inactive, ps_active } player_state;
51
52 int activity_monitor_running = 0;
53
54 pthread_t activity_monitor_thread;
55 pthread_mutex_t activity_monitor_mutex;
56 pthread_cond_t activity_monitor_cv;
57
going_active(int block)58 void going_active(int block) {
59 // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" :
60 // "out");
61 if (config.cmd_active_start)
62 command_execute(config.cmd_active_start, "", block);
63 #ifdef CONFIG_METADATA
64 debug(2, "abeg"); // active mode begin
65 send_ssnc_metadata('abeg', NULL, 0, 1); // contains cancellation points
66 #endif
67
68 #ifdef CONFIG_DBUS_INTERFACE
69 if (dbus_service_is_running())
70 shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
71 #endif
72
73 if (config.disable_standby_mode == disable_standby_auto) {
74 #ifdef CONFIG_DBUS_INTERFACE
75 if (dbus_service_is_running())
76 shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE);
77 else
78 config.keep_dac_busy = 1;
79 #else
80 config.keep_dac_busy = 1;
81 #endif
82 }
83 }
84
going_inactive(int block)85 void going_inactive(int block) {
86 // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" :
87 // "out");
88 if (config.cmd_active_stop)
89 command_execute(config.cmd_active_stop, "", block);
90 #ifdef CONFIG_METADATA
91 debug(2, "aend"); // active mode end
92 send_ssnc_metadata('aend', NULL, 0, 1); // contains cancellation points
93 #endif
94
95 #ifdef CONFIG_DBUS_INTERFACE
96 if (dbus_service_is_running())
97 shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
98 #endif
99
100 if (config.disable_standby_mode == disable_standby_auto) {
101 #ifdef CONFIG_DBUS_INTERFACE
102 if (dbus_service_is_running())
103 shairport_sync_set_disable_standby(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE);
104 else
105 config.keep_dac_busy = 0;
106 #else
107 config.keep_dac_busy = 0;
108 #endif
109 }
110 }
111
activity_monitor_signify_activity(int active)112 void activity_monitor_signify_activity(int active) {
113 // this could be pthread_cancelled and they is likely to be cancellation points in the
114 // hooked-on procedures
115 pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1);
116 player_state = active == 0 ? ps_inactive : ps_active;
117 // Now, although we could simply let the state machine in the activity monitor thread
118 // look after eveything, we will change state here in two situations:
119 // 1. If the state machine is am_inactive and the player is ps_active
120 // we will change the state to am_active and execute the going_active() function.
121 // 2. If the state machine is am_active and the player is ps_inactive and
122 // the activity_idle_timeout is 0, then we will change the state to am_inactive and
123 // execute the going_inactive() function.
124 //
125 // The reason for all this is that we might want to perform the attached scripts
126 // and wait for them to complete before continuing. If they were performed in the
127 // activity monitor thread, then we couldn't wait for them to complete.
128
129 // Thus, the only time the thread will execute a going_... function is when a non-zero
130 // timeout actually matures.
131
132 if ((state == am_inactive) && (player_state == ps_active)) {
133 going_active(
134 config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
135 } else if ((state == am_active) && (player_state == ps_inactive) &&
136 (config.active_state_timeout == 0.0)) {
137 going_inactive(
138 config.cmd_blocking); // note -- will be executed with the mutex locked, but that's okay
139 }
140
141 pthread_cond_signal(&activity_monitor_cv);
142 pthread_cleanup_pop(1); // release the mutex
143 }
144
activity_thread_cleanup_handler(void * arg)145 void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) {
146 debug(3, "activity_monitor: thread exit.");
147 pthread_cond_destroy(&activity_monitor_cv);
148 pthread_mutex_destroy(&activity_monitor_mutex);
149 }
150
activity_monitor_thread_code(void * arg)151 void *activity_monitor_thread_code(void *arg) {
152 int rc = pthread_mutex_init(&activity_monitor_mutex, NULL);
153 if (rc)
154 die("activity_monitor: error %d initialising activity_monitor_mutex.", rc);
155
156 // set the flowcontrol condition variable to wait on a monotonic clock
157 #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
158 pthread_condattr_t attr;
159 pthread_condattr_init(&attr);
160 pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
161 rc = pthread_cond_init(&activity_monitor_cv, &attr);
162 pthread_condattr_destroy(&attr);
163
164 #endif
165 #ifdef COMPILE_FOR_OSX
166 rc = pthread_cond_init(&activity_monitor_cv, NULL);
167 #endif
168 if (rc)
169 die("activity_monitor: error %d initialising activity_monitor_cv.");
170 pthread_cleanup_push(activity_thread_cleanup_handler, arg);
171
172 uint64_t sec;
173 uint64_t nsec;
174 struct timespec time_for_wait;
175
176 state = am_inactive;
177 player_state = ps_inactive;
178
179 pthread_mutex_lock(&activity_monitor_mutex);
180 do {
181 switch (state) {
182 case am_inactive:
183 // debug(1,"am_state: am_inactive");
184 while (player_state != ps_active)
185 pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
186 state = am_active;
187 // going_active(); // this is done in activity_monitor_signify_activity
188 break;
189 case am_active:
190 // debug(1,"am_state: am_active");
191 while (player_state != ps_inactive)
192 pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex);
193 if (config.active_state_timeout == 0.0) {
194 state = am_inactive;
195 // going_inactive(); // this is done in activity_monitor_signify_activity
196 } else {
197 state = am_timing_out;
198
199 uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000);
200
201 #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
202 uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
203 sec = time_of_wakeup_ns / 1000000000;
204 nsec = time_of_wakeup_ns % 1000000000;
205 time_for_wait.tv_sec = sec;
206 time_for_wait.tv_nsec = nsec;
207 #endif
208
209 #ifdef COMPILE_FOR_OSX
210 sec = time_to_wait_for_wakeup_ns / 1000000000;
211 nsec = time_to_wait_for_wakeup_ns % 1000000000;
212 time_for_wait.tv_sec = sec;
213 time_for_wait.tv_nsec = nsec;
214 #endif
215 }
216 break;
217 case am_timing_out:
218 // debug(1,"am_state: am_timing_out");
219 rc = 0;
220 while ((player_state != ps_active) && (rc != ETIMEDOUT)) {
221 #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
222 rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex,
223 &time_for_wait); // this is a pthread cancellation point
224 #endif
225 #ifdef COMPILE_FOR_OSX
226 rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex,
227 &time_for_wait);
228 #endif
229 }
230 if (player_state == ps_active)
231 state = am_active; // player has gone active -- do nothing, because it's still active
232 else if (rc == ETIMEDOUT) {
233 state = am_inactive;
234 pthread_mutex_unlock(&activity_monitor_mutex);
235 going_inactive(0); // don't wait for completion -- it makes no sense
236 pthread_mutex_lock(&activity_monitor_mutex);
237 } else {
238 // activity monitor was woken up in the state am_timing_out, but not by a timeout and player
239 // is not in ps_active state
240 debug(1,
241 "activity monitor was woken up in the state am_timing_out, but didn't change state");
242 }
243 break;
244 default:
245 debug(1, "activity monitor in an illegal state!");
246 state = am_inactive;
247 break;
248 }
249 } while (1);
250 pthread_mutex_unlock(&activity_monitor_mutex);
251 pthread_cleanup_pop(0); // should never happen
252 pthread_exit(NULL);
253 }
254
activity_status()255 enum am_state activity_status() { return (state); }
256
activity_monitor_start()257 void activity_monitor_start() {
258 // debug(1,"activity_monitor_start");
259 pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL);
260 activity_monitor_running = 1;
261 }
262
activity_monitor_stop()263 void activity_monitor_stop() {
264 if (activity_monitor_running) {
265 debug(3, "activity_monitor_stop start...");
266 pthread_cancel(activity_monitor_thread);
267 pthread_join(activity_monitor_thread, NULL);
268 debug(2, "activity_monitor_stop complete");
269 }
270 }
271