mirror of
https://github.com/VDR4Arch/vdr.git
synced 2023-10-10 13:36:52 +02:00
Added debug output for checking the correct sequence of locking global lists
This commit is contained in:
parent
7cdd4877c3
commit
0af3ed548c
@ -3424,6 +3424,8 @@ Jasmin Jessich <jasmin@anw.at>
|
|||||||
debugging MTD support
|
debugging MTD support
|
||||||
for fixing selecting delivery system names in case of undefined indexes
|
for fixing selecting delivery system names in case of undefined indexes
|
||||||
for fixing detecting the inclusion of STL header files in tools.h
|
for fixing detecting the inclusion of STL header files in tools.h
|
||||||
|
for help and suggestions when implementing debug output for checking the correct
|
||||||
|
sequence of locking global lists
|
||||||
|
|
||||||
Martin Schirrmacher <schirrmie@gmail.com>
|
Martin Schirrmacher <schirrmie@gmail.com>
|
||||||
for suggesting to provide a way for skin plugins to get informed about the currently
|
for suggesting to provide a way for skin plugins to get informed about the currently
|
||||||
|
8
HISTORY
8
HISTORY
@ -9055,3 +9055,11 @@ Video Disk Recorder Revision History
|
|||||||
The newplugin script has also been modified accordingly.
|
The newplugin script has also been modified accordingly.
|
||||||
- Fixed detecting the inclusion of STL header files in tools.h (thanks to Jasmin
|
- Fixed detecting the inclusion of STL header files in tools.h (thanks to Jasmin
|
||||||
Jessich).
|
Jessich).
|
||||||
|
|
||||||
|
2017-05-28: Version 2.3.6
|
||||||
|
|
||||||
|
- Added debug output for checking the correct sequence of locking global lists
|
||||||
|
(with help and suggestions from Jasmin Jessich). To activate this, define the
|
||||||
|
macro DEBUG_LOCKSEQ in thread.c. At the first occurrence of an invalid locking
|
||||||
|
sequence, the 20 most recent locks will be printed to stderr, followed by a
|
||||||
|
backtrace that led to the call in question.
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: channels.c 4.3 2015/09/09 10:21:22 kls Exp $
|
* $Id: channels.c 4.4 2017/05/26 15:43:54 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "channels.h"
|
#include "channels.h"
|
||||||
@ -833,7 +833,7 @@ int cChannels::maxChannelNameLength = 0;
|
|||||||
int cChannels::maxShortChannelNameLength = 0;
|
int cChannels::maxShortChannelNameLength = 0;
|
||||||
|
|
||||||
cChannels::cChannels(void)
|
cChannels::cChannels(void)
|
||||||
:cConfig<cChannel>("Channels")
|
:cConfig<cChannel>("2 Channels")
|
||||||
{
|
{
|
||||||
modifiedByUser = 0;
|
modifiedByUser = 0;
|
||||||
}
|
}
|
||||||
|
10
config.h
10
config.h
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: config.h 4.9 2017/04/29 13:33:13 kls Exp $
|
* $Id: config.h 4.10 2017/05/28 12:42:49 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __CONFIG_H
|
#ifndef __CONFIG_H
|
||||||
@ -22,13 +22,13 @@
|
|||||||
|
|
||||||
// VDR's own version number:
|
// VDR's own version number:
|
||||||
|
|
||||||
#define VDRVERSION "2.3.5"
|
#define VDRVERSION "2.3.6"
|
||||||
#define VDRVERSNUM 20305 // Version * 10000 + Major * 100 + Minor
|
#define VDRVERSNUM 20306 // Version * 10000 + Major * 100 + Minor
|
||||||
|
|
||||||
// The plugin API's version number:
|
// The plugin API's version number:
|
||||||
|
|
||||||
#define APIVERSION "2.3.5"
|
#define APIVERSION "2.3.6"
|
||||||
#define APIVERSNUM 20305 // Version * 10000 + Major * 100 + Minor
|
#define APIVERSNUM 20306 // Version * 10000 + Major * 100 + Minor
|
||||||
|
|
||||||
// When loading plugins, VDR searches them by their APIVERSION, which
|
// When loading plugins, VDR searches them by their APIVERSION, which
|
||||||
// may be smaller than VDRVERSION in case there have been no changes to
|
// may be smaller than VDRVERSION in case there have been no changes to
|
||||||
|
4
epg.c
4
epg.c
@ -7,7 +7,7 @@
|
|||||||
* Original version (as used in VDR before 1.3.0) written by
|
* Original version (as used in VDR before 1.3.0) written by
|
||||||
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
* Robert Schneider <Robert.Schneider@web.de> and Rolf Hakenes <hakenes@hippomi.de>.
|
||||||
*
|
*
|
||||||
* $Id: epg.c 4.6 2017/05/09 12:16:36 kls Exp $
|
* $Id: epg.c 4.7 2017/05/28 11:30:32 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "epg.h"
|
#include "epg.h"
|
||||||
@ -1225,7 +1225,7 @@ char *cSchedules::epgDataFileName = NULL;
|
|||||||
time_t cSchedules::lastDump = time(NULL);
|
time_t cSchedules::lastDump = time(NULL);
|
||||||
|
|
||||||
cSchedules::cSchedules(void)
|
cSchedules::cSchedules(void)
|
||||||
:cList<cSchedule>("Schedules")
|
:cList<cSchedule>("5 Schedules")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: recording.c 4.8 2017/04/03 13:34:30 kls Exp $
|
* $Id: recording.c 4.9 2017/05/27 15:46:57 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "recording.h"
|
#include "recording.h"
|
||||||
@ -1469,7 +1469,7 @@ cVideoDirectoryScannerThread *cRecordings::videoDirectoryScannerThread = NULL;
|
|||||||
time_t cRecordings::lastUpdate = 0;
|
time_t cRecordings::lastUpdate = 0;
|
||||||
|
|
||||||
cRecordings::cRecordings(bool Deleted)
|
cRecordings::cRecordings(bool Deleted)
|
||||||
:cList<cRecording>(Deleted ? "DelRecs" : "Recordings")
|
:cList<cRecording>(Deleted ? "4 DelRecs" : "3 Recordings")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
175
thread.c
175
thread.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: thread.c 4.2 2016/12/08 10:18:32 kls Exp $
|
* $Id: thread.c 4.3 2017/05/28 12:41:03 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
@ -24,6 +24,7 @@
|
|||||||
#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem
|
#define ABORT { dsyslog("ABORT!"); abort(); } // use debugger to trace back the problem
|
||||||
|
|
||||||
//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
|
//#define DEBUG_LOCKING // uncomment this line to activate debug output for locking
|
||||||
|
//#define DEBUG_LOCKSEQ // uncomment this line to activate debug output for locking sequence
|
||||||
|
|
||||||
#ifdef DEBUG_LOCKING
|
#ifdef DEBUG_LOCKING
|
||||||
#define dbglocking(a...) fprintf(stderr, a)
|
#define dbglocking(a...) fprintf(stderr, a)
|
||||||
@ -431,6 +432,163 @@ bool cThreadLock::Lock(cThread *Thread)
|
|||||||
|
|
||||||
// --- cStateLock ------------------------------------------------------------
|
// --- cStateLock ------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef DEBUG_LOCKSEQ
|
||||||
|
#include <cxxabi.h>
|
||||||
|
#include <execinfo.h>
|
||||||
|
static cVector<tThreadId> StateLockThreadIds;
|
||||||
|
static cVector<int> StateLockFlags;
|
||||||
|
static cMutex StateLockMutex;
|
||||||
|
#define SLL_SIZE 20 // the number of log entries
|
||||||
|
#define SLL_LENGTH 256 // the maximum length of log entries
|
||||||
|
#define SLL_DELIM '#' // delimiter for caller info
|
||||||
|
static char StateLockLog[SLL_SIZE][SLL_LENGTH] = { 0 };
|
||||||
|
static int StateLockLogIndex = 0;
|
||||||
|
static bool DumpStateLocks = true;
|
||||||
|
#define BT_BUF_SIZE 100
|
||||||
|
|
||||||
|
static cString Demangle(char *s, char *Prefix = NULL)
|
||||||
|
{
|
||||||
|
char *Module = s;
|
||||||
|
char *Function = NULL;
|
||||||
|
//char *Offset = NULL;
|
||||||
|
char *Address = NULL;
|
||||||
|
// separate the string:
|
||||||
|
for (char *q = Module; *q; q++) {
|
||||||
|
if (*q == '(') {
|
||||||
|
*q = 0;
|
||||||
|
Function = q + 1;
|
||||||
|
}
|
||||||
|
else if (*q == '+') {
|
||||||
|
*q = 0;
|
||||||
|
//Offset = q + 1;
|
||||||
|
}
|
||||||
|
else if (*q == ')')
|
||||||
|
*q = 0;
|
||||||
|
else if (*q == '[')
|
||||||
|
Address = q + 1;
|
||||||
|
else if (*q == ']') {
|
||||||
|
*q = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// demangle the function name:
|
||||||
|
int status;
|
||||||
|
char *r = abi::__cxa_demangle(Function, NULL, 0, &status);
|
||||||
|
if (r)
|
||||||
|
Function = r;
|
||||||
|
cString d = cString::sprintf("%s %s %s", Prefix ? Prefix : "", Module, Function);
|
||||||
|
// determine the file name and line number:
|
||||||
|
cString cmd = cString::sprintf("addr2line --exe=%s --functions --demangle --basename %s", Module, Address);
|
||||||
|
cPipe p;
|
||||||
|
if (p.Open(cmd, "r")) {
|
||||||
|
int n = 0;
|
||||||
|
cReadLine rl;
|
||||||
|
while (char *l = rl.Read(p)) {
|
||||||
|
if (n == 0) {
|
||||||
|
if (!isempty(Function) && strcmp(l, Function))
|
||||||
|
d = cString::sprintf("%s calling %s", *d, l);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d = cString::sprintf("%s at %s", *d, l);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
p.Close();
|
||||||
|
}
|
||||||
|
free(r);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BackTrace(void)
|
||||||
|
{
|
||||||
|
void *b[BT_BUF_SIZE];
|
||||||
|
int n = backtrace(b, BT_BUF_SIZE);
|
||||||
|
if (char **s = backtrace_symbols(b, n)) {
|
||||||
|
for (int i = 5; i < n; i++)
|
||||||
|
fprintf(stderr, "%s\n", *Demangle(s[i]));
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BackTraceCaller(int Level, char *t, int l)
|
||||||
|
{
|
||||||
|
void *b[BT_BUF_SIZE];
|
||||||
|
int n = backtrace(b, BT_BUF_SIZE);
|
||||||
|
if (char **s = backtrace_symbols(b, n)) {
|
||||||
|
strn0cpy(t, s[Level], l);
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DumpStateLockLog(tThreadId ThreadId, const char *Name)
|
||||||
|
{
|
||||||
|
if (!DumpStateLocks)
|
||||||
|
return;
|
||||||
|
DumpStateLocks = false;
|
||||||
|
for (int i = 0; i < SLL_SIZE; i++) {
|
||||||
|
char *s = StateLockLog[StateLockLogIndex];
|
||||||
|
if (*s) {
|
||||||
|
if (char *Delim = strchr(s, SLL_DELIM)) {
|
||||||
|
*Delim = 0;
|
||||||
|
fprintf(stderr, "%s\n", *Demangle(Delim + 1, s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (++StateLockLogIndex >= SLL_SIZE)
|
||||||
|
StateLockLogIndex = 0;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%5d invalid lock sequence: %-12s\n", ThreadId, Name);
|
||||||
|
fprintf(stderr, "full backtrace:\n");
|
||||||
|
BackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CheckStateLockLevel(const char *Name, bool Lock, bool Write = false)
|
||||||
|
{
|
||||||
|
if (Name) {
|
||||||
|
int n = *Name - '0';
|
||||||
|
if (1 <= n && n <= 9) {
|
||||||
|
int b = 1 << (n - 1);
|
||||||
|
cMutexLock MutexLock(&StateLockMutex);
|
||||||
|
tThreadId ThreadId = cThread::ThreadId();
|
||||||
|
int Index = StateLockThreadIds.IndexOf(ThreadId);
|
||||||
|
if (Index < 0) {
|
||||||
|
if (Lock) {
|
||||||
|
Index = StateLockThreadIds.Size();
|
||||||
|
StateLockThreadIds.Append(ThreadId);
|
||||||
|
StateLockFlags.Append(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char *p = StateLockLog[StateLockLogIndex];
|
||||||
|
char *q = p;
|
||||||
|
q += sprintf(q, "%5d", ThreadId);
|
||||||
|
for (int i = 0; i <= 9; i++) {
|
||||||
|
char c = '-';
|
||||||
|
if (StateLockFlags[Index] & (1 << i))
|
||||||
|
c = '*';
|
||||||
|
if (i == n - 1)
|
||||||
|
c = Lock ? Write ? 'W' : 'R' : '-';
|
||||||
|
q += sprintf(q, " %c", c);
|
||||||
|
}
|
||||||
|
q += sprintf(q, " %c%c", Lock ? 'L' : 'U', SLL_DELIM);
|
||||||
|
BackTraceCaller(Lock ? 5 : 3, q, SLL_LENGTH - (q - p));
|
||||||
|
if (++StateLockLogIndex >= SLL_SIZE)
|
||||||
|
StateLockLogIndex = 0;
|
||||||
|
if (Lock) {
|
||||||
|
if ((StateLockFlags[Index] & ~b) < b)
|
||||||
|
StateLockFlags[Index] |= b;
|
||||||
|
else if ((StateLockFlags[Index] & b) == 0)
|
||||||
|
DumpStateLockLog(ThreadId, Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StateLockFlags[Index] &= ~b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#define dbglockseq(n, l, w) CheckStateLockLevel(n, l, w)
|
||||||
|
#else
|
||||||
|
#define dbglockseq(n, l, w)
|
||||||
|
#endif // DEBUG_LOCKSEQ
|
||||||
|
|
||||||
cStateLock::cStateLock(const char *Name)
|
cStateLock::cStateLock(const char *Name)
|
||||||
:rwLock(true)
|
:rwLock(true)
|
||||||
{
|
{
|
||||||
@ -442,7 +600,7 @@ cStateLock::cStateLock(const char *Name)
|
|||||||
|
|
||||||
bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
|
bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
|
||||||
{
|
{
|
||||||
dbglocking("%5d %-10s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
|
dbglocking("%5d %-12s %10p lock state = %d/%d write = %d timeout = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, Write, TimeoutMs);
|
||||||
StateKey.timedOut = false;
|
StateKey.timedOut = false;
|
||||||
if (StateKey.stateLock) {
|
if (StateKey.stateLock) {
|
||||||
esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
|
esyslog("ERROR: StateKey already in use in call to cStateLock::Lock() (tid=%d, lock=%s)", StateKey.stateLock->threadId, name);
|
||||||
@ -450,25 +608,27 @@ bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (rwLock.Lock(Write, TimeoutMs)) {
|
if (rwLock.Lock(Write, TimeoutMs)) {
|
||||||
|
dbglockseq(name, true, Write);
|
||||||
StateKey.stateLock = this;
|
StateKey.stateLock = this;
|
||||||
if (Write) {
|
if (Write) {
|
||||||
dbglocking("%5d %-10s %10p locked write\n", cThread::ThreadId(), name, &StateKey);
|
dbglocking("%5d %-12s %10p locked write\n", cThread::ThreadId(), name, &StateKey);
|
||||||
threadId = cThread::ThreadId();
|
threadId = cThread::ThreadId();
|
||||||
StateKey.write = true;
|
StateKey.write = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (state != StateKey.state) {
|
else if (state != StateKey.state) {
|
||||||
dbglocking("%5d %-10s %10p locked read\n", cThread::ThreadId(), name, &StateKey);
|
dbglocking("%5d %-12s %10p locked read\n", cThread::ThreadId(), name, &StateKey);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dbglocking("%5d %-10s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey);
|
dbglocking("%5d %-12s %10p state unchanged\n", cThread::ThreadId(), name, &StateKey);
|
||||||
StateKey.stateLock = NULL;
|
StateKey.stateLock = NULL;
|
||||||
|
dbglockseq(name, false, false);
|
||||||
rwLock.Unlock();
|
rwLock.Unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (TimeoutMs) {
|
else if (TimeoutMs) {
|
||||||
dbglocking("%5d %-10s %10p timeout\n", cThread::ThreadId(), name, &StateKey);
|
dbglocking("%5d %-12s %10p timeout\n", cThread::ThreadId(), name, &StateKey);
|
||||||
StateKey.timedOut = true;
|
StateKey.timedOut = true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -476,7 +636,7 @@ bool cStateLock::Lock(cStateKey &StateKey, bool Write, int TimeoutMs)
|
|||||||
|
|
||||||
void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
|
void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
|
||||||
{
|
{
|
||||||
dbglocking("%5d %-10s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
|
dbglocking("%5d %-12s %10p unlock state = %d/%d inc = %d\n", cThread::ThreadId(), name, &StateKey, state, StateKey.state, IncState);
|
||||||
if (StateKey.stateLock != this) {
|
if (StateKey.stateLock != this) {
|
||||||
esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
|
esyslog("ERROR: cStateLock::Unlock() called with an unused key (tid=%d, lock=%s)", threadId, name);
|
||||||
ABORT;
|
ABORT;
|
||||||
@ -495,6 +655,7 @@ void cStateLock::Unlock(cStateKey &StateKey, bool IncState)
|
|||||||
threadId = 0;
|
threadId = 0;
|
||||||
explicitModify = false;
|
explicitModify = false;
|
||||||
}
|
}
|
||||||
|
dbglockseq(name, false, false);
|
||||||
rwLock.Unlock();
|
rwLock.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
4
timers.c
4
timers.c
@ -4,7 +4,7 @@
|
|||||||
* See the main source file 'vdr.c' for copyright information and
|
* See the main source file 'vdr.c' for copyright information and
|
||||||
* how to reach the author.
|
* how to reach the author.
|
||||||
*
|
*
|
||||||
* $Id: timers.c 4.9 2017/04/20 09:15:06 kls Exp $
|
* $Id: timers.c 4.10 2017/05/26 15:43:38 kls Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "timers.h"
|
#include "timers.h"
|
||||||
@ -714,7 +714,7 @@ cTimers cTimers::timers;
|
|||||||
int cTimers::lastTimerId = 0;
|
int cTimers::lastTimerId = 0;
|
||||||
|
|
||||||
cTimers::cTimers(void)
|
cTimers::cTimers(void)
|
||||||
:cConfig<cTimer>("Timers")
|
:cConfig<cTimer>("1 Timers")
|
||||||
{
|
{
|
||||||
lastDeleteExpired = 0;
|
lastDeleteExpired = 0;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user