CPPMyth
Library to interoperate with MythTV server
Loading...
Searching...
No Matches
latch.cpp
1/*
2 * Copyright (C) 2026 Jean-Luc Barriere
3 *
4 * This library is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published
6 * by the Free Software Foundation; either version 3, or (at your option)
7 * any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; see the file COPYING. If not, write to
16 * the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 */
21
22#include "latch.h"
23
24#include <cassert>
25
26#ifdef NSROOT
27using namespace NSROOT::OS;
28#else
29using namespace OS;
30#endif
31
32void Latch::init()
33{
34 mutex_init(&x_gate_lock);
35 cond_init(&x_gate);
36 mutex_init(&s_gate_lock);
37 cond_init(&s_gate);
38
39 /* preallocate free list with 2 nodes */
40 TNode * n1 = new_node(thread_t());
41 TNode * n2 = new_node(thread_t());
42 free_node(n1);
43 free_node(n2);
44}
45
46Latch::TNode * Latch::find_node(const thread_t& id)
47{
48 TNode * p = s_nodes;
49 while (p != nullptr && thread_equal(p->id, id) == 0)
50 {
51 p = p->_next;
52 }
53 return p;
54}
55
56Latch::TNode * Latch::new_node(const thread_t& id)
57{
58 TNode * p;
59 if (s_freed == nullptr)
60 {
61 /* create node */
62 p = new TNode();
63 }
64 else
65 {
66 /* pop front from free list */
67 p = s_freed;
68 s_freed = p->_next;
69 }
70
71 /* setup */
72 p->id = id;
73 p->count = 0;
74
75 /* push front in list */
76 p->_prev = nullptr;
77 p->_next = s_nodes;
78 if (s_nodes != nullptr)
79 {
80 s_nodes->_prev = p;
81 }
82 s_nodes = p;
83 return p;
84}
85
86void Latch::free_node(TNode * n)
87{
88 /* remove from list */
89 if (n == s_nodes)
90 {
91 s_nodes = n->_next;
92 }
93 else
94 {
95 n->_prev->_next = n->_next;
96 }
97 if (n->_next != nullptr)
98 {
99 n->_next->_prev = n->_prev;
100 }
101
102 /* push front in free list */
103 if (s_freed != nullptr)
104 {
105 s_freed->_prev = n;
106 }
107 n->_next = s_freed;
108 n->_prev = nullptr;
109 s_freed = n;
110}
111
112Latch::Latch()
113: s_spin(0)
114, x_owner(0)
115, x_wait(0)
116, x_flag(0)
117, px(true)
118, s_freed(nullptr)
119, s_nodes(nullptr)
120{
121 init();
122}
123
124Latch::Latch(bool _px)
125: s_spin(0)
126, x_owner(0)
127, x_wait(0)
128, x_flag(0)
129, px(_px)
130, s_freed(nullptr)
131, s_nodes(nullptr)
132{
133 init();
134}
135
136Latch::~Latch()
137{
138 /* destroy free nodes */
139 while (s_freed != nullptr) {
140 TNode * n = s_freed;
141 s_freed = s_freed->_next;
142 delete n;
143 }
144 /* it should be empty, but still tries to destroy any existing busy node */
145 while (s_nodes != nullptr) {
146 TNode * n = s_nodes;
147 s_nodes = s_nodes->_next;
148 delete n;
149 }
150
151 cond_destroy(&s_gate);
152 mutex_destroy(&s_gate_lock);
153 cond_destroy(&x_gate);
154 mutex_destroy(&x_gate_lock);
155}
156
165#define X_STEP_0 0
166#define X_STEP_1 1
167#define X_STEP_2 2
168#define X_STEP_3 3
169
170/* Depending on the internal implementation of conditional variable,
171 * a race condition could arise, permanently blocking the thread;
172 * Setting a timeout works around the issue.
173 */
174#define EXIT_TIMEOUT 1000
175
176void Latch::lock()
177{
178 thread_t tid = thread_self();
179
180 spin_lock();
181
182 if (!thread_equal(x_owner, tid))
183 {
184 /* increments the count of request in wait */
185 x_wait += 1;
186 for (;;)
187 {
188 /* if flag is 0 or 2 then it hold X with no wait,
189 * in other case it have to wait for X gate
190 */
191 if (x_flag == X_STEP_0 || x_flag == X_STEP_2)
192 {
193 x_flag = X_STEP_1;
194 x_wait -= 1;
195 break;
196 }
197 else
198 {
199 /* !!! pop gate then unlock spin */
200 mutex_lock(&x_gate_lock);
201 spin_unlock();
202 cond_timedwait(&x_gate, &x_gate_lock, EXIT_TIMEOUT);
203 mutex_unlock(&x_gate_lock);
204 }
205 spin_lock();
206 }
207
208 /* find the thread node */
209 TNode * n = find_node(tid);
210 /* X = 1, check the releasing of S */
211 for (;;)
212 {
213 /* if the count of S is zeroed, or equal to self count, then it finalizes
214 * with no wait,
215 * in other case it has to wait for S gate */
216 if (s_nodes == nullptr || (s_nodes == n && s_nodes->_next == nullptr))
217 {
218 x_flag = X_STEP_3;
219 break;
220 }
221 else
222 {
223 /* !!! pop gate then unlock spin (reverse order for S notifier) */
224 mutex_lock(&s_gate_lock);
225 spin_unlock();
226 cond_timedwait(&s_gate, &s_gate_lock, EXIT_TIMEOUT);
227 mutex_unlock(&s_gate_lock);
228 spin_lock();
229 /* check if the notifier has hand over, else retry */
230 if (x_flag == X_STEP_3)
231 {
232 break;
233 }
234 }
235 }
236
237 /* X = 3, set owner */
238 x_owner = tid;
239 }
240 else
241 {
242 /* recursive X lock */
243 x_flag += 1;
244 }
245
246 spin_unlock();
247}
248
249void Latch::unlock()
250{
251 thread_t tid = thread_self();
252
253 spin_lock();
254 if (thread_equal(x_owner, tid))
255 {
256 /* decrement recursive lock */
257 x_flag -= 1;
258 if (x_flag == X_STEP_2)
259 {
260 x_owner = thread_t(0);
261 /* hand-over to a request in wait for X, else release */
262 if (x_wait == 0)
263 {
264 x_flag = X_STEP_0;
265 }
266 /* !!! unlock spin then pop gate (reverse order for receiver) */
267 spin_unlock();
268 mutex_lock(&x_gate_lock);
269 cond_broadcast(&x_gate);
270 mutex_unlock(&x_gate_lock);
271 }
272 else
273 {
274 spin_unlock();
275 }
276 }
277 else
278 {
279 spin_unlock();
280 }
281}
282
283void Latch::lock_shared()
284{
285 thread_t tid = thread_self();
286
287 spin_lock();
288
289 /* find the thread node */
290 TNode * n = find_node(tid);
291
292 if (!thread_equal(x_owner, tid))
293 {
294 /* if flag is 0 or 1 then it hold S with no wait,
295 * in other case it have to wait for X gate
296 */
297 for (;;)
298 {
299 if (!px)
300 {
301 /* X precedence is false */
302 if (x_flag < X_STEP_2)
303 {
304 break;
305 }
306 }
307 else
308 {
309 /* X precedence is true,
310 * test if this thread holds a recursive S lock
311 */
312 if (x_flag == X_STEP_0 || (x_flag == X_STEP_1 && n != nullptr))
313 {
314 break;
315 }
316 }
317 /* !!! pop gate then unlock spin */
318 mutex_lock(&x_gate_lock);
319 spin_unlock();
320 cond_timedwait(&x_gate, &x_gate_lock, EXIT_TIMEOUT);
321 mutex_unlock(&x_gate_lock);
322 spin_lock();
323 }
324 }
325 if (n == nullptr)
326 {
327 n = new_node(tid);
328 }
329 /* increment recursive count for this thread */
330 ++n->count;
331
332 spin_unlock();
333}
334
335void Latch::unlock_shared()
336{
337 thread_t tid = thread_self();
338
339 spin_lock();
340
341 /* find the thread node */
342 TNode * n = find_node(tid);
343 /* does it own shared lock ? */
344 assert(n != nullptr);
345
346 /* decrement recursive count for this thread, finally free */
347 if (--n->count == 0)
348 {
349 free_node(n);
350 /* on last S, finalize X request in wait, and notify */
351 if (x_flag == X_STEP_1 && !thread_equal(x_owner, tid))
352 {
353 if (s_nodes == nullptr)
354 {
355 x_flag = X_STEP_3;
356 }
357 /* !!! unlock spin then pop gate (reverse order for X receiver) */
358 spin_unlock();
359 mutex_lock(&s_gate_lock);
360 cond_signal(&s_gate);
361 mutex_unlock(&s_gate_lock);
362 }
363 else
364 {
365 spin_unlock();
366 }
367 }
368 else
369 {
370 spin_unlock();
371 }
372}
373
374bool Latch::try_lock_shared()
375{
376 thread_t tid = thread_self();
377
378 spin_lock();
379 /* if X = 0 then it hold S with success,
380 * in other case fails
381 */
382 if (x_flag == X_STEP_0 || thread_equal(x_owner, tid))
383 {
384 /* find the thread node, else create */
385 TNode * n = find_node(tid);
386 if (n == nullptr)
387 {
388 n = new_node(tid);
389 }
390 /* increment recursive count for this thread */
391 ++n->count;
392
393 spin_unlock();
394 return true;
395 }
396 spin_unlock();
397 return false;
398}