@@ -86,29 +86,40 @@ private struct MetadataDb : IDisposable
8686
8787 internal int Length { get ; private set ; }
8888 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+ }
92105
93106 internal MetadataDb ( byte [ ] completeDb )
94107 {
95108 _data = completeDb ;
96- Length = completeDb . Length ;
97-
98- #if DEBUG
99109 _isLocked = true ;
100- #endif
110+ _convertToAlloc = false ;
111+ Length = completeDb . Length ;
101112 }
102113
103- internal MetadataDb ( int payloadLength )
114+ internal static MetadataDb CreateRented ( int payloadLength , bool convertToAlloc )
104115 {
105116 // Assume that a token happens approximately every 12 bytes.
106117 // int estimatedTokens = payloadLength / 12
107118 // now acknowledge that the number of bytes we need per token is 12.
108119 // So that's just the payload length.
109120 //
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 ;
112123
113124 // Stick with ArrayPool's rent/return range if it looks feasible.
114125 // If it's wrong, we'll just grow and copy as we would if the tokens
@@ -120,30 +131,17 @@ internal MetadataDb(int payloadLength)
120131 initialSize = OneMegabyte ;
121132 }
122133
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 ) ;
128136 }
129137
130- internal MetadataDb ( MetadataDb source , bool useArrayPools )
138+ internal static MetadataDb CreateLocked ( int payloadLength )
131139 {
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 ;
137142
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 ) ;
147145 }
148146
149147 public void Dispose ( )
@@ -154,9 +152,7 @@ public void Dispose()
154152 return ;
155153 }
156154
157- #if DEBUG
158155 Debug . Assert ( ! _isLocked , "Dispose called on a locked database" ) ;
159- #endif
160156
161157 // The data in this rented buffer only conveys the positions and
162158 // lengths of tokens in a document, but no content; so it does not
@@ -165,28 +161,51 @@ public void Dispose()
165161 Length = 0 ;
166162 }
167163
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 ( )
169169 {
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 )
175171 {
176- byte [ ] newRent = ArrayPool < byte > . Shared . Rent ( Length ) ;
177- byte [ ] returnBuf = newRent ;
178-
179- if ( newRent . Length < _data . Length )
172+ if ( _convertToAlloc )
180173 {
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+ }
184208 }
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 ) ;
190209 }
191210 }
192211
@@ -197,10 +216,6 @@ internal void Append(JsonTokenType tokenType, int startLocation, int length)
197216 ( tokenType == JsonTokenType . StartArray || tokenType == JsonTokenType . StartObject ) ==
198217 ( length == DbRow . UnknownSize ) ) ;
199218
200- #if DEBUG
201- Debug . Assert ( ! _isLocked , "Appending to a locked database" ) ;
202- #endif
203-
204219 if ( Length >= _data . Length - DbRow . Size )
205220 {
206221 Enlarge ( ) ;
@@ -213,6 +228,8 @@ internal void Append(JsonTokenType tokenType, int startLocation, int length)
213228
214229 private void Enlarge ( )
215230 {
231+ Debug . Assert ( ! _isLocked , "Appending to a locked database" ) ;
232+
216233 byte [ ] toReturn = _data ;
217234 _data = ArrayPool < byte > . Shared . Rent ( toReturn . Length * 2 ) ;
218235 Buffer . BlockCopy ( toReturn , 0 , _data , 0 , toReturn . Length ) ;
0 commit comments