Skip to content

Commit 73c8d10

Browse files
authored
CSHARP-5618: CSOT: Race condition in ExclusiveConnectionPool could lead to ArgumentOutOfRangeException (#1712)
1 parent 3f60b85 commit 73c8d10

File tree

3 files changed

+46
-13
lines changed

3 files changed

+46
-13
lines changed

src/MongoDB.Driver/OperationContext.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
using System.Diagnostics;
1818
using System.Threading;
1919
using System.Threading.Tasks;
20-
#if !NET6_0_OR_GREATER
2120
using MongoDB.Driver.Core.Misc;
22-
#endif
2321

2422
namespace MongoDB.Driver
2523
{
@@ -36,7 +34,7 @@ public OperationContext(TimeSpan timeout, CancellationToken cancellationToken)
3634
internal OperationContext(Stopwatch stopwatch, TimeSpan timeout, CancellationToken cancellationToken)
3735
{
3836
Stopwatch = stopwatch;
39-
Timeout = timeout;
37+
Timeout = Ensure.IsInfiniteOrGreaterThanOrEqualToZero(timeout, nameof(timeout));
4038
CancellationToken = cancellationToken;
4139
}
4240

@@ -53,7 +51,13 @@ public TimeSpan RemainingTimeout
5351
return System.Threading.Timeout.InfiniteTimeSpan;
5452
}
5553

56-
return Timeout - Stopwatch.Elapsed;
54+
var result = Timeout - Stopwatch.Elapsed;
55+
if (result < TimeSpan.Zero)
56+
{
57+
result = TimeSpan.Zero;
58+
}
59+
60+
return result;
5761
}
5862
}
5963

@@ -69,7 +73,7 @@ public bool IsTimedOut()
6973
return false;
7074
}
7175

72-
return remainingTimeout < TimeSpan.Zero;
76+
return remainingTimeout == TimeSpan.Zero;
7377
}
7478

7579
public void ThrowIfTimedOutOrCanceled()
@@ -141,6 +145,8 @@ public async Task WaitTaskAsync(Task task)
141145

142146
public OperationContext WithTimeout(TimeSpan timeout)
143147
{
148+
Ensure.IsInfiniteOrGreaterThanOrEqualToZero(timeout, nameof(timeout));
149+
144150
var remainingTimeout = RemainingTimeout;
145151
if (timeout == System.Threading.Timeout.InfiniteTimeSpan)
146152
{

tests/MongoDB.Bson.TestHelpers/Threading/ThreadingUtilities.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static void ExecuteOnNewThreads(int threadsCount, Action<int> action, int
2929

3030
if (exceptions.Any())
3131
{
32-
throw exceptions.First();
32+
throw new AggregateException(exceptions);
3333
}
3434
}
3535

tests/MongoDB.Driver.Tests/OperationContextTests.cs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ public void Constructor_should_initialize_properties()
4242
operationContext.ParentContext.Should().BeNull();
4343
}
4444

45+
[Fact]
46+
public void Constructor_throws_on_negative_timeout()
47+
{
48+
var exception = Record.Exception(() => new OperationContext(TimeSpan.FromSeconds(-5), CancellationToken.None));
49+
50+
exception.Should().BeOfType<ArgumentOutOfRangeException>();
51+
}
52+
4553
[Fact]
4654
public void RemainingTimeout_should_calculate()
4755
{
@@ -68,16 +76,12 @@ public void RemainingTimeout_should_return_infinite_for_infinite_timeout()
6876
}
6977

7078
[Fact]
71-
public void RemainingTimeout_could_be_negative()
79+
public void RemainingTimeout_should_return_zero_for_timeout_context()
7280
{
73-
var timeout = TimeSpan.FromMilliseconds(5);
74-
var stopwatch = Stopwatch.StartNew();
81+
var operationContext = new OperationContext(TimeSpan.FromMilliseconds(5), CancellationToken.None);
7582
Thread.Sleep(10);
76-
stopwatch.Stop();
7783

78-
var operationContext = new OperationContext(stopwatch, timeout, CancellationToken.None);
79-
80-
operationContext.RemainingTimeout.Should().Be(timeout - stopwatch.Elapsed);
84+
operationContext.RemainingTimeout.Should().Be(TimeSpan.Zero);
8185
}
8286

8387
[Theory]
@@ -276,6 +280,29 @@ public void WithTimeout_should_set_ParentContext()
276280

277281
resultContext.ParentContext.Should().Be(operationContext);
278282
}
283+
284+
[Fact]
285+
public void WithTimeout_should_create_timed_out_context_on_timed_out_context()
286+
{
287+
var operationContext = new OperationContext(TimeSpan.FromMilliseconds(5), CancellationToken.None);
288+
Thread.Sleep(10);
289+
operationContext.IsTimedOut().Should().BeTrue();
290+
291+
var resultContext = operationContext.WithTimeout(TimeSpan.FromSeconds(10));
292+
293+
resultContext.IsTimedOut().Should().BeTrue();
294+
}
295+
296+
[Fact]
297+
public void WithTimeout_throws_on_negative_timeout()
298+
{
299+
var operationContext = new OperationContext(Timeout.InfiniteTimeSpan, CancellationToken.None);
300+
301+
var exception = Record.Exception(() => operationContext.WithTimeout(TimeSpan.FromSeconds(-5)));
302+
303+
exception.Should().BeOfType<ArgumentOutOfRangeException>()
304+
.Subject.ParamName.Should().Be("timeout");
305+
}
279306
}
280307
}
281308

0 commit comments

Comments
 (0)