forked from npgsql/npgsql
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathPgStreamingConverter.cs
More file actions
100 lines (88 loc) · 3.99 KB
/
PgStreamingConverter.cs
File metadata and controls
100 lines (88 loc) · 3.99 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
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>(bool customDbNullPredicate = false) : PgConverter<T>(customDbNullPredicate)
{
public override bool CanConvert(DataFormat format, out BufferRequirements bufferRequirements)
{
bufferRequirements = BufferRequirements.None;
return format is DataFormat.Binary;
}
// 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);
}
}