Don't eat exceptions from faulted Tasks.

If explicitly testing for IsCompleted then one must also test for
IsFaulted and if not handling the exception, return the faulted Task.
This commit is contained in:
Jon Hanna
2017-01-10 11:12:46 +00:00
parent dd8d6217a1
commit 82bfde49e0
4 changed files with 152 additions and 29 deletions
@@ -1770,6 +1770,68 @@ null//comment
Assert.IsTrue(writer.WriteValueAsync(default(ushort?), token).IsCanceled);
Assert.IsTrue(writer.WriteWhitespaceAsync(" ", token).IsCanceled);
}
[Test]
public async Task FailureOnStartWriteProperty()
{
JsonTextWriter writer = new JsonTextWriter(new ThrowingWriter(' '));
writer.Formatting = Formatting.Indented;
await writer.WriteStartObjectAsync();
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(async () => await writer.WritePropertyNameAsync("aa"));
}
[Test]
public async Task FailureOnStartWriteObject()
{
JsonTextWriter writer = new JsonTextWriter(new ThrowingWriter('{'));
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(async () => await writer.WriteStartObjectAsync());
}
public class ThrowingWriter : TextWriter
{
// allergic to certain characters, this null-stream writer throws on any attempt to write them.
private char[] _singleCharBuffer = new char[1];
public ThrowingWriter(params char[] throwChars)
{
ThrowChars = throwChars;
}
public char[] ThrowChars { get; set; }
public override Encoding Encoding => Encoding.UTF8;
public override Task WriteAsync(char value)
{
_singleCharBuffer[0] = value;
return WriteAsync(_singleCharBuffer, 0, 1);
}
public override Task WriteAsync(char[] buffer, int index, int count)
{
if (buffer.Skip(index).Take(count).Any(c => ThrowChars.Contains(c)))
{
// Pre-4.6 equivalent to .FromException()
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
tcs.SetException(new InvalidOperationException());
return tcs.Task;
}
return Task.Delay(0);
}
public override Task WriteAsync(string value)
{
return WriteAsync(value.ToCharArray(), 0, value.Length);
}
public override void Write(char value)
{
throw new NotImplementedException();
}
}
}
public class CustomAsyncJsonTextWriter : CustomJsonTextWriter
+65 -21
View File
@@ -186,9 +186,13 @@ namespace Newtonsoft.Json
private Task WriteValueInternalAsync(JsonToken token, string value, CancellationToken cancellationToken)
{
Task task = InternalWriteValueAsync(token, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return _writer.WriteAsync(value, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return _writer.WriteAsync(value, cancellationToken);
}
return WriteValueInternalAsync(task, value, cancellationToken);
@@ -266,9 +270,13 @@ namespace Newtonsoft.Json
private Task WriteIntegerValueAsync(ulong uvalue, bool negative, CancellationToken cancellationToken)
{
Task task = InternalWriteValueAsync(JsonToken.Integer, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return WriteDigitsAsync(uvalue, negative, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return WriteDigitsAsync(uvalue, negative, cancellationToken);
}
return WriteIntegerValueAsync(task, uvalue, negative, cancellationToken);
@@ -317,18 +325,30 @@ namespace Newtonsoft.Json
internal Task DoWritePropertyNameAsync(string name, CancellationToken cancellationToken)
{
Task task = InternalWritePropertyNameAsync(name, cancellationToken);
if (!task.IsCompleted)
switch (task.Status)
{
return DoWritePropertyNameAsync(task, name, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
// Completed synchronously (or very fast!), continue within this rather
// that the default branch which uses async
break;
default:
return DoWritePropertyNameAsync(task, name, cancellationToken);
}
task = WriteEscapedStringAsync(name, _quoteName, cancellationToken);
if (!task.IsCompleted)
switch (task.Status)
{
return JavaScriptUtils.WriteCharAsync(task, _writer, ':', cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return _writer.WriteAsync(':', cancellationToken);
}
return _writer.WriteAsync(':', cancellationToken);
return JavaScriptUtils.WriteCharAsync(task, _writer, ':', cancellationToken);
}
private async Task DoWritePropertyNameAsync(Task task, string name, CancellationToken cancellationToken)
@@ -395,9 +415,13 @@ namespace Newtonsoft.Json
internal Task DoWriteStartArrayAsync(CancellationToken cancellationToken)
{
Task task = InternalWriteStartAsync(JsonToken.StartArray, JsonContainerType.Array, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return _writer.WriteAsync('[', cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return _writer.WriteAsync('[', cancellationToken);
}
return DoWriteStartArrayAsync(task, cancellationToken);
@@ -425,9 +449,13 @@ namespace Newtonsoft.Json
internal Task DoWriteStartObjectAsync(CancellationToken cancellationToken)
{
Task task = InternalWriteStartAsync(JsonToken.StartObject, JsonContainerType.Object, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return _writer.WriteAsync('{', cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return _writer.WriteAsync('{', cancellationToken);
}
return DoWriteStartObjectAsync(task, cancellationToken);
@@ -477,9 +505,13 @@ namespace Newtonsoft.Json
internal Task DoWriteUndefinedAsync(CancellationToken cancellationToken)
{
Task task = InternalWriteValueAsync(JsonToken.Undefined, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return _writer.WriteAsync(JsonConvert.Undefined, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return _writer.WriteAsync(JsonConvert.Undefined, cancellationToken);
}
return DoWriteUndefinedAsync(task, cancellationToken);
@@ -1054,9 +1086,13 @@ namespace Newtonsoft.Json
internal Task DoWriteValueAsync(string value, CancellationToken cancellationToken)
{
Task task = InternalWriteValueAsync(JsonToken.String, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return value == null ? _writer.WriteAsync(JsonConvert.Null, cancellationToken) : WriteEscapedStringAsync(value, true, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return value == null ? _writer.WriteAsync(JsonConvert.Null, cancellationToken) : WriteEscapedStringAsync(value, true, cancellationToken);
}
return DoWriteValueAsync(task, value, cancellationToken);
@@ -1189,9 +1225,13 @@ namespace Newtonsoft.Json
internal Task WriteValueNotNullAsync(Uri value, CancellationToken cancellationToken)
{
Task task = InternalWriteValueAsync(JsonToken.String, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return WriteEscapedStringAsync(value.OriginalString, true, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return WriteEscapedStringAsync(value.OriginalString, true, cancellationToken);
}
return WriteValueNotNullAsync(task, value, cancellationToken);
@@ -1310,9 +1350,13 @@ namespace Newtonsoft.Json
{
UpdateScopeWithFinishedValue();
Task task = AutoCompleteAsync(JsonToken.Undefined, cancellationToken);
if (task.IsCompleted)
switch (task.Status)
{
return WriteRawAsync(json, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return WriteRawAsync(json, cancellationToken);
}
return DoWriteRawValueAsync(task, json, cancellationToken);
+7 -3
View File
@@ -44,12 +44,16 @@ namespace Newtonsoft.Json.Linq
public override Task WriteToAsync(JsonWriter writer, CancellationToken cancellationToken, params JsonConverter[] converters)
{
Task task = writer.WritePropertyNameAsync(_name, cancellationToken);
if (!task.IsCompleted)
switch (task.Status)
{
return WriteToAsync(task, writer, cancellationToken, converters);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return WriteValueAsync(writer, cancellationToken, converters);
}
return WriteValueAsync(writer, cancellationToken, converters);
return WriteToAsync(task, writer, cancellationToken, converters);
}
private async Task WriteToAsync(Task task, JsonWriter writer, CancellationToken cancellationToken, params JsonConverter[] converters)
@@ -381,21 +381,34 @@ namespace Newtonsoft.Json.Utilities
bool[] charEscapeFlags, StringEscapeHandling stringEscapeHandling, JsonTextWriter client, char[] writeBuffer, CancellationToken cancellationToken)
{
Task task = writer.WriteAsync(delimiter, cancellationToken);
if (!task.IsCompleted)
switch (task.Status)
{
return WriteEscapedJavaScriptStringWithDelimitersAsync(task, writer, s, delimiter, charEscapeFlags, stringEscapeHandling, client, writeBuffer, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
// Completed synchronously (or very fast!), continue within this rather
// that the default branch which uses async
break;
default:
return WriteEscapedJavaScriptStringWithDelimitersAsync(task, writer, s, delimiter, charEscapeFlags, stringEscapeHandling, client, writeBuffer, cancellationToken);
}
if (!string.IsNullOrEmpty(s))
{
task = WriteEscapedJavaScriptStringWithoutDelimitersAsync(writer, s, charEscapeFlags, stringEscapeHandling, client, writeBuffer, cancellationToken);
if (!task.IsCompleted)
switch (task.Status)
{
return WriteCharAsync(task, writer, delimiter, cancellationToken);
case TaskStatus.Canceled:
case TaskStatus.Faulted:
return task;
case TaskStatus.RanToCompletion:
return writer.WriteAsync(delimiter, cancellationToken);
}
}
return writer.WriteAsync(delimiter, cancellationToken);
return WriteCharAsync(task, writer, delimiter, cancellationToken);
}
private static async Task WriteEscapedJavaScriptStringWithDelimitersAsync(Task task, TextWriter writer, string s, char delimiter,