Skip to content

Commit fe18f15

Browse files
committed
Fix race condition in SingleThreadSynchronizationContext (#4468)
Fixes #4465 (cherry picked from commit 4e7f2a7)
1 parent 622c6c5 commit fe18f15

1 file changed

Lines changed: 45 additions & 13 deletions

File tree

src/Npgsql/SingleThreadSynchronizationContext.cs

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
using System;
22
using System.Collections.Concurrent;
3+
using System.Diagnostics;
34
using System.Threading;
45

56
namespace Npgsql
67
{
78
sealed class SingleThreadSynchronizationContext : SynchronizationContext, IDisposable
89
{
910
readonly BlockingCollection<CallbackAndState> _tasks = new BlockingCollection<CallbackAndState>();
10-
Thread? _thread;
11+
readonly object _lockObject = new object();
12+
volatile Thread? _thread;
13+
bool _doingWork;
1114

1215
const int ThreadStayAliveMs = 10000;
1316
readonly string _threadName;
@@ -19,12 +22,16 @@ public override void Post(SendOrPostCallback callback, object? state)
1922
{
2023
_tasks.Add(new CallbackAndState { Callback = callback, State = state });
2124

22-
if (_thread == null)
25+
lock (_lockObject)
2326
{
24-
lock (this)
27+
if (!_doingWork)
2528
{
26-
if (_thread != null)
27-
return;
29+
// Either there is no thread, or the current thread is exiting
30+
// In which case, wait for it to complete
31+
var currentThread = _thread;
32+
currentThread?.Join();
33+
Debug.Assert(_thread is null);
34+
_doingWork = true;
2835
_thread = new Thread(WorkLoop) { Name = _threadName, IsBackground = true };
2936
_thread.Start();
3037
}
@@ -34,12 +41,10 @@ public override void Post(SendOrPostCallback callback, object? state)
3441
public void Dispose()
3542
{
3643
_tasks.CompleteAdding();
37-
_tasks.Dispose();
44+
var thread = _thread;
45+
thread?.Join();
3846

39-
lock (this)
40-
{
41-
_thread?.Join();
42-
}
47+
_tasks.Dispose();
4348
}
4449

4550
void WorkLoop()
@@ -52,13 +57,40 @@ void WorkLoop()
5257
{
5358
var taken = _tasks.TryTake(out var callbackAndState, ThreadStayAliveMs);
5459
if (!taken)
55-
return;
56-
callbackAndState.Callback(callbackAndState.State);
60+
{
61+
lock (_lockObject)
62+
{
63+
if (_tasks.Count == 0)
64+
{
65+
_doingWork = false;
66+
return;
67+
}
68+
}
69+
70+
continue;
71+
}
72+
73+
try
74+
{
75+
Debug.Assert(_doingWork);
76+
callbackAndState.Callback(callbackAndState.State);
77+
}
78+
catch (Exception)
79+
{
80+
// No logging until 5.0
81+
}
5782
}
5883
}
84+
catch (Exception)
85+
{
86+
// Here we attempt to catch any exception coming from BlockingCollection _tasks
87+
lock (_lockObject)
88+
_doingWork = false;
89+
}
5990
finally
6091
{
61-
lock (this) { _thread = null; }
92+
Debug.Assert(!_doingWork);
93+
_thread = null;
6294
}
6395
}
6496

0 commit comments

Comments
 (0)