]>
Commit | Line | Data |
---|---|---|
a8639f6c MB |
1 | /* |
2 | * Activity Monitor | |
2e442853 | 3 | * |
a8639f6c | 4 | * Contains code to run an activity flag and associated timer |
2e442853 | 5 | * A pthread implements a simple state machine with three states, |
a8639f6c | 6 | * "idle", "active" and "timing out". |
2e442853 MB |
7 | * |
8 | * | |
a8639f6c MB |
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 | ||
a8639f6c MB |
34 | #include <errno.h> |
35 | #include <inttypes.h> | |
2e442853 | 36 | #include <stdlib.h> |
a8639f6c MB |
37 | #include <sys/types.h> |
38 | ||
39 | #include "config.h" | |
40 | ||
2e442853 | 41 | #include "activity_monitor.h" |
a8639f6c | 42 | #include "common.h" |
604ca8de | 43 | #include "rtsp.h" |
a8639f6c | 44 | |
58acd161 MB |
45 | #ifdef CONFIG_DBUS_INTERFACE |
46 | #include "dbus-service.h" | |
47 | #endif | |
48 | ||
c86823c3 | 49 | enum am_state state; |
2e442853 | 50 | enum ps_state { ps_inactive, ps_active } player_state; |
a8639f6c | 51 | |
79e22063 MB |
52 | int activity_monitor_running = 0; |
53 | ||
a8639f6c MB |
54 | pthread_t activity_monitor_thread; |
55 | pthread_mutex_t activity_monitor_mutex; | |
56 | pthread_cond_t activity_monitor_cv; | |
57 | ||
2e181f93 | 58 | void going_active(int block) { |
e8b8c6ed MB |
59 | // debug(1, "activity_monitor: state transitioning to \"active\" with%s blocking", block ? "" : |
60 | // "out"); | |
2e181f93 MB |
61 | if (config.cmd_active_start) |
62 | command_execute(config.cmd_active_start, "", block); | |
604ca8de | 63 | #ifdef CONFIG_METADATA |
2e442853 | 64 | debug(2, "abeg"); // active mode begin |
70f60fcd | 65 | send_ssnc_metadata('abeg', NULL, 0, 1); // contains cancellation points |
604ca8de | 66 | #endif |
34351a99 MB |
67 | |
68 | #ifdef CONFIG_DBUS_INTERFACE | |
97a4766e | 69 | if (dbus_service_is_running()) |
34351a99 MB |
70 | shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), TRUE); |
71 | #endif | |
72 | ||
83c0405d | 73 | if (config.disable_standby_mode == disable_standby_auto) { |
6b6d5986 | 74 | config.keep_dac_busy = 1; |
34351a99 | 75 | } |
a8639f6c MB |
76 | } |
77 | ||
2e181f93 | 78 | void going_inactive(int block) { |
e8b8c6ed MB |
79 | // debug(1, "activity_monitor: state transitioning to \"inactive\" with%s blocking", block ? "" : |
80 | // "out"); | |
2e181f93 MB |
81 | if (config.cmd_active_stop) |
82 | command_execute(config.cmd_active_stop, "", block); | |
604ca8de | 83 | #ifdef CONFIG_METADATA |
2e442853 | 84 | debug(2, "aend"); // active mode end |
70f60fcd | 85 | send_ssnc_metadata('aend', NULL, 0, 1); // contains cancellation points |
604ca8de | 86 | #endif |
34351a99 MB |
87 | |
88 | #ifdef CONFIG_DBUS_INTERFACE | |
97a4766e | 89 | if (dbus_service_is_running()) |
34351a99 MB |
90 | shairport_sync_set_active(SHAIRPORT_SYNC(shairportSyncSkeleton), FALSE); |
91 | #endif | |
92 | ||
83c0405d | 93 | if (config.disable_standby_mode == disable_standby_auto) { |
a382ac66 | 94 | config.keep_dac_busy = 0; |
34351a99 | 95 | } |
a8639f6c MB |
96 | } |
97 | ||
2e181f93 | 98 | void activity_monitor_signify_activity(int active) { |
d8bad42b | 99 | // this could be pthread_cancelled and there is likely to be cancellation points in the |
20967812 | 100 | // hooked-on procedures |
4d608216 | 101 | pthread_mutex_lock(&activity_monitor_mutex); |
2e181f93 MB |
102 | player_state = active == 0 ? ps_inactive : ps_active; |
103 | // Now, although we could simply let the state machine in the activity monitor thread | |
4d608216 | 104 | // look after everything, we will change state here in two situations: |
2e181f93 MB |
105 | // 1. If the state machine is am_inactive and the player is ps_active |
106 | // we will change the state to am_active and execute the going_active() function. | |
107 | // 2. If the state machine is am_active and the player is ps_inactive and | |
108 | // the activity_idle_timeout is 0, then we will change the state to am_inactive and | |
109 | // execute the going_inactive() function. | |
110 | // | |
111 | // The reason for all this is that we might want to perform the attached scripts | |
b9cf91b2 | 112 | // and wait for them to complete before continuing. If they were performed in the |
20967812 | 113 | // activity monitor thread, then we couldn't wait for them to complete. |
2e442853 | 114 | |
4d608216 MB |
115 | // So, if the active end procedure is on a timer, it will be executed when the |
116 | // timeout occurs and the "blocking" status is ignored. | |
fd880056 | 117 | |
2e181f93 | 118 | if ((state == am_inactive) && (player_state == ps_active)) { |
4d608216 MB |
119 | state = am_active; |
120 | pthread_mutex_unlock(&activity_monitor_mutex); | |
fd880056 | 121 | going_active(config.cmd_blocking); |
2e442853 | 122 | } else if ((state == am_active) && (player_state == ps_inactive) && |
a9000de6 | 123 | (config.active_state_timeout == 0.0)) { |
4d608216 MB |
124 | state = am_inactive; |
125 | pthread_mutex_unlock(&activity_monitor_mutex); | |
fd880056 | 126 | going_inactive(config.cmd_blocking); |
4d608216 MB |
127 | } else { |
128 | pthread_mutex_unlock(&activity_monitor_mutex); | |
2e181f93 | 129 | } |
4d608216 MB |
130 | // lock the mutex again to send a signal |
131 | pthread_cleanup_debug_mutex_lock(&activity_monitor_mutex, 10000, 1); | |
2e442853 | 132 | pthread_cond_signal(&activity_monitor_cv); |
20967812 | 133 | pthread_cleanup_pop(1); // release the mutex |
a8639f6c MB |
134 | } |
135 | ||
136 | void activity_thread_cleanup_handler(__attribute__((unused)) void *arg) { | |
137 | debug(3, "activity_monitor: thread exit."); | |
138 | pthread_cond_destroy(&activity_monitor_cv); | |
139 | pthread_mutex_destroy(&activity_monitor_mutex); | |
140 | } | |
141 | ||
142 | void *activity_monitor_thread_code(void *arg) { | |
143 | int rc = pthread_mutex_init(&activity_monitor_mutex, NULL); | |
144 | if (rc) | |
145 | die("activity_monitor: error %d initialising activity_monitor_mutex.", rc); | |
146 | ||
a8639f6c | 147 | rc = pthread_cond_init(&activity_monitor_cv, NULL); |
a8639f6c MB |
148 | if (rc) |
149 | die("activity_monitor: error %d initialising activity_monitor_cv."); | |
150 | pthread_cleanup_push(activity_thread_cleanup_handler, arg); | |
2e442853 | 151 | |
a8639f6c MB |
152 | uint64_t sec; |
153 | uint64_t nsec; | |
a8639f6c | 154 | struct timespec time_for_wait; |
2e442853 | 155 | |
a8639f6c MB |
156 | state = am_inactive; |
157 | player_state = ps_inactive; | |
2e442853 | 158 | |
a8639f6c MB |
159 | pthread_mutex_lock(&activity_monitor_mutex); |
160 | do { | |
161 | switch (state) { | |
2e442853 | 162 | case am_inactive: |
c0a3dacf | 163 | debug(2, "am_state: am_inactive"); |
2e442853 MB |
164 | while (player_state != ps_active) |
165 | pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); | |
4d608216 MB |
166 | // state = am_active; this is done by the activity_monitor_signify_activity(1) function |
167 | debug(2, "am_state: am_active"); | |
a8639f6c | 168 | break; |
2e442853 MB |
169 | case am_active: |
170 | // debug(1,"am_state: am_active"); | |
171 | while (player_state != ps_inactive) | |
172 | pthread_cond_wait(&activity_monitor_cv, &activity_monitor_mutex); | |
fd880056 | 173 | |
4d608216 MB |
174 | // if it's not already am_inactive, the it should be beginning to time out... |
175 | if (state != am_inactive) { | |
2e442853 MB |
176 | state = am_timing_out; |
177 | ||
67e9b1b6 | 178 | uint64_t time_to_wait_for_wakeup_ns = (uint64_t)(config.active_state_timeout * 1000000000); |
2e442853 | 179 | |
a8639f6c | 180 | #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD |
56bef8e7 | 181 | uint64_t time_of_wakeup_ns = get_realtime_in_ns() + time_to_wait_for_wakeup_ns; |
67e9b1b6 MB |
182 | sec = time_of_wakeup_ns / 1000000000; |
183 | nsec = time_of_wakeup_ns % 1000000000; | |
2e442853 MB |
184 | time_for_wait.tv_sec = sec; |
185 | time_for_wait.tv_nsec = nsec; | |
a8639f6c | 186 | #endif |
67e9b1b6 | 187 | |
a8639f6c | 188 | #ifdef COMPILE_FOR_OSX |
67e9b1b6 MB |
189 | sec = time_to_wait_for_wakeup_ns / 1000000000; |
190 | nsec = time_to_wait_for_wakeup_ns % 1000000000; | |
2e442853 MB |
191 | time_for_wait.tv_sec = sec; |
192 | time_for_wait.tv_nsec = nsec; | |
a8639f6c | 193 | #endif |
2e442853 | 194 | } |
a8639f6c | 195 | break; |
2e442853 | 196 | case am_timing_out: |
2e442853 MB |
197 | rc = 0; |
198 | while ((player_state != ps_active) && (rc != ETIMEDOUT)) { | |
a8639f6c | 199 | #ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD |
2e442853 MB |
200 | rc = pthread_cond_timedwait(&activity_monitor_cv, &activity_monitor_mutex, |
201 | &time_for_wait); // this is a pthread cancellation point | |
a8639f6c MB |
202 | #endif |
203 | #ifdef COMPILE_FOR_OSX | |
2e442853 MB |
204 | rc = pthread_cond_timedwait_relative_np(&activity_monitor_cv, &activity_monitor_mutex, |
205 | &time_for_wait); | |
a8639f6c | 206 | #endif |
2e442853 MB |
207 | } |
208 | if (player_state == ps_active) | |
209 | state = am_active; // player has gone active -- do nothing, because it's still active | |
210 | else if (rc == ETIMEDOUT) { | |
a8639f6c | 211 | state = am_inactive; |
2e442853 MB |
212 | pthread_mutex_unlock(&activity_monitor_mutex); |
213 | going_inactive(0); // don't wait for completion -- it makes no sense | |
214 | pthread_mutex_lock(&activity_monitor_mutex); | |
215 | } else { | |
216 | // activity monitor was woken up in the state am_timing_out, but not by a timeout and player | |
217 | // is not in ps_active state | |
218 | debug(1, | |
219 | "activity monitor was woken up in the state am_timing_out, but didn't change state"); | |
220 | } | |
221 | break; | |
222 | default: | |
223 | debug(1, "activity monitor in an illegal state!"); | |
224 | state = am_inactive; | |
a8639f6c MB |
225 | break; |
226 | } | |
227 | } while (1); | |
2e442853 | 228 | pthread_mutex_unlock(&activity_monitor_mutex); |
a8639f6c MB |
229 | pthread_cleanup_pop(0); // should never happen |
230 | pthread_exit(NULL); | |
231 | } | |
232 | ||
c8b0be30 | 233 | enum am_state activity_status() { return (state); } |
c86823c3 | 234 | |
a8639f6c | 235 | void activity_monitor_start() { |
604ca8de | 236 | // debug(1,"activity_monitor_start"); |
a8639f6c | 237 | pthread_create(&activity_monitor_thread, NULL, activity_monitor_thread_code, NULL); |
79e22063 | 238 | activity_monitor_running = 1; |
a8639f6c MB |
239 | } |
240 | ||
241 | void activity_monitor_stop() { | |
79e22063 | 242 | if (activity_monitor_running) { |
a9000de6 MB |
243 | debug(2, "activity_monitor_stop begin. state: %d, player_state: %d.", state, player_state); |
244 | if ((state == am_active) || (state == am_timing_out)) { | |
245 | going_inactive(config.cmd_blocking); | |
246 | state = am_inactive; | |
247 | } | |
79e22063 MB |
248 | pthread_cancel(activity_monitor_thread); |
249 | pthread_join(activity_monitor_thread, NULL); | |
405a028f | 250 | debug(2, "activity_monitor_stop complete"); |
79e22063 | 251 | } |
a8639f6c | 252 | } |