Skip to content

Commit f0e231e

Browse files
committed
Added thread deadlock detection module
1 parent 4000d88 commit f0e231e

File tree

7 files changed

+317
-3
lines changed

7 files changed

+317
-3
lines changed

Base/Base.h

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
*
88
***/
99

10+
// Not having access to offsetof causes all sorts of odd compilation errors
11+
// that take time to track down!
12+
#ifndef offsetof
13+
#error Include stddef.h please!
14+
#endif
15+
1016

1117
#include "Macros.h"
1218
#include "Types.h"
@@ -19,6 +25,7 @@
1925
#include "Str.h"
2026
#include "Sync.h"
2127
#include "Task.h"
28+
#include "Thread.h"
2229
#include "Time.h"
2330

2431

Base/Base.vcxproj

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
<ClInclude Include="Sync.h" />
8383
<ClInclude Include="targetver.h" />
8484
<ClInclude Include="Task.h" />
85+
<ClInclude Include="Thread.h" />
8586
<ClInclude Include="Time.h" />
8687
<ClInclude Include="Types.h" />
8788
</ItemGroup>
@@ -97,6 +98,7 @@
9798
<ClCompile Include="Str.cpp" />
9899
<ClCompile Include="Sync.cpp" />
99100
<ClCompile Include="Task.cpp" />
101+
<ClCompile Include="Thread.cpp" />
100102
<ClCompile Include="Time.cpp" />
101103
</ItemGroup>
102104
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

Base/Debug.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ void DebugMsgV (const char fmt[], va_list args) {
6464
}
6565

