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