3
3
using System . Buffers ;
4
4
using System . IO ;
5
5
using System . Runtime . CompilerServices ;
6
+ using System . Runtime . InteropServices ;
7
+ using System . Threading ;
6
8
7
9
namespace ProtoBuf . Grpc . Configuration
8
10
{
9
11
/// <summary>
10
12
/// Provides protobuf-net implementation of a per-type marshaller
11
13
/// </summary>
12
- public class ProtoBufMarshallerFactory : MarshallerFactory
14
+ public partial class ProtoBufMarshallerFactory : MarshallerFactory
13
15
{
14
16
/// <summary>
15
17
/// Options that control protobuf-net marshalling
@@ -24,69 +26,153 @@ public enum Options
24
26
/// <summary>
25
27
/// Enforce that only contract-types should be allowed
26
28
/// </summary>
27
- ContractTypesOnly = 1 ,
29
+ ContractTypesOnly = 1 << 0 ,
30
+ /// <summary>
31
+ /// Disable 'contextual' serializer usage (this means serializers that use <see cref="ReadOnlySequence{byte}"/> or <see cref="IBufferWriter{byte}"/>),
32
+ /// using the legacy <see cref="byte[]"/> APIs.
33
+ /// </summary>
34
+ DisableContextualSerializer = 1 << 1 ,
28
35
}
29
36
30
37
/// <summary>
31
38
/// Uses the default protobuf-net serializer
32
39
/// </summary>
33
- public static MarshallerFactory Default { get ; } = new ProtoBufMarshallerFactory ( RuntimeTypeModel . Default , Options . None ) ;
40
+ public static MarshallerFactory Default { get ; } = new ProtoBufMarshallerFactory ( RuntimeTypeModel . Default , Options . None , default ) ;
34
41
35
- private readonly RuntimeTypeModel _model ;
42
+ private readonly TypeModel _model ;
43
+ private readonly SerializationContext ? _userState ;
36
44
private readonly Options _options ;
45
+ // note: these are the same *object*, but pre-checked for optional API support, for efficiency
46
+ // (the minimum .NET object size means that the extra fields don't cost anything)
47
+ private readonly IMeasuredProtoOutput < IBufferWriter < byte > > ? _measuredWriterModel ;
48
+ private readonly IProtoInput < ReadOnlySequence < byte > > ? _squenceReaderModel ;
49
+
37
50
/// <summary>
38
51
/// Create a new factory using a specific protobuf-net model
39
52
/// </summary>
40
- public static MarshallerFactory Create ( RuntimeTypeModel ? model = null , Options options = Options . None )
53
+ public static MarshallerFactory Create ( TypeModel ? model = null , Options options = Options . None , object ? userState = null )
41
54
{
42
- if ( model == null ) model = RuntimeTypeModel . Default ;
43
- if ( options == Options . None && model == RuntimeTypeModel . Default ) return Default ;
44
- return new ProtoBufMarshallerFactory ( model , options ) ;
55
+ model ?? = RuntimeTypeModel . Default ;
56
+ if ( options == Options . None && model == RuntimeTypeModel . Default && userState is null ) return Default ;
57
+ return new ProtoBufMarshallerFactory ( model , options , userState ) ;
45
58
}
46
59
47
- internal ProtoBufMarshallerFactory ( RuntimeTypeModel model , Options options )
60
+ /// <summary>
61
+ /// Create a new factory using a specific protobuf-net model
62
+ /// </summary>
63
+ public static MarshallerFactory Create ( RuntimeTypeModel ? model , Options options )
64
+ => Create ( ( TypeModel ? ) model , options , null ) ;
65
+
66
+ internal ProtoBufMarshallerFactory ( TypeModel model , Options options , object ? userState = null )
48
67
{
49
68
_model = model ;
50
69
_options = options ;
70
+ _userState = userState switch
71
+ {
72
+ null => null ,
73
+ SerializationContext ctx => ctx ,
74
+ _ => new SerializationContext { Context = userState }
75
+ } ;
76
+
77
+ if ( UseContextualSerializer )
78
+ {
79
+ // test these once rather than every time
80
+ _measuredWriterModel = model as IMeasuredProtoOutput < IBufferWriter < byte > > ;
81
+ _squenceReaderModel = model as IProtoInput < ReadOnlySequence < byte > > ;
82
+ }
51
83
}
52
84
85
+ private bool UseContextualSerializer => ( _options & Options . DisableContextualSerializer ) == 0 ;
86
+
53
87
[ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
54
88
private bool Has ( Options option ) => ( _options & option ) == option ;
55
89
56
- /* see: https://github.com/grpc/grpc/pull/19471 / https://github.com/grpc/grpc/issues/19470
57
90
/// <summary>
58
91
/// Deserializes an object from a payload
59
92
/// </summary>
60
93
protected internal override global ::Grpc . Core . Marshaller < T > CreateMarshaller < T > ( )
61
- => new global::Grpc.Core.Marshaller<T>(ContextualSerialize<T>, ContextualDeserialize<T>);
62
- */
94
+ => UseContextualSerializer
95
+ ? new global ::Grpc . Core . Marshaller < T > ( ContextualSerialize < T > , ContextualDeserialize < T > )
96
+ : base . CreateMarshaller < T > ( ) ;
97
+
98
+ #if DEBUG
99
+ private int _uplevelBufferReadCount , _uplevelBufferWriteCount ;
100
+ public int UplevelBufferReadCount => Volatile . Read ( ref _uplevelBufferReadCount ) ;
101
+ public int UplevelBufferWriteCount => Volatile . Read ( ref _uplevelBufferWriteCount ) ;
102
+
103
+ partial void RecordUplevelBufferRead ( ) => Interlocked . Increment ( ref _uplevelBufferReadCount ) ;
104
+ partial void RecordUplevelBufferWrite ( ) => Interlocked . Increment ( ref _uplevelBufferWriteCount ) ;
105
+ #endif
106
+ partial void RecordUplevelBufferRead ( ) ;
107
+ partial void RecordUplevelBufferWrite ( ) ;
108
+
109
+ private bool TryGetBufferWriter ( global ::Grpc . Core . SerializationContext context , out IBufferWriter < byte > ? writer )
110
+ {
111
+ // the managed implementation does not yet implement this API
112
+ try { writer = context . GetBufferWriter ( ) ; }
113
+ catch ( NotSupportedException ) { writer = default ; }
114
+ catch ( NotImplementedException ) { writer = default ; }
115
+ return writer is object ;
116
+ }
63
117
64
118
private void ContextualSerialize < T > ( T value , global ::Grpc . Core . SerializationContext context )
65
- => context . Complete ( Serialize ( value ) ) ;
119
+ {
120
+ if ( _measuredWriterModel is object )
121
+ { // forget what we think we know about TypeModel; if we have protobuf-net 3.*, we can do this
122
+
123
+ RecordUplevelBufferWrite ( ) ;
124
+
125
+ using var measured = _measuredWriterModel . Measure ( value , userState : _userState ) ;
126
+ int len = checked ( ( int ) measured . Length ) ;
127
+ context . SetPayloadLength ( len ) ;
128
+
129
+ if ( TryGetBufferWriter ( context , out var writer ) )
130
+ { // write to the buffer-writer API
131
+ _measuredWriterModel . Serialize ( measured , writer ! ) ;
132
+ context . Complete ( ) ;
133
+ }
134
+ else
135
+ {
136
+ // the buffer-writer API wasn't supported, but we can still optimize by right-sizing
137
+ // a MemoryStream to write to, to avoid a resize etc
138
+ context . Complete ( Serialize < T > ( value , len ) ) ;
139
+ }
140
+ }
141
+ else
142
+ {
143
+ context . Complete ( Serialize < T > ( value ) ) ;
144
+ }
145
+ }
66
146
67
147
private T ContextualDeserialize < T > ( global ::Grpc . Core . DeserializationContext context )
68
148
{
69
149
var ros = context . PayloadAsReadOnlySequence ( ) ;
70
- #if PLAT_PBN_NOSPAN
71
- // copy the data out of the ROS into a rented buffer, and deserialize
72
- // from that
150
+ if ( _squenceReaderModel is object )
151
+ { // forget what we think we know about TypeModel; if we have protobuf-net 3.*, we can do this
152
+ RecordUplevelBufferRead ( ) ;
153
+ return _squenceReaderModel . Deserialize < T > ( ros , userState : _userState ) ;
154
+ }
155
+
156
+ // 2.4.2+ can use array-segments
157
+ IProtoInput < ArraySegment < byte > > segmentReader = _model ;
158
+
159
+ // can we go direct to a single segment?
160
+ if ( ros . IsSingleSegment && MemoryMarshal . TryGetArray ( ros . First , out var segment ) )
161
+ {
162
+ return segmentReader . Deserialize < T > ( segment , userState : _userState ) ;
163
+ }
164
+
165
+ // otherwise; linearize the data
73
166
var oversized = ArrayPool < byte > . Shared . Rent ( context . PayloadLength ) ;
74
167
try
75
168
{
76
169
ros . CopyTo ( oversized ) ;
77
- return Deserialize < T > ( oversized , 0 , context . PayloadLength ) ;
170
+ return segmentReader . Deserialize < T > ( new ArraySegment < byte > ( oversized , 0 , context . PayloadLength ) , userState : _userState ) ;
78
171
}
79
172
finally
80
173
{
81
174
ArrayPool < byte > . Shared . Return ( oversized ) ;
82
175
}
83
- #else
84
- // create a reader directly on the ROS
85
- using ( var reader = ProtoReader . Create ( out var state , ros , _model ) )
86
- {
87
- return ( T ) _model . Deserialize ( reader , ref state , null , typeof ( T ) ) ;
88
- }
89
- #endif
90
176
}
91
177
92
178
/// <summary>
@@ -101,21 +187,9 @@ protected internal override bool CanSerialize(Type type)
101
187
/// Deserializes an object from a payload
102
188
/// </summary>
103
189
protected override T Deserialize < T > ( byte [ ] payload )
104
- => Deserialize < T > ( payload , 0 , payload . Length ) ;
105
-
106
- private T Deserialize < T > ( byte [ ] payload , int offset , int count )
107
190
{
108
- #if PLAT_PBN_NOSPAN
109
- using var ms = new MemoryStream ( payload , offset , count ) ;
110
- using var reader = ProtoReader . Create ( ms , _model ) ;
111
- return ( T ) _model . Deserialize ( reader , null , typeof ( T ) ) ;
112
- #else
113
- var range = new ReadOnlyMemory < byte > ( payload , offset , count ) ;
114
- using ( var reader = ProtoReader . Create ( out var state , range , _model ) )
115
- {
116
- return ( T ) _model . Deserialize ( reader , ref state , null , typeof ( T ) ) ;
117
- }
118
- #endif
191
+ IProtoInput < byte [ ] > segmentReader = _model ;
192
+ return segmentReader . Deserialize < T > ( payload , userState : _userState ) ;
119
193
}
120
194
121
195
/// <summary>
@@ -127,5 +201,16 @@ protected override byte[] Serialize<T>(T value)
127
201
_model . Serialize ( ms , value , context : null ) ;
128
202
return ms . ToArray ( ) ;
129
203
}
204
+
205
+ private byte [ ] Serialize < T > ( T value , int length )
206
+ {
207
+ if ( length == 0 ) return Array . Empty < byte > ( ) ;
208
+ var arr = new byte [ length ] ;
209
+ using var ms = new MemoryStream ( arr ) ;
210
+ _model . Serialize ( ms , value , context : null ) ;
211
+ if ( length != ms . Length ) throw new InvalidOperationException (
212
+ $ "Length miscalculated; expected { length } , got { ms . Length } ") ;
213
+ return arr ;
214
+ }
130
215
}
131
216
}
0 commit comments