6666
//=============================================================================
67-
void DebugSetThreadName (const char name[]) {
67+
void DebugSetThreadName (const char name[], unsigned threadId) {
6868
THREADNAME_INFO info;
6969
info.dwType = 0x1000;
7070
info.szName = name;
71-
info.dwThreadID = GetCurrentThreadId();
71+
info.dwThreadID = threadId ? threadId : GetCurrentThreadId();
7272
info.dwFlags = 0;
7373

7474
__try {

Base/Debug.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
void __cdecl DebugMsg (const char fmt[], ...);
1818
void DebugMsgV (const char fmt[], va_list args);
1919

20-
void DebugSetThreadName (const char name[]);
20+
void DebugSetThreadName (const char name[], unsigned threadId = 0);
2121

2222

2323
//===================================

Base/Mem.h

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222
#define ALLOC(bytes) MemAlloc(bytes, __FILE__, __LINE__)
2323
#define REALLOC(ptr, bytes) MemRealloc(ptr, bytes, __FILE__, __LINE__)
2424

25+
// Used to allocate a structure that contains a variable length text
26+
// string at the end. Use StrChars for the "chars" field, not StrLen!
27+
#define SIZEOF_STRUCT_STRING(type, field, chars) ( \
28+
sizeof(type) \
29+
- sizeof(((type *) 0)->field) \
30+
+ sizeof(((type *) 0)->field[0]) * chars \
31+
) //
32+
2533
// Memory allocation
2634
void MemFree (void * ptr);
2735
void * MemAlloc (size_t bytes, const char file[], int line);

Base/Thread.cpp

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/******************************************************************************
2+
*
3+
* Thread.cpp
4+
*
5+
*
6+
* By Patrick Wyatt
7+
*
8+
***/
9+
10+
11+
#include "stdafx.h"
12+
13+
14+
/******************************************************************************
15+
*
16+
* Private
17+
*
18+
***/
19+
20+
struct Thread {
21+
LIST_LINK(Thread) m_link;
22+
unsigned m_id;
23+
HANDLE m_handle;
24+
unsigned m_lastTimeMs;
25+
char * m_name;
26+
27+
Thread (const char name[])
28+
:m_id(0)
29+
,m_handle(NULL)
30+
,m_lastTimeMs(GetTickCount())
31+
,m_name(StrDupAnsi(name))
32+
{}
33+
34+
~Thread() {
35+
MemFree(m_name);
36+
}
37+
};
38+
39+
// Check all threads every minute to ensure they haven't gotten "stuck"
40+
static const unsigned DEADLOCK_CHECK_FREQUENCY_MS = 60 * 1000;
41+
42+
static HANDLE s_deadlockThread;
43+
static HANDLE s_deadlockEvent;
44+
static CCritSect s_critsect;
45+
static LIST_DECLARE(Thread, m_link) s_threads;
46+
47+
48+
//=============================================================================
49+
static void __cdecl Out (const char fmt[], ...) {
50+
va_list args;
51+
va_start(args, fmt);
52+
vprintf(fmt, args);
53+
va_end(args);
54+
}
55+
56+
//=============================================================================
57+
static void LogThread_CS (const Thread & t) {
58+
// TODO: this function is a little... thin. Replace with Matt Pietrek's code:
59+
// http://www.microsoft.com/msj/0497/hood/hood0497.aspx
60+
Out("Thread: %u [%s]\n", t.m_id, t.m_name);
61+
62+
CONTEXT ctx;
63+
ctx.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
64+
if (!GetThreadContext(t.m_handle, &ctx)) {
65+
Out(" ERR: no thread context (%u)\n\n", GetLastError());
66+
}
67+
68+
Out(" EIP: %0x\n\n", ctx.Eip);
69+
}
70+
71+
//=============================================================================
72+
static void LogThreads_CS () {
73+
for (const Thread * t = s_threads.Head(); t; t = t->m_link.Next()) {
74+
SuspendThread(t->m_handle);
75+
LogThread_CS(*t);
76+
ResumeThread(t->m_handle);
77+
}
78+
}
79+
80+
//=============================================================================
81+
static void CheckForDeadlocks_CS () {
82+
unsigned timeMs = GetTickCount();
83+
for (const Thread * t = s_threads.Head(); t; t = t->m_link.Next()) {
84+
// delta might be less than zero because of an inherent
85+
// race condition with ThreadMarkAlive; that's okay, but
86+
// requires that we use a signed comparison below.
87+
signed delta = (signed) (timeMs - t->m_lastTimeMs);
88+
if (delta < (signed) DEADLOCK_CHECK_FREQUENCY_MS)
89+
continue;
90+
91+
// Deadlock!
92+
LogThreads_CS();
93+
DebugBreak();
94+
* (int *) 0 = 0;
95+
break;
96+
}
97+
}
98+
99+
//=============================================================================
100+
static unsigned __stdcall DeadlockThreadProc (void *) {
101+
for (;;) {
102+
DWORD result = WaitForSingleObject(
103+
s_deadlockEvent,
104+
DEADLOCK_CHECK_FREQUENCY_MS
105+
);
106+
if (result != WAIT_TIMEOUT)
107+
break;
108+
109+
s_critsect.Enter();
110+
{
111+
CheckForDeadlocks_CS();
112+
}
113+
s_critsect.Leave();
114+
}
115+
116+
return 0;
117+
}
118+
119+
120+
/******************************************************************************
121+
*
122+
* Public
123+
*
124+
***/
125+
126+
//=============================================================================
127+
void ThreadInit () {
128+
s_deadlockEvent = CreateEvent(NULL, true, false, NULL);
129+
ASSERT(s_deadlockEvent);
130+
131+
unsigned threadId;
132+
s_deadlockThread = (HANDLE) _beginthreadex(
133+
NULL,
134+
0,
135+
DeadlockThreadProc,
136+
NULL,
137+
0,
138+
&threadId
139+
);
140+
ASSERT(s_deadlockThread);
141+
}
142+
143+
//=============================================================================
144+
void ThreadDestroy () {
145+
ASSERT(!s_threads.Head());
146+
147+
if (s_deadlockThread) {
148+
SetEvent(s_deadlockEvent);
149+
WaitForSingleObject(s_deadlockThread, INFINITE);
150+
CloseHandle(s_deadlockThread);
151+
s_deadlockThread = NULL;
152+
}
153+
154+
if (s_deadlockEvent) {
155+
CloseHandle(s_deadlockEvent);
156+
s_deadlockEvent = NULL;
157+
}
158+
}
159+
160+
161+
//=============================================================================
162+
Thread * ThreadCreate (
163+
const char name[],
164+
unsigned stack_size,
165+
unsigned (__stdcall * start_address )( void * ),
166+
void *arglist
167+
) {
168+
Thread * t = new Thread(name);
169+
t->m_handle = (HANDLE) _beginthreadex(
170+
NULL,
171+
stack_size,
172+
start_address,
173+
arglist,
174+
CREATE_SUSPENDED,
175+
&t->m_id
176+
);
177+
ASSERT(t->m_handle);
178+
DebugSetThreadName(name, t->m_id);
179+
180+
s_critsect.Enter();
181+
{
182+
s_threads.InsertTail(t);
183+
}
184+
s_critsect.Leave();
185+
186+
ResumeThread(t->m_handle);
187+
return t;
188+
}
189+
190+
//=============================================================================
191+
void ThreadDestroy (Thread * t) {
192+
WaitForSingleObject(t->m_handle, INFINITE);
193+
194+
s_critsect.Enter();
195+
{
196+
t->m_link.Unlink();
197+
}
198+
s_critsect.Leave();
199+
200+
CloseHandle(t->m_handle);
201+
delete t;
202+
}
203+
204+
//=============================================================================
205+
void ThreadMarkAlive (Thread * thread) {
206+
thread->m_lastTimeMs = GetTickCount();
207+
}
208+
209+
//=============================================================================
210+
void ThreadLogAllThreads () {
211+
s_critsect.Enter();
212+
{
213+
LogThreads_CS();
214+
}
215+
s_critsect.Leave();
216+
}
217+
218+
219+
//===================================
220+
// MIT License
221+
//
222+
// Copyright (c) 2012 by Patrick Wyatt
223+
//
224+
// Permission is hereby granted, free of charge, to any person obtaining a copy
225+
// of this software and associated documentation files (the "Software"), to deal
226+
// in the Software without restriction, including without limitation the rights
227+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
228+
// copies of the Software, and to permit persons to whom the Software is
229+
// furnished to do so, subject to the following conditions:
230+
//
231+
// The above copyright notice and this permission notice shall be included in
232+
// all copies or substantial portions of the Software.
233+
//
234+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
235+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
236+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
237+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
238+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
239+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
240+
// THE SOFTWARE.
241+
//===================================

Base/Thread.h

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/******************************************************************************
2+
*
3+
* Thread.h
4+
*
5+
*
6+
* By Patrick Wyatt
7+
*
8+
***/
9+
10+
11+
struct Thread;
12+
13+
// Module functions
14+
void ThreadInit ();
15+
void ThreadDestroy ();
16+
void ThreadLogAllThreads ();
17+
18+
19+
// Thread functions
20+
Thread * ThreadCreate (
21+
const char name[],
22+
unsigned stack_size,
23+
unsigned (__stdcall * start_address )(void *),
24+
void *arglist
25+
);
26+
void ThreadDestroy (Thread * thread);
27+
28+
// For each thread created with ThreadCreate, call this function
29+
// at least once per minute to mark it alive, otherwise deadlock
30+
// processor will mark it dead and crash the application.
31+
void ThreadMarkAlive (Thread * thread);
32+
33+
34+
//===================================
35+
// MIT License
36+
//
37+
// Copyright (c) 2012 by Patrick Wyatt
38+
//
39+
// Permission is hereby granted, free of charge, to any person obtaining a copy
40+
// of this software and associated documentation files (the "Software"), to deal
41+
// in the Software without restriction, including without limitation the rights
42+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
43+
// copies of the Software, and to permit persons to whom the Software is
44+
// furnished to do so, subject to the following conditions:
45+
//
46+
// The above copyright notice and this permission notice shall be included in
47+
// all copies or substantial portions of the Software.
48+
//
49+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
53+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
54+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
55+
// THE SOFTWARE.
56+
//===================================

0 commit comments

Comments
 (0)