HLCC Documentation 3.0.0
Loading...
Searching...
No Matches
circularBufferConcurrent.ipp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2020-2025 European Southern Observatory (ESO)
2//
3// SPDX-License-Identifier: LGPL-3.0-only
4/*
5 * circularBufferConcurrent.hpp
6 *
7 * Created on: Jul 7, 2021
8 * Author: hsommer
9 */
10
11#include <mutex>
12#include <condition_variable>
13#include <functional>
14
15#include <boost/circular_buffer.hpp>
16
17
18// HSO TODO: Check async calls in ProcessRequest in ~/git-rtc/rtctk/componentFramework/services/oldb/src/oldbAdapter.cpp
19
20
21namespace hlcc::oldbmux {
22
23template<typename T>
25 const std::string& name, std::size_t capacity, const log4cplus::Logger& logger)
26 : m_logger {logger},
27 m_closed {false},
28 cb {capacity},
29 mutex {},
30 buffer_not_empty {},
31 discard_listener {},
32 name {name}
33{
34 if (capacity < 1) {
35 // TODO what to throw? Java has throw new IllegalArgumentException("bufferCapacity must be >= 1.");
36 }
37}
38
39
40template<typename T>
44
45
46template<typename T>
48 RAD_TRACE(m_logger);
49
50 // we better close right away
51 bool expected = false; // TODO: how to pass bool& as a 'false' literal, without declaring the variable?
52 // if m_closed == false then set it to true and execute the code block that closes the buffer.
53 if (m_closed.compare_exchange_strong(expected, true)) {
54
55 // release waiting thread if Poll was called with a timeout
56 buffer_not_empty.notify_all();
58 /*
59 * To speed up destruction, we discard from the buffer all but the last data.
60 * For discarded data, we call the discard_listener.
61 *
62 * Clients such as CiiOldbDataPointAsync may decrement a counter for ongoing write operations
63 * in their discard callback functions, to block their dtors until all data is written or discarded.
64 * There is a race condition with the background worker thread of CiiOldbDataPointAsync, in which
65 * the thread may be scheduled before Close() gets called, but runs only afterwards. We leave the last data item
66 * for such a thread to process and decrement the counter, to make sure that the thread does not wake up
67 * using the invalid memory location of an already destructed CiiOldbDataPointAsync instance.
68 * If not discarding one data item becomes a performance problem during shutdown, then we should discard
69 * all queued data, but put back a special sentinel data that the background thread can clear in no time,
70 * but which nonetheless keeps the writer count up until the thread is done.
71 */
72 std::lock_guard lck {mutex};
73 while (cb.size() > 1) {
74 std::optional<T> data_opt = Poll();
75 if (data_opt && discard_listener) {
76 discard_listener(*data_opt);
77 }
78 }
79 }
80}
81
82template<typename T>
84 if (!m_closed) {
85 { // lock scope
86 std::lock_guard lck {mutex};
88 std::optional<T> data_old {};
90 if (cb.full() && discard_listener) {
91 data_old = std::move(cb.front()); // saves a copy
92 }
93
94 cb.push_back(std::move(data)); // discards the front data if buffer is full (i.e., assigns the new data to the old element).
95
96 if (data_old && discard_listener) {
97 discard_listener(*data_old);
98 }
99
100 // release lck before notifying the condition variable.
101 // If the thread waiting in Poll wakes immediately, it doesn't then
102 // have to block again, waiting for us to unlock the mutex.
103 }
104 buffer_not_empty.notify_one();
105 } else {
106 // We call discard_listener directly for data given to Push after a Close call, without using cb.
107 if (discard_listener) {
108 discard_listener(data);
109 }
110 }
111}
112
113
114template<typename T>
115std::optional<T> CircularBufferConcurrent<T>::Poll(std::chrono::milliseconds timeout) {
116
117 // Note that it is important for this method to work even when m_closed==true.
118 // As explained in Poll, the UC is for CiiOldbDataPointAsync, whose worker thread must process
119 // the last buffered item.
120
121 // condition variable needs to be able to lock and unlock the mutex.
122 // Our normal lock_guard doesn't allow this, thus we use unique_lock.
123 std::unique_lock lck {mutex};
124 // wait_for with predicate keeps looping after spurious wakeups.
125 // It yields the lock and re-acquires it after wakeup, see https://en.cppreference.com/w/cpp/thread/condition_variable/wait_for
126 if (buffer_not_empty.wait_for(lck, timeout, [this](){return (!cb.empty() || m_closed);})) {
127 // no timeout but condition met: Either got data, or got notified by Close.
128 // If it was Close,
129 // - then perhaps Close has already emptied the buffer and notified discard_listener.
130 // - and if there is still data, then we return that last data item. The others will be removed by Close right after this.
131 if (!cb.empty()) {
132 T data = std::move(cb.front());
133 cb.pop_front();
134 return std::make_optional<T>(std::move(data));
135 }
136 }
137 // got no data because:
138 // - wait_for returned false for !cb.empty() (i.e., empty buffer) or
139 // - wait_for returned true for m_closed and there was no data in the buffer or
140 // - wait_for timed out (if timeout > 0) and thus predicate value was still false.
141 return {};
142 // RAII unlocks mutex
143}
144
145
146template<typename T>
148 std::lock_guard lck {mutex};
149 cb.clear();
150}
151
152
153template<typename T>
155 std::lock_guard lck {mutex};
156 return cb.size();
157}
158
159
160template<typename T>
161const std::unique_ptr<std::scoped_lock<std::recursive_mutex>> CircularBufferConcurrent<T>::Lock() const {
162 return std::make_unique<std::scoped_lock<std::recursive_mutex>>(mutex);
163}
164
165
166template<typename T>
167void CircularBufferConcurrent<T>::SetDiscardListener(std::function<void(T&)> discard_listener) {
168 std::lock_guard lck {mutex};
169 this->discard_listener = discard_listener;
170}
171
172template<typename T>
173boost::circular_buffer<T>& CircularBufferConcurrent<T>::GetCb() {
174 return cb;
175}
176
177
178} // end namespace hlcc::oldbmux
void Close()
Definition circularBufferConcurrent.ipp:47
int Size() const
Definition circularBufferConcurrent.ipp:154
const std::unique_ptr< std::scoped_lock< std::recursive_mutex > > Lock() const
Definition circularBufferConcurrent.ipp:161
std::optional< hlcc::oldbmux::CiiOldbDataPointAsync::OldbDataWithPromise > Poll(std::chrono::milliseconds timeout=std::chrono::milliseconds::zero())
Definition circularBufferConcurrent.ipp:115
CircularBufferConcurrent(const std::string &name, std::size_t capacity, const log4cplus::Logger &logger)
Definition circularBufferConcurrent.ipp:24
boost::circular_buffer< T > & GetCb()
Definition circularBufferConcurrent.ipp:173
~CircularBufferConcurrent()
Definition circularBufferConcurrent.ipp:41
void Clear()
Definition circularBufferConcurrent.ipp:147
void SetDiscardListener(std::function< void(T &)> discard_listener)
Definition circularBufferConcurrent.ipp:167
void Push(T &&data)
Adds new data to the circular buffer.
Definition circularBufferConcurrent.ipp:83
Definition ciiOldbDataPointAsync.hpp:35
ccsinsdetifllnetio::PointingKernelPositions data
Definition pkp_llnetio_subscriber.cpp:29