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