Shaka Packager SDK
Loading...
Searching...
No Matches
producer_consumer_queue.h
1// Copyright 2014 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#ifndef PACKAGER_MEDIA_BASE_PRODUCER_CONSUMER_QUEUE_H_
8#define PACKAGER_MEDIA_BASE_PRODUCER_CONSUMER_QUEUE_H_
9
10#include <chrono>
11#include <cstddef>
12#include <cstdint>
13#include <deque>
14
15#include <absl/base/thread_annotations.h>
16#include <absl/log/check.h>
17#include <absl/strings/str_format.h>
18#include <absl/synchronization/mutex.h>
19#include <absl/time/time.h>
20
21#include <packager/macros/classes.h>
22#include <packager/status.h>
23
24namespace shaka {
25namespace media {
26
27static const size_t kUnlimitedCapacity = 0u;
28static const int64_t kInfiniteTimeout = -1;
29
33template <class T>
35 public:
39 explicit ProducerConsumerQueue(size_t capacity);
40
45 ProducerConsumerQueue(size_t capacity, size_t starting_pos);
46
48
57 Status Push(const T& element, int64_t timeout_ms);
58
66 Status Pop(T* element, int64_t timeout_ms);
67
80 Status Peek(size_t pos, T* element, int64_t timeout_ms);
81
85 void Stop() {
86 absl::MutexLock lock(mutex_);
87 stop_requested_ = true;
88 not_empty_cv_.SignalAll();
89 not_full_cv_.SignalAll();
90 new_element_cv_.SignalAll();
91 }
92
94 bool Empty() const {
95 absl::MutexLock lock(mutex_);
96 return q_.empty();
97 }
98
100 size_t Size() const {
101 absl::MutexLock lock(mutex_);
102 return q_.size();
103 }
104
107 size_t HeadPos() const {
108 absl::MutexLock lock(mutex_);
109 return head_pos_;
110 }
111
114 size_t TailPos() const {
115 absl::MutexLock lock(mutex_);
116 return head_pos_ + q_.size() - 1;
117 }
118
121 bool Stopped() const {
122 absl::MutexLock lock(mutex_);
123 return stop_requested_;
124 }
125
126 private:
127 // Move head_pos_ to center on pos.
128 void SlideHeadOnCenter(size_t pos);
129
130 const size_t capacity_; // Maximum number of elements; zero means unlimited.
131
132 mutable absl::Mutex mutex_;
133 size_t head_pos_ ABSL_GUARDED_BY(mutex_); // Head position.
134 std::deque<T> q_
135 ABSL_GUARDED_BY(mutex_); // Internal queue holding the elements.
136 absl::CondVar not_empty_cv_ ABSL_GUARDED_BY(mutex_);
137 absl::CondVar not_full_cv_ ABSL_GUARDED_BY(mutex_);
138 absl::CondVar new_element_cv_ ABSL_GUARDED_BY(mutex_);
139 bool stop_requested_
140 ABSL_GUARDED_BY(mutex_); // True after Stop has been called.
141
142 DISALLOW_COPY_AND_ASSIGN(ProducerConsumerQueue);
143};
144
145// Implementations of non-inline functions.
146template <class T>
148 : capacity_(capacity), head_pos_(0), stop_requested_(false) {}
149
150template <class T>
152 size_t starting_pos)
153 : capacity_(capacity), head_pos_(starting_pos), stop_requested_(false) {}
154
155template <class T>
157
158template <class T>
159Status ProducerConsumerQueue<T>::Push(const T& element, int64_t timeout_ms) {
160 absl::MutexLock lock(mutex_);
161 bool woken = false;
162
163 // Check for queue shutdown.
164 if (stop_requested_)
165 return Status(error::STOPPED, "");
166
167 auto start = std::chrono::steady_clock::now();
168 auto timeout_delta = std::chrono::milliseconds(timeout_ms);
169
170 if (capacity_) {
171 while (q_.size() == capacity_) {
172 if (timeout_ms < 0) {
173 // Wait forever, or until Stop.
174 not_full_cv_.Wait(&mutex_);
175 } else {
176 auto elapsed = std::chrono::steady_clock::now() - start;
177 if (elapsed < timeout_delta) {
178 // Wait with timeout, or until Stop.
179 not_full_cv_.WaitWithTimeout(
180 &mutex_, absl::FromChrono(timeout_delta - elapsed));
181 } else {
182 // We're through waiting.
183 return Status(error::TIME_OUT, "Time out on pushing.");
184 }
185 }
186 // Re-check for queue shutdown after waking from Wait.
187 if (stop_requested_)
188 return Status(error::STOPPED, "");
189
190 woken = true;
191 }
192 DCHECK_LT(q_.size(), capacity_);
193 }
194
195 // Signal consumer to proceed if we are going to create some elements.
196 if (q_.empty())
197 not_empty_cv_.Signal();
198 new_element_cv_.Signal();
199
200 q_.push_back(element);
201
202 // Signal other producers if we just acquired more capacity.
203 if (woken && q_.size() != capacity_)
204 not_full_cv_.Signal();
205 return Status::OK;
206}
207
208template <class T>
209Status ProducerConsumerQueue<T>::Pop(T* element, int64_t timeout_ms) {
210 absl::MutexLock lock(mutex_);
211 bool woken = false;
212
213 auto start = std::chrono::steady_clock::now();
214 auto timeout_delta = std::chrono::milliseconds(timeout_ms);
215
216 while (q_.empty()) {
217 if (stop_requested_)
218 return Status(error::STOPPED, "");
219
220 if (timeout_ms < 0) {
221 // Wait forever, or until Stop.
222 not_empty_cv_.Wait(&mutex_);
223 } else {
224 auto elapsed = std::chrono::steady_clock::now() - start;
225 if (elapsed < timeout_delta) {
226 // Wait with timeout, or until Stop.
227 not_empty_cv_.WaitWithTimeout(
228 &mutex_, absl::FromChrono(timeout_delta - elapsed));
229 } else {
230 // We're through waiting.
231 return Status(error::TIME_OUT, "Time out on popping.");
232 }
233 }
234 woken = true;
235 }
236
237 // Signal producer to proceed if we are going to create some capacity.
238 if (q_.size() == capacity_)
239 not_full_cv_.Signal();
240
241 *element = q_.front();
242 q_.pop_front();
243 ++head_pos_;
244
245 // Signal other consumers if we have more elements.
246 if (woken && !q_.empty())
247 not_empty_cv_.Signal();
248 return Status::OK;
249}
250
251template <class T>
253 T* element,
254 int64_t timeout_ms) {
255 absl::MutexLock lock(mutex_);
256 if (pos < head_pos_) {
257 return Status(error::INVALID_ARGUMENT,
258 absl::StrFormat("pos (%zu) is too small; head is at %zu.",
259 pos, head_pos_));
260 }
261
262 bool woken = false;
263
264 auto start = std::chrono::steady_clock::now();
265 auto timeout_delta = std::chrono::milliseconds(timeout_ms);
266
267 // Move head to create some space (move the sliding window centered @ pos).
268 SlideHeadOnCenter(pos);
269
270 while (pos >= head_pos_ + q_.size()) {
271 if (stop_requested_)
272 return Status(error::STOPPED, "");
273
274 if (timeout_ms < 0) {
275 // Wait forever, or until Stop.
276 new_element_cv_.Wait(&mutex_);
277 } else {
278 auto elapsed = std::chrono::steady_clock::now() - start;
279 if (elapsed < timeout_delta) {
280 // Wait with timeout, or until Stop.
281 new_element_cv_.WaitWithTimeout(
282 &mutex_, absl::FromChrono(timeout_delta - elapsed));
283 } else {
284 // We're through waiting.
285 return Status(error::TIME_OUT, "Time out on peeking.");
286 }
287 }
288 // Move head to create some space (move the sliding window centered @ pos).
289 SlideHeadOnCenter(pos);
290 woken = true;
291 }
292
293 *element = q_[pos - head_pos_];
294
295 // Signal other consumers if we have more elements.
296 if (woken && !q_.empty())
297 new_element_cv_.Signal();
298 return Status::OK;
299}
300
301template <class T>
303 mutex_.AssertHeld();
304
305 if (capacity_) {
306 // Signal producer to proceed if we are going to create some capacity.
307 if (q_.size() == capacity_ && pos > head_pos_ + capacity_ / 2)
308 not_full_cv_.Signal();
309
310 while (!q_.empty() && pos > head_pos_ + capacity_ / 2) {
311 ++head_pos_;
312 q_.pop_front();
313 }
314 }
315}
316
317} // namespace media
318} // namespace shaka
319
320#endif // PACKAGER_MEDIA_BASE_PRODUCER_CONSUMER_QUEUE_H_
Status Push(const T &element, int64_t timeout_ms)
Status Peek(size_t pos, T *element, int64_t timeout_ms)
Status Pop(T *element, int64_t timeout_ms)
All the methods that are virtual are virtual for mocking.