-
Notifications
You must be signed in to change notification settings - Fork 890
Expand file tree
/
Copy pathPgStreamingConverter.cs
More file actions
104 lines (90 loc) · 4.15 KB
/
Copy pathPgStreamingConverter.cs
File metadata and controls
104 lines (90 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Npgsql.Internal;
[Experimental(NpgsqlDiagnostics.ConvertersExperimental)]
public abstract class PgStreamingConverter<T> : PgConverter<T>
{
protected PgStreamingConverter()
{
}
[Obsolete("Call the parameterless constructor and set HandleDbNull directly.")]
protected PgStreamingConverter(bool customDbNullPredicate) => HandleDbNull = customDbNullPredicate;
public override ConverterDescriptor GetDescriptor(in DescriptorContext context)
=> ConverterDescriptor.Invariant with { BufferRequirements = BufferRequirements.Streaming };
// Workaround for trimming https://github.com/dotnet/runtime/issues/92850#issuecomment-1744521361
internal Task<T>? ReadAsyncAsTask(PgReader reader, CancellationToken cancellationToken, out T result)
{
var task = ReadAsync(reader, cancellationToken);
if (task.IsCompletedSuccessfully)
{
result = task.Result;
return null;
}
result = default!;
return task.AsTask();
}
internal sealed override unsafe ValueTask<object?> ReadAsObject(
bool async, PgReader reader, CancellationToken cancellationToken)
{
if (!async)
return new(Read(reader));
var task = ReadAsync(reader, cancellationToken);
return task.IsCompletedSuccessfully
? new(task.Result)
: PgStreamingConverterHelpers.AwaitTask(task.AsTask(), new(this, &BoxResult));
static object? BoxResult(Task task)
{
// Justification: exact type Unsafe.As used to reduce generic duplication cost.
Debug.Assert(task is Task<T>);
// Using .Result on ValueTask is equivalent to GetAwaiter().GetResult(), this removes TaskAwaiter<T> rooting.
return new ValueTask<T>(task: Unsafe.As<Task<T>>(task)).Result;
}
}
internal sealed override ValueTask WriteAsObject(bool async, PgWriter writer, object? value, CancellationToken cancellationToken)
{
if (async)
return WriteAsync(writer, (T)value!, cancellationToken);
Write(writer, (T)value!);
return new();
}
}
// Using a function pointer here is safe against assembly unloading as the instance reference that the static pointer method lives on is
// passed along. As such the instance cannot be collected by the gc which means the entire assembly is prevented from unloading until we're
// done.
// The alternatives are:
// 1. Add a virtual method and make AwaitTask call into it (bloating the vtable of all derived types).
// 2. Using a delegate, meaning we add a static field + an alloc per T + metadata, slightly slower dispatch perf so overall strictly worse
// as well.
static class PgStreamingConverterHelpers
{
// Split out from the generic class to amortize the huge size penalty per async state machine, which would otherwise be per
// instantiation.
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
public static async ValueTask<object?> AwaitTask(Task task, Continuation continuation)
{
await task.ConfigureAwait(false);
var result = continuation.Invoke(task);
// Guarantee the type stays loaded until the function pointer call is done.
GC.KeepAlive(continuation.Handle);
return result;
}
// Split out into a struct as unsafe and async don't mix, while we do want a nicely typed function pointer signature to prevent
// mistakes.
public readonly unsafe struct Continuation
{
public object Handle { get; }
readonly delegate*<Task, object?> _continuation;
/// <param name="handle">A reference to the type that houses the static method <see ref="continuation"/> points to.</param>
/// <param name="continuation">The continuation</param>
public Continuation(object handle, delegate*<Task, object?> continuation)
{
Handle = handle;
_continuation = continuation;
}
public object? Invoke(Task task) => _continuation(task);
}
}