vdr 2.8.4
thread.c
Go to the documentation of this file.
1/*
2 * thread.c: A simple thread base class
3 *
4 * See the main source file 'vdr.c' for copyright information and
5 * how to reach the author.
6 *
7 * $Id: thread.c 5.10 2026/08/31 10:48:15 kls Exp $
8 */
9
10#include "thread.h"
11#include <cxxabi.h>
12#include <dlfcn.h>
13#include <errno.h>
14#include <execinfo.h>
15#include <linux/unistd.h>
16#include <malloc.h>
17#include <stdarg.h>
18#include <stdlib.h>
19#include <sys/prctl.h>
20#include <sys/resource.h>
21#include <sys/syscall.h>
22#include <sys/time.h>
23#include <sys/wait.h>
24#include <unistd.h>
25#include "tools.h"
26
27#define ABORT { dsyslog("ABORT!"); cBackTrace::BackTrace(); abort(); }
28
29//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
30#define DEBUG_LOCKSEQ // uncomment this line to activate debug output for invalid locking sequence
31//#define DEBUG_LOCKCALL // uncomment this line to activate caller information with DEBUG_LOCKSEQ (WARNING: expensive operation, use only when actually debugging the locking sequence!)
32
33#ifdef DEBUG_LOCKING
34#define dbglocking(a...) fprintf(stderr, a)
35#else
36#define dbglocking(a...)
37#endif
38
39static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
40{
41 struct timeval now;
42 if (gettimeofday(&now, NULL) == 0) { // get current time
43 MillisecondsFromNow = max(MillisecondsFromNow, 3); // // making sure the time is >2ms to avoid possible busy waits
44 now.tv_sec += MillisecondsFromNow / 1000; // add full seconds
45 now.tv_usec += (MillisecondsFromNow % 1000) * 1000; // add microseconds
46 if (now.tv_usec >= 1000000) { // take care of an overflow
47 now.tv_sec++;
48 now.tv_usec -= 1000000;
49 }
50 Abstime->tv_sec = now.tv_sec; // seconds
51 Abstime->tv_nsec = now.tv_usec * 1000; // nano seconds
52 return true;
53 }
54 return false;
55}
56
57// --- cCondWait -------------------------------------------------------------
58
60{
61 signaled = false;
62 pthread_mutex_init(&mutex, NULL);
63 pthread_cond_init(&cond, NULL);
64}
65
67{
68 pthread_cond_broadcast(&cond); // wake up any sleepers
69 pthread_cond_destroy(&cond);
70 pthread_mutex_destroy(&mutex);
71}
72
73void cCondWait::SleepMs(int TimeoutMs)
74{
75 cCondWait w;
76 w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait
77}
78
79bool cCondWait::Wait(int TimeoutMs)
80{
81 pthread_mutex_lock(&mutex);
82 if (!signaled) {
83 if (TimeoutMs) {
84 struct timespec abstime;
85 if (GetAbsTime(&abstime, TimeoutMs)) {
86 while (!signaled) {
87 if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT)
88 break;
89 }
90 }
91 }
92 else
93 pthread_cond_wait(&cond, &mutex);
94 }
95 bool r = signaled;
96 signaled = false;
97 pthread_mutex_unlock(&mutex);
98 return r;
99}
100
102{
103 pthread_mutex_lock(&mutex);
104 signaled = true;
105 pthread_cond_broadcast(&cond);
106 pthread_mutex_unlock(&mutex);
107}
108
109// --- cCondVar --------------------------------------------------------------
110
112{
113 pthread_cond_init(&cond, 0);
114}
115
117{
118 pthread_cond_broadcast(&cond); // wake up any sleepers
119 pthread_cond_destroy(&cond);
120}
121
123{
124 if (Mutex.locked) {
125 int locked = Mutex.locked;
126 Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait
127 // does an implicit unlock of the mutex
128 Mutex.lockThreadId = 0;
129 pthread_cond_wait(&cond, &Mutex.mutex);
130 Mutex.lockThreadId = cThread::ThreadId(); // pthread_cond_wait re-locked the mutex without going through cMutex::Lock()
131 Mutex.locked = locked;
132 }
133}
134
135bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs)
136{
137 bool r = true; // true = condition signaled, false = timeout
138
139 if (Mutex.locked) {
140 struct timespec abstime;
141 if (GetAbsTime(&abstime, TimeoutMs)) {
142 int locked = Mutex.locked;
143 Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_timedwait
144 // does an implicit unlock of the mutex.
145 Mutex.lockThreadId = 0;
146 if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT)
147 r = false;
148 Mutex.lockThreadId = cThread::ThreadId(); // pthread_cond_timedwait re-locked the mutex without going through cMutex::Lock()
149 Mutex.locked = locked;
150 }
151 }
152 return r;
153}
154
156{
157 pthread_cond_broadcast(&cond);
158}
159
160// --- cRwLock ---------------------------------------------------------------
161
162cRwLock::cRwLock(bool PreferWriter)
163{
164 locked = 0;
166 pthread_rwlockattr_t attr;
167 pthread_rwlockattr_init(&attr);
168 pthread_rwlockattr_setkind_np(&attr, PreferWriter ? PTHREAD_RWLOCK_PREFER_WRITER_NP : PTHREAD_RWLOCK_PREFER_READER_NP);
169 pthread_rwlock_init(&rwlock, &attr);
170}
171
173{
174 pthread_rwlock_destroy(&rwlock);
175}
176
177bool cRwLock::Lock(bool Write, int TimeoutMs)
178{
179 int Result = 0;
180 struct timespec abstime;
181 if (TimeoutMs) {
182 if (!GetAbsTime(&abstime, TimeoutMs))
183 TimeoutMs = 0;
184 }
185 if (Write) {
186 Result = TimeoutMs ? pthread_rwlock_timedwrlock(&rwlock, &abstime) : pthread_rwlock_wrlock(&rwlock);
187 if (Result == 0)
189 }
190 else if (writeLockThreadId == cThread::ThreadId()) {
191 locked++; // there can be any number of stacked read locks, so we keep track here
192 Result = 0; // acquiring a read lock while holding a write lock within the same thread is OK
193 }
194 else
195 Result = TimeoutMs ? pthread_rwlock_timedrdlock(&rwlock, &abstime) : pthread_rwlock_rdlock(&rwlock);
196 return Result == 0;
197}
198
200{
201 if (writeLockThreadId == cThread::ThreadId()) { // this is the thread that obtained the initial write lock
202 if (locked) { // this is the unlock of a read lock within the write lock
203 locked--;
204 return;
205 }
206 }
208 pthread_rwlock_unlock(&rwlock);
209}
210
211// --- cMutex ----------------------------------------------------------------
212
214{
215 lockThreadId = 0;
216 locked = 0;
217 pthread_mutexattr_t attr;
218 pthread_mutexattr_init(&attr);
219 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
220 pthread_mutex_init(&mutex, &attr);
221}
222
224{
225 pthread_mutex_destroy(&mutex);
226}
227
228bool cMutex::Lock(int TimeoutMs, bool *TimedOut)
229{
230 // PTHREAD_MUTEX_ERRORCHECK_NP rejects recursive entry from the same thread; handle the
231 // nested cMutexLock case (documented in thread.h) here, like cRwLock does below:
232 tThreadId ThisThreadId = cThread::ThreadId();
233 if (lockThreadId == ThisThreadId && locked) {
234 locked++;
235 return true;
236 }
237 if (TimeoutMs > 0) {
238 struct timespec abstime;
239 if (!GetAbsTime(&abstime, TimeoutMs) || pthread_mutex_timedlock(&mutex, &abstime) != 0) {
240 if (TimedOut)
241 *TimedOut = true;
242 return false;
243 }
244 }
245 else
246 pthread_mutex_lock(&mutex);
247 lockThreadId = ThisThreadId;
248 locked++;
249 return true;
250}
251
253{
254 if (!--locked) {
255 lockThreadId = 0;
256 pthread_mutex_unlock(&mutex);
257 }
258}
259
260// --- cThread ---------------------------------------------------------------
261
263
264cThread::cThread(const char *Description, bool LowPriority)
265{
266 active = running = false;
267 childTid = 0;
268 childThreadId = 0;
269 description = NULL;
270 if (Description)
271 SetDescription("%s", Description);
272 lowPriority = LowPriority;
273}
274
276{
277 Cancel(); // just in case the derived class didn't call it
278 free(description);
279}
280
281void cThread::SetPriority(int Priority)
282{
283 if (setpriority(PRIO_PROCESS, 0, Priority) < 0)
284 LOG_ERROR;
285}
286
287void cThread::SetIOPriority(int Priority)
288{
289 if (syscall(SYS_ioprio_set, 1, 0, (Priority & 0xff) | (3 << 13)) < 0) // idle class
290 LOG_ERROR;
291}
292
293void cThread::SetDescription(const char *Description, ...)
294{
295 free(description);
296 description = NULL;
297 if (Description) {
298 va_list ap;
299 va_start(ap, Description);
300 description = strdup(cString::vsprintf(Description, ap));
301 va_end(ap);
302 }
303}
304
306{
307 Thread->childThreadId = ThreadId();
308 if (Thread->description) {
309 dsyslog("%s thread started (pid=%d, tid=%d, prio=%s)", Thread->description, getpid(), Thread->childThreadId.load(), Thread->lowPriority ? "low" : "high");
310#ifdef PR_SET_NAME
311 if (prctl(PR_SET_NAME, Thread->description, 0, 0, 0) < 0)
312 esyslog("%s thread naming failed (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId.load());
313#endif
314 }
315 if (Thread->lowPriority) {
316 Thread->SetPriority(19);
317 Thread->SetIOPriority(7);
318 }
319 Thread->Action();
320 if (Thread->description)
321 dsyslog("%s thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId.load());
322 Thread->running = false;
323 Thread->active = false;
324 return NULL;
325}
326
327#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it
328#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop
329
331{
332 if (!running) {
333 if (active) {
334 // Wait until the previous incarnation of this thread has completely ended
335 // before starting it newly:
336 cTimeMs RestartTimeout;
337 while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT)
339 }
340 if (!active) {
341 active = running = true;
342 if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) {
343 pthread_detach(childTid); // auto-reap
344 }
345 else {
346 LOG_ERROR;
347 active = running = false;
348 return false;
349 }
350 }
351 }
352 return true;
353}
354
356{
357 if (active) {
358 //
359 // Single UNIX Spec v2 says:
360 //
361 // The pthread_kill() function is used to request
362 // that a signal be delivered to the specified thread.
363 //
364 // As in kill(), if sig is zero, error checking is
365 // performed but no signal is actually sent.
366 //
367 int err;
368 if ((err = pthread_kill(childTid, 0)) != 0) {
369 if (err != ESRCH)
370 LOG_ERROR;
371 childTid = 0;
372 if (active) // read before overwriting: a thread that ended by itself has cleared it, and seeing that orders its work before the caller's
373 active = running = false;
374 }
375 else
376 return true;
377 }
378 return false;
379}
380
381void cThread::Cancel(int WaitSeconds)
382{
383 running = false;
384 if (active && WaitSeconds > -1) {
385 if (WaitSeconds > 0) {
386 cTimeMs t(WaitSeconds * 1000);
387 while (!t.TimedOut()) {
388 if (!Active())
389 return;
391 }
392 esyslog("ERROR: %s thread %d won't end (waited %d seconds) - canceling it...", description ? description : "", childThreadId.load(), WaitSeconds);
393 }
394 pthread_cancel(childTid);
395 childTid = 0;
396 active = false;
397 }
398}
399
401{
402 // Cache the result in thread-local storage. This is called on every
403 // cMutex::Lock() and every cRwLock operation; the bare syscall costs
404 // ~70ns whereas a TLS read costs ~1.5ns. A thread's id is fixed for its
405 // lifetime, so caching is always safe. Each thread starts with 0 and
406 // fetches its own id on first use.
407 static thread_local tThreadId threadId = 0;
408 if (threadId == 0)
409 threadId = syscall(__NR_gettid);
410 return threadId;
411}
412
414{
415 if (mainThreadId == 0)
417 else
418 esyslog("ERROR: attempt to set main thread id to %d while it already is %d", ThreadId(), mainThreadId);
419}
420
421// --- cMutexLock ------------------------------------------------------------
422
424{
425 mutex = NULL;
426 locked = false;
427 Lock(Mutex);
428}
429
431{
432 if (mutex && locked)
433 mutex->Unlock();
434}
435
436bool cMutexLock::Lock(cMutex *Mutex, int TimeoutMs, bool *TimedOut)
437{
438 if (Mutex && !mutex) {
439 if (!Mutex->Lock(TimeoutMs, TimedOut))
440 return false;
441 mutex = Mutex;
442 locked = true;
443 return true;
444 }
445 return false;
446}
447
448// --- cThreadLock -----------------------------------------------------------
449
451{
452 thread = NULL;
453 locked = false;
454 Lock(Thread);
455}
456
458{
459 if (thread && locked)
460 thread->Unlock();
461}
462
464{
465 if (Thread && !thread) {
466 thread = Thread;
467 Thread->Lock();
468 locked = true;
469 return true;
470 }
471 return false;
472}
473
474// --- cBackTrace ------------------------------------------------------------
475
476#define BT_BUF_SIZE 100
477
479{
480 char *Module = s;
481 char *Function = NULL;
482 char *Offset = NULL;
483 char *Address = NULL;
484 // separate the string:
485 for (char *q = Module; *q; q++) {
486 if (*q == '(') {
487 *q = 0;
488 Function = q + 1;
489 }
490 else if (*q == '+') {
491 *q = 0;
492 Offset = q + 1;
493 }
494 else if (*q == ')')
495 *q = 0;
496 else if (*q == '[')
497 Address = q + 1;
498 else if (*q == ']') {
499 *q = 0;
500 break;
501 }
502 }
503 // demangle the function name:
504 char *DemangledFunction = NULL;
505 if (Function) {
506 int status;
507 DemangledFunction = abi::__cxa_demangle(Function, NULL, 0, &status);
508 if (DemangledFunction)
509 Function = DemangledFunction;
510 if (!*Function)
511 Function = NULL;
512 }
513 cString d = cString::sprintf("%s%s%s", Module, Function ? " " : "", Function ? Function : "");
514 // convert string address to numbers:
515 unsigned long long addr = Address ? strtoull(Address, NULL, 0) : 0;
516 unsigned long long offs = Offset ? strtoull(Offset, NULL, 0) : 0;
517 // for shared libraries we need get the offset inside the library:
518 if (Function) {
519 // check whether the module name ends with ".so*":
520 char *e = Module;
521 char *p = NULL;
522 while (e = strstr(e, ".so"))
523 p = e++;
524 if (p && !strchr(p, '/')) {
525 Dl_info dlinfo;
526 if (dladdr(reinterpret_cast<void*>(addr), &dlinfo)) {
527 if ((strcmp(Module, dlinfo.dli_fname) == 0) && dlinfo.dli_fbase) {
528 unsigned long long base = reinterpret_cast<unsigned long long>(dlinfo.dli_fbase);
529 addr -= base;
530 addr &= 0x0FFFFFFFF; // to make it work on both 32 and 64 bit systems
531 }
532 }
533 }
534 }
535 // determine the file name and line number:
536 cString cmd = cString::sprintf("addr2line --functions --demangle --inlines --basename --exe=%s 0x%llx", Module, Function ? addr : offs);
537 cPipe p;
538 if (p.Open(cmd, "r")) {
539 int n = 0;
540 cReadLine rl;
541 while (char *l = rl.Read(p)) {
542 if (n == 0) {
543 if (Function && strcmp(l, Function))
544 d = cString::sprintf("%s calling %s", *d, l);
545 }
546 else
547 d = cString::sprintf("%s at %s", *d, l);
548 n++;
549 }
550 p.Close();
551 }
552 free(DemangledFunction);
553 return d;
554}
555
556void cBackTrace::BackTrace(cStringList &StringList, int Level, bool Mangled)
557{
558 void *b[BT_BUF_SIZE];
559 int n = backtrace(b, BT_BUF_SIZE);
560 if (char **s = backtrace_symbols(b, n)) {
561 for (int i = max(Level, 0) + 1; i < n; i++) // 1 is the call to this function itself
562 StringList.Append(strdup(Mangled ? s[i] : *Demangle(s[i])));
563 free(s);
564 }
565}
566
567void cBackTrace::BackTrace(FILE *f, int Level, bool Mangled)
568{
569 cStringList sl;
570 BackTrace(sl, Level + 1, Mangled); // 1 is the call to this function itself
571 for (int i = 0; i < sl.Size(); i++) {
572 if (f)
573 fprintf(f, "%s\n", sl[i]);
574 else
575 dsyslog("%s", sl[i]);
576 }
577}
578
579cString cBackTrace::GetCaller(int Level, bool Mangled)
580{
581 cString Caller;
582 Level = max(Level, 0) + 1; // 1 is the call to this function itself
583 void *b[BT_BUF_SIZE];
584 int n = backtrace(b, BT_BUF_SIZE);
585 if (char **s = backtrace_symbols(b, n)) {
586 if (Level < n)
587 Caller = Mangled ? s[Level] : *Demangle(s[Level]);
588 free(s);
589 }
590 return Caller;
591}
592
593// --- cStateLockLog ---------------------------------------------------------
594
595#ifdef DEBUG_LOCKSEQ
596#define SLL_SIZE 20 // the number of log entries
597#define SLL_LENGTH 512 // the maximum length of log entries
598#define SLL_THREADS 20 // the maximum number of threads holding locks at the same time (typically well below 10)
599#define SLL_MAX_LIST 9 // max. number of lists to log
600#define SLL_WRITE_FLAG 0x80000000
601#define SLL_LOCK_FLAG 0x40000000
602
604private:
611#ifdef DEBUG_LOCKCALL
612 char logCaller[SLL_SIZE][SLL_LENGTH];
613#endif
615 bool dumped;
616 void Dump(const char *Name, tThreadId ThreadId);
617public:
618 cStateLockLog(void);
619 void Check(const char *Name, bool Lock, bool Write = false);
620 };
621
623{
624 memset(logThreadIds, 0, sizeof(logThreadIds));
625 memset(logFlags, 0, sizeof(logFlags));
626 memset(logCounter, 0, sizeof(logCounter));
627#ifdef DEBUG_LOCKCALL
628 memset(logCaller, 0, sizeof(logCaller));
629#endif
630 logIndex = 0;
631 dumped = false;
632}
633
634void cStateLockLog::Dump(const char *Name, tThreadId ThreadId)
635{
636 dsyslog("--- begin invalid lock sequence report");
637 dsyslog("TID T C R DR S ST");
638 int LastFlags = 0;
639 for (int i = 0; i < SLL_SIZE; i++) {
640 if (tThreadId tid = logThreadIds[logIndex]) {
641 char msg[SLL_LENGTH];
642 char *q = msg;
643 q += sprintf(q, "%5d", tid);
644 int Flags = logFlags[logIndex];
645 bool Write = Flags & SLL_WRITE_FLAG;
646 bool Lock = Flags & SLL_LOCK_FLAG;
647 Flags &= ~(SLL_WRITE_FLAG | SLL_LOCK_FLAG);
648 int Changed = LastFlags ^ Flags;
649 LastFlags = Flags;
650 for (int i = 0; i <= SLL_MAX_LIST; i++) {
651 char c = '-';
652 int b = 1 << i;
653 if ((Flags & b) != 0)
654 c = '*';
655 if ((Changed & b) != 0)
656 c = Lock ? Write ? 'W' : 'R' : 'U';
657 q += sprintf(q, " %c", c);
658 }
659 q += sprintf(q, " %c", Lock ? 'L' : 'U');
660#ifdef DEBUG_LOCKCALL
661 if (*logCaller[logIndex]) {
662 *q++ = ' ';
663 strn0cpy(q, *cBackTrace::Demangle(logCaller[logIndex]), sizeof(msg) - (q - msg));
664 }
665#endif
666 dsyslog("%s", msg);
667 }
668 if (++logIndex >= SLL_SIZE)
669 logIndex = 0;
670 }
671 dsyslog("%5d invalid lock sequence: %s", ThreadId, Name);
672 dsyslog("full backtrace:");
673 cBackTrace::BackTrace(NULL, 2);
674 dsyslog("--- end invalid lock sequence report");
675 dsyslog("--- THERE WILL BE NO FURTHER REPORTS UNTIL VDR IS RESTARTED!");
676 fprintf(stderr, "invalid lock sequence at %s\n", *DayDateTime(time(NULL)));
677}
678
679void cStateLockLog::Check(const char *Name, bool Lock, bool Write)
680{
681 if (!dumped && Name) {
682 int n = *Name - '0' - 1;
683 if (0 <= n && n < SLL_MAX_LIST) {
684 int b = 1 << n;
685 cMutexLock MutexLock(&mutex);
686 tThreadId ThreadId = cThread::ThreadId();
687 int Index = -1;
688 int AvailableIndex = -1;
689 for (int i = 0; i < threadIds.Size(); i++) {
690 if (ThreadId == threadIds[i]) {
691 Index = i;
692 break;
693 }
694 if (threadIds[i] == 0)
695 AvailableIndex = i;
696 }
697 if (Index < 0) {
698 if (AvailableIndex < 0) {
699 Index = threadIds.Size();
700 threadIds.Append(ThreadId);
701 flags.Append(0);
702 }
703 else {
704 Index = AvailableIndex;
705 threadIds[Index] = ThreadId;
706 }
707 }
708 if (Index >= SLL_THREADS) {
709 // should never happen!
710 esyslog("ERROR: too many threads holding list locks at the same time - stopped logging locks!");
711 dumped = true;
712 return;
713 }
714 bool DoDump = false;
715 if (Lock) {
716 if ((flags[Index] & ~b) < b) // thread holds only "smaller" locks -> OK
717 ;
718 else if ((flags[Index] & b) == 0) // thread already holds "bigger" locks, so it may only re-lock one that it already has!
719 DoDump = true;
720 logCounter[Index][n]++;
721 flags[Index] |= b;
722 }
723 else if (--logCounter[Index][n] == 0)
724 flags[Index] &= ~b;
725 logThreadIds[logIndex] = ThreadId;
726 logFlags[logIndex] = flags[Index] | (Write ? SLL_WRITE_FLAG : 0) | (Lock ? SLL_LOCK_FLAG : 0);
727 if (flags[Index] == 0)
728 threadIds[Index] = 0;
729#ifdef DEBUG_LOCKCALL
730 strn0cpy(logCaller[logIndex], cBackTrace::GetCaller(Lock ? 3 : 5, true), SLL_LENGTH);
731#endif
732 if (++logIndex >= SLL_SIZE)
733 logIndex = 0;
734 if (DoDump) {
735 Dump(Name, ThreadId);
736 dumped = true;
737 }
738 }
739 }
740}
741
743
744#define dbglockseq(n, l, w) StateLockLog.Check(n, l, w)
745#else
746#define dbglockseq(n, l, w)
747#endif // DEBUG_LOCKSEQ
748
749// --- cStateLock ------------------------------------------------------------
750
751cStateLock::cStateLock(const char *Name)
752:rwLock(true)
753{
754 name = Name;
755 threadId = 0;
756 state = 0;
758 syncStateKey = NULL;
759}
760
761bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
762{
763 dbglocking("%5d %-12s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
764 StateKey.timedOut = false;
765 if (StateKey.stateLock) {
766 esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
767 ABORT;
768 return false;
769 }
770 if (rwLock.Lock(Write, TimeoutMs)) {
771 dbglockseq(name, true, Write);
772 StateKey.stateLock = this;
773 if (Write) {
774 dbglocking("%5d %-12s %10p locked write\n", cThread::ThreadId(), name, &StateKey);
776 StateKey.write = true;
777 return true;
778 }
779 else if (state != StateKey.state) {
780 dbglocking("%5d %-12s %10p locked read\n", cThread::ThreadId(), name, &StateKey);
781 return true;
782 }
783 else {
784 dbglocking("%5d %-12s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey);
785 StateKey.stateLock = NULL;
786 dbglockseq(name, false, false);
787 rwLock.Unlock();
788 }
789 }
790 else if (TimeoutMs) {
791 dbglocking("%5d %-12s %10p timeout\n", cThread::ThreadId(), name, &StateKey);
792 StateKey.timedOut = true;
793 }
794 else if (threadId == cThread::ThreadId()) {
795 static bool DoubleWriteLockReported = false;
796 if (!DoubleWriteLockReported) {
797 dsyslog("WARNING: attempt to acquire write lock while already holding a write lock in the same thread - this may crash! (backtrace follows)");
799 DoubleWriteLockReported = true;
800 }
801 }
802 return false;
803}
804
805void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
806{
807 dbglocking("%5d %-12s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
808 if (StateKey.stateLock != this) {
809 esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
810 ABORT;
811 return;
812 }
813 if (StateKey.write && threadId != cThread::ThreadId()) {
814 esyslog("ERROR: cStateLock::Unlock() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
815 ABORT;
816 return;
817 }
818 if (StateKey.write && (IncState && explicitModify != emArmed || explicitModify == emEnabled)) {
821 state++;
822 }
823 StateKey.state = state;
824 StateKey.stateLock = NULL;
825 if (StateKey.write) {
826 StateKey.write = false;
827 threadId = 0;
829 syncStateKey = NULL;
830 }
831 dbglockseq(name, false, false);
832 rwLock.Unlock();
833}
834
836{
837 dbglocking("%5d %-12s %10p SetSyncStateKey\n", cThread::ThreadId(), name, &StateKey);
838 if (threadId != cThread::ThreadId()) {
839 esyslog("ERROR: cStateLock::SetSyncStateKey() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
840 ABORT;
841 return;
842 }
843 if (StateKey.stateLock == this) {
844 esyslog("ERROR: cStateLock::SetSyncStateKey() called with locked key (tid=%d, lock=%s)", threadId, name);
845 ABORT;
846 return;
847 }
848 if (syncStateKey) {
849 esyslog("ERROR: cStateLock::SetSyncStateKey() called twice (tid=%d, lock=%s)", threadId, name);
850 ABORT;
851 return;
852 }
853 syncStateKey = &StateKey;
854}
855
857{
858 if (threadId != cThread::ThreadId()) {
859 esyslog("ERROR: cStateLock::SetExplicitModify() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
860 ABORT;
861 return;
862 }
863 if (explicitModify != emDisabled) {
864 esyslog("ERROR: cStateLock::SetExplicitModify() called twice (tid=%d, lock=%s)", threadId, name);
865 ABORT;
866 return;
867 }
869}
870
872{
873 if (threadId != cThread::ThreadId()) {
874 esyslog("ERROR: cStateLock::SetModified() called without holding a write lock (tid=%d, lock=%s)", threadId, name);
875 ABORT;
876 return;
877 }
879}
880
881// --- cStateKey -------------------------------------------------------------
882
883cStateKey::cStateKey(bool IgnoreFirst)
884{
885 stateLock = NULL;
886 write = false;
887 state = 0;
888 if (!IgnoreFirst)
889 Reset();
890}
891
893{
894 if (stateLock) {
895 esyslog("ERROR: cStateKey::~cStateKey() called without releasing the lock first (tid=%d, lock=%s, key=%p)", stateLock->threadId, stateLock->name, this);
896 ABORT;
897 }
898}
899
901{
902 state = -1; // lock and key are initialized differently, to make the first check return true
903}
904
905void cStateKey::Remove(bool IncState)
906{
907 if (stateLock)
908 stateLock->Unlock(*this, IncState);
909 else {
910 esyslog("ERROR: cStateKey::Remove() called without holding a lock (key=%p)", this);
911 ABORT;
912 }
913}
914
916{
917 if (!stateLock) {
918 esyslog("ERROR: cStateKey::StateChanged() called without holding a lock (tid=%d, key=%p)", cThread::ThreadId(), this);
919 ABORT;
920 }
921 else if (write)
922 return state != stateLock->state;
923 else
924 return true;
925}
926
927// --- cIoThrottle -----------------------------------------------------------
928
930std::atomic<int> cIoThrottle::count(0);
931
933{
934 active = false;
935}
936
941
943{
944 mutex.Lock();
945 if (!active) {
946 count++;
947 active = true;
948 dsyslog("i/o throttle activated, count = %d (tid=%d)", count.load(), cThread::ThreadId());
949 }
950 mutex.Unlock();
951}
952
954{
955 mutex.Lock();
956 if (active) {
957 count--;
958 active = false;
959 dsyslog("i/o throttle released, count = %d (tid=%d)", count.load(), cThread::ThreadId());
960 }
961 mutex.Unlock();
962}
963
965{
966 return count > 0;
967}
968
969// --- cPipe -----------------------------------------------------------------
970
971// cPipe::Open() and cPipe::Close() are based on code originally received from
972// Andreas Vitting <Andreas@huji.de>
973
975{
976 pid = -1;
977 f = NULL;
978}
979
981{
982 Close();
983}
984
985bool cPipe::Open(const char *Command, const char *Mode)
986{
987 int fd[2];
988
989 if (pipe(fd) < 0) {
990 LOG_ERROR_STR(Command);
991 return false;
992 }
993 if ((pid = fork()) < 0) { // fork failed
994 LOG_ERROR_STR(Command);
995 close(fd[0]);
996 close(fd[1]);
997 return false;
998 }
999
1000 const char *mode = "w";
1001 int iopipe = 0;
1002
1003 if (pid > 0) { // parent process
1004 if (strcmp(Mode, "r") == 0) {
1005 mode = "r";
1006 iopipe = 1;
1007 }
1008 close(fd[iopipe]);
1009 if ((f = fdopen(fd[1 - iopipe], mode)) == NULL) {
1010 LOG_ERROR_STR(Command);
1011 close(fd[1 - iopipe]);
1012 }
1013 return f != NULL;
1014 }
1015 else { // child process
1016 int iofd = STDOUT_FILENO;
1017 if (strcmp(Mode, "w") == 0) {
1018 iopipe = 1;
1019 iofd = STDIN_FILENO;
1020 }
1021 close(fd[iopipe]);
1022 if (dup2(fd[1 - iopipe], iofd) == -1) { // now redirect
1023 close(fd[1 - iopipe]);
1024 _exit(-1);
1025 }
1026 else {
1027 int MaxPossibleFileDescriptors = getdtablesize();
1028 for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
1029 close(i); //close all dup'ed filedescriptors
1030 if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1) {
1031 close(fd[1 - iopipe]);
1032 _exit(-1);
1033 }
1034 }
1035 _exit(0);
1036 }
1037}
1038
1040{
1041 int ret = -1;
1042
1043 if (f) {
1044 fclose(f);
1045 f = NULL;
1046 }
1047
1048 if (pid > 0) {
1049 int status = 0;
1050 int i = 5;
1051 while (i > 0) {
1052 ret = waitpid(pid, &status, WNOHANG);
1053 if (ret < 0) {
1054 if (errno != EINTR && errno != ECHILD) {
1055 LOG_ERROR;
1056 break;
1057 }
1058 }
1059 else if (ret == pid)
1060 break;
1061 i--;
1062 cCondWait::SleepMs(100);
1063 }
1064 if (!i) {
1065 kill(pid, SIGKILL);
1066 ret = -1;
1067 }
1068 else if (ret == -1 || !WIFEXITED(status))
1069 ret = -1;
1070 pid = -1;
1071 }
1072
1073 return ret;
1074}
1075
1076// --- SystemExec ------------------------------------------------------------
1077
1078int SystemExec(const char *Command, bool Detached)
1079{
1080 pid_t pid;
1081
1082 if ((pid = fork()) < 0) { // fork failed
1083 LOG_ERROR_STR(Command);
1084 return -1;
1085 }
1086
1087 if (pid > 0) { // parent process
1088 int status = 0;
1089 if (waitpid(pid, &status, 0) < 0) {
1090 LOG_ERROR_STR(Command);
1091 return -1;
1092 }
1093 return status;
1094 }
1095 else { // child process
1096 if (Detached) {
1097 // Fork again and let first child die - grandchild stays alive without parent
1098 if (fork() > 0)
1099 _exit(0);
1100 // Start a new session
1101 pid_t sid = setsid();
1102 if (sid < 0)
1103 _exit(-1);
1104 // close STDIN and re-open as /dev/null
1105 int devnull = open("/dev/null", O_RDONLY);
1106 if (devnull < 0 || dup2(devnull, 0) < 0)
1107 _exit(-1);
1108 }
1109 int MaxPossibleFileDescriptors = getdtablesize();
1110 for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
1111 close(i); //close all dup'ed filedescriptors
1112 if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1)
1113 _exit(-1);
1114 _exit(0);
1115 }
1116}
static void BackTrace(cStringList &StringList, int Level=0, bool Mangled=false)
Produces a backtrace and stores it in the given StringList.
Definition thread.c:556
static cString GetCaller(int Level=0, bool Mangled=false)
Returns the caller at the given Level (or the immediate caller, if Level is 0).
Definition thread.c:579
static cString Demangle(char *s)
Demangles the function name in the given string and returns the converted version of s.
Definition thread.c:478
void Wait(cMutex &Mutex)
Definition thread.c:122
cCondVar(void)
Definition thread.c:111
bool TimedWait(cMutex &Mutex, int TimeoutMs)
Definition thread.c:135
void Broadcast(void)
Definition thread.c:155
pthread_cond_t cond
Definition thread.h:47
~cCondVar()
Definition thread.c:116
pthread_cond_t cond
Definition thread.h:23
bool signaled
Definition thread.h:24
cCondWait(void)
Definition thread.c:59
~cCondWait()
Definition thread.c:66
bool Wait(int TimeoutMs=0)
Waits at most TimeoutMs milliseconds for a call to Signal(), or forever if TimeoutMs is 0.
Definition thread.c:79
void Signal(void)
Signals a caller of Wait() that the condition it is waiting for is met.
Definition thread.c:101
static void SleepMs(int TimeoutMs)
Creates a cCondWait object and uses it to sleep for TimeoutMs milliseconds, immediately giving up the...
Definition thread.c:73
pthread_mutex_t mutex
Definition thread.h:22
cIoThrottle(void)
Definition thread.c:932
static std::atomic< int > count
Definition thread.h:284
void Activate(void)
Activates the global I/O throttling mechanism.
Definition thread.c:942
~cIoThrottle()
Definition thread.c:937
void Release(void)
Releases the global I/O throttling mechanism.
Definition thread.c:953
bool active
Definition thread.h:285
static bool Engaged(void)
Returns true if any I/O throttling object is currently active.
Definition thread.c:964
static cMutex mutex
Definition thread.h:283
cMutexLock(cMutex *Mutex=NULL)
Definition thread.c:423
~cMutexLock()
Definition thread.c:430
cMutex * mutex
Definition thread.h:152
bool Lock(cMutex *Mutex, int TimeoutMs=0, bool *TimedOut=NULL)
Returns true if the mutex was successfully locked.
Definition thread.c:436
bool locked
Definition thread.h:153
bool Lock(int TimeoutMs=0, bool *TimedOut=NULL)
Returns true if the mutex was successfully locked.
Definition thread.c:228
std::atomic< tThreadId > lockThreadId
Definition thread.h:72
pthread_mutex_t mutex
Definition thread.h:71
cMutex(void)
Definition thread.c:213
~cMutex()
Definition thread.c:223
int locked
Definition thread.h:73
void Unlock(void)
Definition thread.c:252
pid_t pid
Definition thread.h:308
int Close(void)
Definition thread.c:1039
FILE * f
Definition thread.h:309
bool Open(const char *Command, const char *Mode)
Definition thread.c:985
cPipe(void)
Definition thread.c:974
~cPipe()
Definition thread.c:980
char * Read(FILE *f)
Definition tools.c:1574
int locked
Definition thread.h:59
pthread_rwlock_t rwlock
Definition thread.h:58
cRwLock(bool PreferWriter=false)
Definition thread.c:162
std::atomic< tThreadId > writeLockThreadId
Definition thread.h:60
bool Lock(bool Write, int TimeoutMs=0)
Definition thread.c:177
void Unlock(void)
Definition thread.c:199
~cRwLock()
Definition thread.c:172
cStateLock * stateLock
Definition thread.h:250
cStateKey(bool IgnoreFirst=false)
Sets up a new state key.
Definition thread.c:883
int state
Definition thread.h:252
void Remove(bool IncState=true)
Removes this key from the lock it was previously used with.
Definition thread.c:905
~cStateKey()
Definition thread.c:892
void Reset(void)
Resets the state of this key, so that the next call to a lock's Lock() function with this key will re...
Definition thread.c:900
bool timedOut
Definition thread.h:253
bool write
Definition thread.h:251
bool StateChanged(void)
Returns true if this key is used for obtaining a write lock, and the lock's state differs from that o...
Definition thread.c:915
cVector< int > flags
Definition thread.c:607
cVector< tThreadId > threadIds
Definition thread.c:606
cStateLockLog(void)
Definition thread.c:622
uint8_t logCounter[SLL_THREADS][SLL_MAX_LIST]
Definition thread.c:610
int logFlags[SLL_SIZE]
Definition thread.c:609
void Check(const char *Name, bool Lock, bool Write=false)
Definition thread.c:679
tThreadId logThreadIds[SLL_SIZE]
Definition thread.c:608
bool dumped
Definition thread.c:615
void Dump(const char *Name, tThreadId ThreadId)
Definition thread.c:634
cMutex mutex
Definition thread.c:605
tThreadId threadId
Definition thread.h:190
const char * name
Definition thread.h:189
cRwLock rwLock
Definition thread.h:191
int state
Definition thread.h:192
void SetExplicitModify(void)
If you have obtained a write lock on this lock, and you don't want its state to be automatically incr...
Definition thread.c:856
cStateLock(const char *Name=NULL)
Definition thread.c:751
friend class cStateKey
Definition thread.h:186
cStateKey * syncStateKey
Definition thread.h:194
int explicitModify
Definition thread.h:193
void Unlock(cStateKey &StateKey, bool IncState=true)
Releases a lock that has been obtained by a previous call to Lock() with the given StateKey.
Definition thread.c:805
void SetSyncStateKey(cStateKey &StateKey)
Sets the given StateKey to be synchronized to the state of this lock.
Definition thread.c:835
void SetModified(void)
Sets this lock to have its state incremented when the current write lock state key is removed.
Definition thread.c:871
bool Lock(cStateKey &StateKey, bool Write=false, int TimeoutMs=0)
Tries to get a lock and returns true if successful.
Definition thread.c:761
@ emDisabled
Definition thread.h:188
@ emEnabled
Definition thread.h:188
static cString static cString vsprintf(const char *fmt, va_list &ap)
Definition tools.c:1255
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition tools.c:1242
cThreadLock(cThread *Thread=NULL)
Definition thread.c:450
bool Lock(cThread *Thread)
Definition thread.c:463
bool locked
Definition thread.h:174
~cThreadLock()
Definition thread.c:457
cThread * thread
Definition thread.h:173
virtual ~cThread()
Definition thread.c:275
void SetIOPriority(int Priority)
Definition thread.c:287
void Unlock(void)
Definition thread.h:104
std::atomic< tThreadId > childThreadId
Definition thread.h:94
static void SetMainThreadId(void)
Definition thread.c:413
virtual void Action(void)=0
A derived cThread class must implement the code it wants to execute as a separate thread in this func...
void bool Start(void)
Sets the description of this thread, which will be used when logging starting or stopping of the thre...
Definition thread.c:330
void SetDescription(const char *Description,...) __attribute__((format(printf
Definition thread.c:293
std::atomic< bool > active
Definition thread.h:91
void SetPriority(int Priority)
Definition thread.c:281
void Lock(void)
Definition thread.h:103
cThread(const char *Description=NULL, bool LowPriority=false)
Creates a new thread.
Definition thread.c:264
bool lowPriority
Definition thread.h:97
static void * StartThread(cThread *Thread)
Definition thread.c:305
void Cancel(int WaitSeconds=0)
Cancels the thread by first setting 'running' to false, so that the Action() loop can finish in an or...
Definition thread.c:381
std::atomic< bool > running
Definition thread.h:92
static tThreadId mainThreadId
Definition thread.h:98
pthread_t childTid
Definition thread.h:93
bool Active(void)
Checks whether the thread is still alive.
Definition thread.c:355
static tThreadId ThreadId(void)
Definition thread.c:400
char * description
Definition thread.h:96
uint64_t Elapsed(void) const
Returns the number of milliseconds that have elapsed since the last call to Set().
Definition tools.c:855
bool TimedOut(void) const
Returns true if the number of milliseconds given in the last call to Set() have passed.
Definition tools.c:850
int Size(void) const
Definition tools.h:773
virtual void Append(T Data)
Definition tools.h:794
#define BT_BUF_SIZE
Definition thread.c:476
static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
Definition thread.c:39
#define SLL_LENGTH
Definition thread.c:597
#define dbglockseq(n, l, w)
Definition thread.c:744
#define ABORT
Definition thread.c:27
#define SLL_MAX_LIST
Definition thread.c:599
#define THREAD_STOP_SLEEP
Definition thread.c:328
#define SLL_SIZE
Definition thread.c:596
int SystemExec(const char *Command, bool Detached)
Definition thread.c:1078
#define SLL_THREADS
Definition thread.c:598
#define SLL_LOCK_FLAG
Definition thread.c:601
#define SLL_WRITE_FLAG
Definition thread.c:600
#define THREAD_STOP_TIMEOUT
Definition thread.c:327
static cStateLockLog StateLockLog
Definition thread.c:742
#define dbglocking(a...)
Definition thread.c:36
pid_t tThreadId
Definition thread.h:18
cString DayDateTime(time_t t)
Converts the given time to a string of the form "www dd.mm. hh:mm".
Definition tools.c:1307
char * strn0cpy(char *dest, const char *src, size_t n)
Definition tools.c:128
#define LOG_ERROR_STR(s)
Definition tools.h:40
#define dsyslog(a...)
Definition tools.h:37
T max(T a, T b)
Definition tools.h:64
#define esyslog(a...)
Definition tools.h:35
#define LOG_ERROR
Definition tools.h:39