Skip to content

Commit 64ebfcb

Browse files
committed
Refactor
1 parent 6856b0d commit 64ebfcb

File tree

2 files changed

+65
-33
lines changed

2 files changed

+65
-33
lines changed

src/Grpc.Net.Client/Internal/GrpcCall.cs

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,7 @@ public void Dispose()
186186

187187
// If the call was disposed then observe any potential response exception
188188
// Observe the task's exception to prevent TaskScheduler.UnobservedTaskException from firing.
189-
if (_responseTcs != null)
190-
{
191-
ObserveException(_responseTcs.Task);
192-
}
189+
_responseTcs?.Task.ObserveException();
193190
}
194191
}
195192

@@ -329,10 +326,7 @@ private async Task<Metadata> GetResponseHeadersCoreAsync()
329326
// If there was an error fetching response headers then it's likely the same error is reported
330327
// by response TCS. The user is unlikely to observe both errors.
331328
// Observe the task's exception to prevent TaskScheduler.UnobservedTaskException from firing.
332-
if (_responseTcs != null)
333-
{
334-
ObserveException(_responseTcs.Task);
335-
}
329+
_responseTcs?.Task.ObserveException();
336330

337331
if (ResolveException(ErrorStartingCallMessage, ex, out _, out var resolvedException))
338332
{
@@ -606,43 +600,30 @@ private async Task RunCall(HttpRequestMessage request, TimeSpan? timeout)
606600

607601
// Update HTTP response TCS before clean up. Needs to happen first because cleanup will
608602
// cancel the TCS for anyone still listening.
609-
SetTcsException(_httpResponseTcs, resolvedException, observeException: true);
603+
_httpResponseTcs.TrySetException(resolvedException);
604+
_httpResponseTcs.Task.ObserveException();
610605

611606
Cleanup(status.Value);
612607

613608
// Update response TCS after overall call status is resolved. This is required so that
614609
// the call is completed before an error is thrown from ResponseAsync. If it happens
615610
// afterwards then there is a chance GetStatus() will error because the call isn't complete.
616-
SetTcsException(_responseTcs, resolvedException, observeException: IsCancellationOrDeadlineException(ex));
611+
if (_responseTcs != null)
612+
{
613+
_responseTcs.TrySetException(resolvedException);
614+
615+
// Always observe cancellation-like exceptions.
616+
if (IsCancellationOrDeadlineException(ex))
617+
{
618+
_responseTcs.Task.ObserveException();
619+
}
620+
}
617621
}
618622

619623
// Verify that FinishCall is called in every code path of this method.
620624
// Should create an "Unassigned variable" compiler error if not set.
621625
Debug.Assert(finished);
622626
}
623-
624-
static void SetTcsException<T>(TaskCompletionSource<T>? tcs, Exception ex, bool observeException)
625-
{
626-
if (tcs != null)
627-
{
628-
tcs.TrySetException(ex);
629-
630-
// It's possible the TCS is never awaited.
631-
// Observe the task's exception to prevent TaskScheduler.UnobservedTaskException from firing.
632-
if (observeException)
633-
{
634-
ObserveException(tcs.Task);
635-
}
636-
}
637-
}
638-
}
639-
640-
private static void ObserveException(Task task)
641-
{
642-
if (task.IsFaulted)
643-
{
644-
_ = task.Exception;
645-
}
646627
}
647628

648629
private bool IsCancellationOrDeadlineException(Exception ex)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
namespace Grpc.Net.Client.Internal
20+
{
21+
internal static class TaskExtensions
22+
{
23+
private static readonly Action<Task> IgnoreTaskContinuation = t => { _ = t.Exception; };
24+
25+
/// <summary>
26+
/// Observes and ignores a potential exception on a given Task.
27+
/// If a Task fails and throws an exception which is never observed, it will be caught by the .NET finalizer thread.
28+
/// This function awaits the given task and if the exception is thrown, it observes this exception and simply ignores it.
29+
/// This will prevent the escalation of this exception to the .NET finalizer thread.
30+
/// </summary>
31+
/// <param name="task">The task to be ignored.</param>
32+
public static void ObserveException(this Task task)
33+
{
34+
if (task.IsCompleted)
35+
{
36+
if (task.IsFaulted)
37+
{
38+
_ = task.Exception;
39+
}
40+
}
41+
else
42+
{
43+
task.ContinueWith(
44+
IgnoreTaskContinuation,
45+
CancellationToken.None,
46+
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
47+
TaskScheduler.Default);
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)