@@ -86,29 +86,40 @@ private struct MetadataDb : IDisposable
86
86
87
87
internal int Length { get ; private set ; }
88
88
private byte [ ] _data ;
89
- #if DEBUG
90
- private readonly bool _isLocked ;
91
- #endif
89
+
90
+ private bool _convertToAlloc ; // Convert the rented data to an alloc when complete.
91
+ private bool _isLocked ; // Is the array the correct fixed size.
92
+ // _isLocked _convertToAlloc truth table:
93
+ // false false Standard flow. Size is not known and renting used throughout lifetime.
94
+ // true false Used by JsonElement.ParseValue() for primitives and JsonDocument.Clone(). Size is known and no renting.
95
+ // false true Used by JsonElement.ParseValue() for arrays and objects. Renting used until size is known.
96
+ // true true not valid
97
+
98
+ private MetadataDb ( byte [ ] initialDb , bool isLocked , bool convertToAlloc )
99
+ {
100
+ _data = initialDb ;
101
+ _isLocked = isLocked ;
102
+ _convertToAlloc = convertToAlloc ;
103
+ Length = 0 ;
104
+ }
92
105
93
106
internal MetadataDb ( byte [ ] completeDb )
94
107
{
95
108
_data = completeDb ;
96
- Length = completeDb . Length ;
97
-
98
- #if DEBUG
99
109
_isLocked = true ;
100
- #endif
110
+ _convertToAlloc = false ;
111
+ Length = completeDb . Length ;
101
112
}
102
113
103
- internal MetadataDb ( int payloadLength )
114
+ internal static MetadataDb CreateRented ( int payloadLength , bool convertToAlloc )
104
115
{
105
116
// Assume that a token happens approximately every 12 bytes.
106
117
// int estimatedTokens = payloadLength / 12
107
118
// now acknowledge that the number of bytes we need per token is 12.
108
119
// So that's just the payload length.
109
120
//
110
- // Add one token's worth of data just because .
111
- int initialSize = DbRow . Size + payloadLength ;
121
+ // Add one row worth of data since we need at least one row for a primitive type .
122
+ int initialSize = payloadLength + DbRow . Size ;
112
123
113
124
// Stick with ArrayPool's rent/return range if it looks feasible.
114
125
// If it's wrong, we'll just grow and copy as we would if the tokens
@@ -120,30 +131,17 @@ internal MetadataDb(int payloadLength)
120
131
initialSize = OneMegabyte ;
121
132
}
122
133
123
- _data = ArrayPool < byte > . Shared . Rent ( initialSize ) ;
124
- Length = 0 ;
125
- #if DEBUG
126
- _isLocked = false ;
127
- #endif
134
+ byte [ ] data = ArrayPool < byte > . Shared . Rent ( initialSize ) ;
135
+ return new MetadataDb ( data , isLocked : false , convertToAlloc ) ;
128
136
}
129
137
130
- internal MetadataDb ( MetadataDb source , bool useArrayPools )
138
+ internal static MetadataDb CreateLocked ( int payloadLength )
131
139
{
132
- Length = source . Length ;
133
-
134
- #if DEBUG
135
- _isLocked = ! useArrayPools ;
136
- #endif
140
+ // Add one row worth of data since we need at least one row for a primitive type.
141
+ int size = payloadLength + DbRow . Size ;
137
142
138
- if ( useArrayPools )
139
- {
140
- _data = ArrayPool < byte > . Shared . Rent ( Length ) ;
141
- source . _data . AsSpan ( 0 , Length ) . CopyTo ( _data ) ;
142
- }
143
- else
144
- {
145
- _data = source . _data . AsSpan ( 0 , Length ) . ToArray ( ) ;
146
- }
143
+ byte [ ] data = new byte [ size ] ;
144
+ return new MetadataDb ( data , isLocked : true , convertToAlloc : false ) ;
147
145
}
148
146
149
147
public void Dispose ( )
@@ -154,9 +152,7 @@ public void Dispose()
154
152
return ;
155
153
}
156
154
157
- #if DEBUG
158
155
Debug . Assert ( ! _isLocked , "Dispose called on a locked database" ) ;
159
- #endif
160
156
161
157
// The data in this rented buffer only conveys the positions and
162
158
// lengths of tokens in a document, but no content; so it does not
@@ -165,28 +161,51 @@ public void Dispose()
165
161
Length = 0 ;
166
162
}
167
163
168
- internal void TrimExcess ( )
164
+ /// <summary>
165
+ /// If using array pools, trim excess if necessary.
166
+ /// If not using array pools, release the temporary array pool and alloc.
167
+ /// </summary>
168
+ internal void CompleteAllocations ( )
169
169
{
170
- // There's a chance that the size we have is the size we'd get for this
171
- // amount of usage (particularly if Enlarge ever got called); and there's
172
- // the small copy-cost associated with trimming anyways. "Is half-empty" is
173
- // just a rough metric for "is trimming worth it?".
174
- if ( Length <= _data . Length / 2 )
170
+ if ( ! _isLocked )
175
171
{
176
- byte [ ] newRent = ArrayPool < byte > . Shared . Rent ( Length ) ;
177
- byte [ ] returnBuf = newRent ;
178
-
179
- if ( newRent . Length < _data . Length )
172
+ if ( _convertToAlloc )
180
173
{
181
- Buffer . BlockCopy ( _data , 0 , newRent , 0 , Length ) ;
182
- returnBuf = _data ;
183
- _data = newRent ;
174
+ Debug . Assert ( _data != null ) ;
175
+ byte [ ] returnBuf = _data ;
176
+ _data = _data . AsSpan ( 0 , Length ) . ToArray ( ) ;
177
+ _isLocked = true ;
178
+ _convertToAlloc = false ;
179
+
180
+ // The data in this rented buffer only conveys the positions and
181
+ // lengths of tokens in a document, but no content; so it does not
182
+ // need to be cleared.
183
+ ArrayPool < byte > . Shared . Return ( returnBuf ) ;
184
+ }
185
+ else
186
+ {
187
+ // There's a chance that the size we have is the size we'd get for this
188
+ // amount of usage (particularly if Enlarge ever got called); and there's
189
+ // the small copy-cost associated with trimming anyways. "Is half-empty" is
190
+ // just a rough metric for "is trimming worth it?".
191
+ if ( Length <= _data . Length / 2 )
192
+ {
193
+ byte [ ] newRent = ArrayPool < byte > . Shared . Rent ( Length ) ;
194
+ byte [ ] returnBuf = newRent ;
195
+
196
+ if ( newRent . Length < _data . Length )
197
+ {
198
+ Buffer . BlockCopy ( _data , 0 , newRent , 0 , Length ) ;
199
+ returnBuf = _data ;
200
+ _data = newRent ;
201
+ }
202
+
203
+ // The data in this rented buffer only conveys the positions and
204
+ // lengths of tokens in a document, but no content; so it does not
205
+ // need to be cleared.
206
+ ArrayPool < byte > . Shared . Return ( returnBuf ) ;
207
+ }
184
208
}
185
-
186
- // The data in this rented buffer only conveys the positions and
187
- // lengths of tokens in a document, but no content; so it does not
188
- // need to be cleared.
189
- ArrayPool < byte > . Shared . Return ( returnBuf ) ;
190
209
}
191
210
}
192
211
@@ -197,10 +216,6 @@ internal void Append(JsonTokenType tokenType, int startLocation, int length)
197
216
( tokenType == JsonTokenType . StartArray || tokenType == JsonTokenType . StartObject ) ==
198
217
( length == DbRow . UnknownSize ) ) ;
199
218
200
- #if DEBUG
201
- Debug . Assert ( ! _isLocked , "Appending to a locked database" ) ;
202
- #endif
203
-
204
219
if ( Length >= _data . Length - DbRow . Size )
205
220
{
206
221
Enlarge ( ) ;
@@ -213,6 +228,8 @@ internal void Append(JsonTokenType tokenType, int startLocation, int length)
213
228
214
229
private void Enlarge ( )
215
230
{
231
+ Debug . Assert ( ! _isLocked , "Appending to a locked database" ) ;
232
+
216
233
byte [ ] toReturn = _data ;
217
234
_data = ArrayPool < byte > . Shared . Rent ( toReturn . Length * 2 ) ;
218
235
Buffer . BlockCopy ( toReturn , 0 , _data , 0 , toReturn . Length ) ;
0 commit comments