blob: eb1948718b4e2e2a60709a3f41583dc6cc0e2a93 [file] [log] [blame]
Brian Silverman26e4e522015-12-17 01:56:40 -05001/*----------------------------------------------------------------------------*/
Brian Silverman1a675112016-02-20 20:42:49 -05002/* Copyright (c) FIRST 2008-2016. All Rights Reserved. */
Brian Silverman26e4e522015-12-17 01:56:40 -05003/* Open Source Software - may be modified and shared by FRC teams. The code */
Brian Silverman1a675112016-02-20 20:42:49 -05004/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
Brian Silverman26e4e522015-12-17 01:56:40 -05006/*----------------------------------------------------------------------------*/
7
8#include "Notifier.h"
9#include "Timer.h"
10#include "Utility.h"
11#include "WPIErrors.h"
12
Brian Silverman1a675112016-02-20 20:42:49 -050013std::list<Notifier*> Notifier::timerQueue;
Brian Silverman26e4e522015-12-17 01:56:40 -050014priority_recursive_mutex Notifier::queueMutex;
15std::atomic<int> Notifier::refcount{0};
16std::thread Notifier::m_task;
17std::atomic<bool> Notifier::m_stopped(false);
18
19/**
20 * Create a Notifier for timer event notification.
21 * @param handler The handler is called at the notification time which is set
22 * using StartSingle or StartPeriodic.
23 */
Brian Silverman1a675112016-02-20 20:42:49 -050024Notifier::Notifier(TimerEventHandler handler)
Brian Silverman26e4e522015-12-17 01:56:40 -050025{
26 if (handler == nullptr)
27 wpi_setWPIErrorWithContext(NullParameter, "handler must not be nullptr");
28 m_handler = handler;
Brian Silverman26e4e522015-12-17 01:56:40 -050029 m_periodic = false;
30 m_expirationTime = 0;
31 m_period = 0;
Brian Silverman26e4e522015-12-17 01:56:40 -050032 m_queued = false;
33 {
34 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
35 // do the first time intialization of static variables
36 if (refcount.fetch_add(1) == 0) {
37 m_task = std::thread(Run);
38 }
39 }
40}
41
42/**
43 * Free the resources for a timer event.
44 * All resources will be freed and the timer event will be removed from the
45 * queue if necessary.
46 */
47Notifier::~Notifier()
48{
49 {
50 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
51 DeleteFromQueue();
52
53 // Delete the static variables when the last one is going away
54 if (refcount.fetch_sub(1) == 1)
55 {
56 m_stopped = true;
57 m_task.join();
58 }
59 }
60
61 // Acquire the semaphore; this makes certain that the handler is
62 // not being executed by the interrupt manager.
63 std::lock_guard<priority_mutex> lock(m_handlerMutex);
64}
65
66/**
67 * Update the alarm hardware to reflect the current first element in the queue.
68 * Compute the time the next alarm should occur based on the current time and the
69 * period for the first element in the timer queue.
70 * WARNING: this method does not do synchronization! It must be called from somewhere
71 * that is taking care of synchronizing access to the queue.
72 */
73void Notifier::UpdateAlarm()
74{
75}
76
77/**
78 * ProcessQueue is called whenever there is a timer interrupt.
79 * We need to wake up and process the current top item in the timer queue as long
80 * as its scheduled time is after the current time. Then the item is removed or
81 * rescheduled (repetitive events) in the queue.
82 */
83void Notifier::ProcessQueue(uint32_t mask, void *params)
84{
85 Notifier *current;
86 while (true) // keep processing past events until no more
87 {
88 {
89 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
90 double currentTime = GetClock();
Brian Silverman1a675112016-02-20 20:42:49 -050091
92 if (timerQueue.empty())
93 {
94 break;
95 }
96 current = timerQueue.front();
97 if (current->m_expirationTime > currentTime)
Brian Silverman26e4e522015-12-17 01:56:40 -050098 {
99 break; // no more timer events to process
100 }
Brian Silverman1a675112016-02-20 20:42:49 -0500101 // remove next entry before processing it
102 timerQueue.pop_front();
103
104 current->m_queued = false;
Brian Silverman26e4e522015-12-17 01:56:40 -0500105 if (current->m_periodic)
106 {
107 // if periodic, requeue the event
108 // compute when to put into queue
109 current->InsertInQueue(true);
110 }
111 else
112 {
113 // not periodic; removed from queue
114 current->m_queued = false;
115 }
116 // Take handler mutex while holding queue semaphore to make sure
117 // the handler will execute to completion in case we are being deleted.
118 current->m_handlerMutex.lock();
119 }
120
Brian Silverman1a675112016-02-20 20:42:49 -0500121 current->m_handler(); // call the event handler
Brian Silverman26e4e522015-12-17 01:56:40 -0500122 current->m_handlerMutex.unlock();
123 }
124 // reschedule the first item in the queue
125 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
126 UpdateAlarm();
127}
128
129/**
130 * Insert this Notifier into the timer queue in right place.
131 * WARNING: this method does not do synchronization! It must be called from somewhere
132 * that is taking care of synchronizing access to the queue.
133 * @param reschedule If false, the scheduled alarm is based on the curent time and UpdateAlarm
134 * method is called which will enable the alarm if necessary.
135 * If true, update the time by adding the period (no drift) when rescheduled periodic from ProcessQueue.
136 * This ensures that the public methods only update the queue after finishing inserting.
137 */
138void Notifier::InsertInQueue(bool reschedule)
139{
140 if (reschedule)
141 {
142 m_expirationTime += m_period;
143 }
144 else
145 {
146 m_expirationTime = GetClock() + m_period;
147 }
Brian Silverman1a675112016-02-20 20:42:49 -0500148
149 // Attempt to insert new entry into queue
150 for (auto i = timerQueue.begin(); i != timerQueue.end(); i++)
Brian Silverman26e4e522015-12-17 01:56:40 -0500151 {
Brian Silverman1a675112016-02-20 20:42:49 -0500152 if ((*i)->m_expirationTime > m_expirationTime)
153 {
154 timerQueue.insert(i, this);
155 m_queued = true;
156 }
157 }
158
159 /* If the new entry wasn't queued, either the queue was empty or the first
160 * element was greater than the new entry.
161 */
162 if (!m_queued)
163 {
164 timerQueue.push_front(this);
165
Brian Silverman26e4e522015-12-17 01:56:40 -0500166 if (!reschedule)
167 {
Brian Silverman1a675112016-02-20 20:42:49 -0500168 /* Since the first element changed, update alarm, unless we already
169 * plan to
170 */
Brian Silverman26e4e522015-12-17 01:56:40 -0500171 UpdateAlarm();
172 }
Brian Silverman1a675112016-02-20 20:42:49 -0500173
174 m_queued = true;
Brian Silverman26e4e522015-12-17 01:56:40 -0500175 }
Brian Silverman26e4e522015-12-17 01:56:40 -0500176}
177
178/**
179 * Delete this Notifier from the timer queue.
180 * WARNING: this method does not do synchronization! It must be called from somewhere
181 * that is taking care of synchronizing access to the queue.
182 * Remove this Notifier from the timer queue and adjust the next interrupt time to reflect
183 * the current top of the queue.
184 */
185void Notifier::DeleteFromQueue()
186{
187 if (m_queued)
188 {
189 m_queued = false;
Brian Silverman1a675112016-02-20 20:42:49 -0500190 wpi_assert(!timerQueue.empty());
191 if (timerQueue.front() == this)
Brian Silverman26e4e522015-12-17 01:56:40 -0500192 {
193 // remove the first item in the list - update the alarm
Brian Silverman1a675112016-02-20 20:42:49 -0500194 timerQueue.pop_front();
Brian Silverman26e4e522015-12-17 01:56:40 -0500195 UpdateAlarm();
196 }
197 else
198 {
Brian Silverman1a675112016-02-20 20:42:49 -0500199 timerQueue.remove(this);
Brian Silverman26e4e522015-12-17 01:56:40 -0500200 }
201 }
202}
203
204/**
205 * Register for single event notification.
206 * A timer event is queued for a single event after the specified delay.
207 * @param delay Seconds to wait before the handler is called.
208 */
209void Notifier::StartSingle(double delay)
210{
211 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
212 m_periodic = false;
213 m_period = delay;
214 DeleteFromQueue();
215 InsertInQueue(false);
216}
217
218/**
219 * Register for periodic event notification.
220 * A timer event is queued for periodic event notification. Each time the interrupt
221 * occurs, the event will be immediately requeued for the same time interval.
222 * @param period Period in seconds to call the handler starting one period after the call to this method.
223 */
224void Notifier::StartPeriodic(double period)
225{
226 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
227 m_periodic = true;
228 m_period = period;
229 DeleteFromQueue();
230 InsertInQueue(false);
231}
232
233/**
234 * Stop timer events from occuring.
235 * Stop any repeating timer events from occuring. This will also remove any single
236 * notification events from the queue.
237 * If a timer-based call to the registered handler is in progress, this function will
238 * block until the handler call is complete.
239 */
240void Notifier::Stop()
241{
242 {
243 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
244 DeleteFromQueue();
245 }
246 // Wait for a currently executing handler to complete before returning from Stop()
247 std::lock_guard<priority_mutex> sync(m_handlerMutex);
248}
249
250void Notifier::Run() {
251 while (!m_stopped) {
252 Notifier::ProcessQueue(0, nullptr);
Brian Silverman1a675112016-02-20 20:42:49 -0500253 bool isEmpty;
Brian Silverman26e4e522015-12-17 01:56:40 -0500254 {
Brian Silverman1a675112016-02-20 20:42:49 -0500255 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
256 isEmpty = timerQueue.empty();
257 }
258 if (!isEmpty)
259 {
260 double expirationTime;
261 {
262 std::lock_guard<priority_recursive_mutex> sync(queueMutex);
263 expirationTime = timerQueue.front()->m_expirationTime;
264 }
265 Wait(expirationTime - GetClock());
Brian Silverman26e4e522015-12-17 01:56:40 -0500266 }
267 else
268 {
269 Wait(0.05);
270 }
271 }
272}