18
18
using Datadog . Trace . Debugger . Sink ;
19
19
using Datadog . Trace . Debugger . Symbols . Model ;
20
20
using Datadog . Trace . Debugger . Upload ;
21
- using Datadog . Trace . ExtensionMethods ;
22
21
using Datadog . Trace . Logging ;
23
22
using Datadog . Trace . RemoteConfigurationManagement ;
24
23
using Datadog . Trace . Util ;
@@ -230,8 +229,10 @@ private async Task UploadAssemblySymbols(Assembly assembly)
230
229
231
230
private async Task UploadClasses ( Root root , IEnumerable < Model . Scope ? > classes )
232
231
{
232
+ var rootAsString = JsonConvert . SerializeObject ( root ) ;
233
+ var rootBytes = Encoding . UTF8 . GetByteCount ( rootAsString ) ;
234
+ var builder = StringBuilderCache . Acquire ( ( int ) _thresholdInBytes + rootBytes + 4 ) ;
233
235
var accumulatedBytes = 0 ;
234
- var builder = StringBuilderCache . Acquire ( ( int ) _thresholdInBytes ) ;
235
236
236
237
try
237
238
{
@@ -242,20 +243,36 @@ private async Task UploadClasses(Root root, IEnumerable<Model.Scope?> classes)
242
243
continue ;
243
244
}
244
245
245
- accumulatedBytes += SerializeClass ( classSymbol . Value , builder ) ;
246
- if ( accumulatedBytes < _thresholdInBytes )
246
+ // Try to serialize and append the class
247
+ if ( ! TrySerializeClass ( classSymbol . Value , builder , accumulatedBytes , out var newByteCount ) )
247
248
{
248
- continue ;
249
+ // If we couldn't append because it would exceed capacity,
250
+ // upload current batch first
251
+ bool succeeded = false ;
252
+ if ( builder . Length > 0 )
253
+ {
254
+ await Upload ( rootAsString , builder ) . ConfigureAwait ( false ) ;
255
+ builder . Clear ( ) ;
256
+ accumulatedBytes = 0 ;
257
+ // Try again with empty builder
258
+ succeeded = TrySerializeClass ( classSymbol . Value , builder , accumulatedBytes , out newByteCount ) ;
259
+ }
260
+
261
+ if ( ! succeeded )
262
+ {
263
+ // If it still doesn't fit, this single class is too large
264
+ Log . Warning ( "Class {Name} exceeds maximum capacity" , classSymbol . Value . Name ) ;
265
+ continue ;
266
+ }
249
267
}
250
268
251
- await Upload ( root , builder ) . ConfigureAwait ( false ) ;
252
- builder . Clear ( ) ;
253
- accumulatedBytes = 0 ;
269
+ accumulatedBytes = newByteCount ;
254
270
}
255
271
256
- if ( accumulatedBytes > 0 )
272
+ // Upload any remaining data
273
+ if ( builder . Length > 0 )
257
274
{
258
- await Upload ( root , builder ) . ConfigureAwait ( false ) ;
275
+ await Upload ( rootAsString , builder ) . ConfigureAwait ( false ) ;
259
276
}
260
277
}
261
278
finally
@@ -267,9 +284,9 @@ private async Task UploadClasses(Root root, IEnumerable<Model.Scope?> classes)
267
284
}
268
285
}
269
286
270
- private async Task Upload ( Root root , StringBuilder builder )
287
+ private async Task Upload ( string rootAsString , StringBuilder builder )
271
288
{
272
- FinalizeSymbolForSend ( root , builder ) ;
289
+ FinalizeSymbolForSend ( rootAsString , builder ) ;
273
290
await SendSymbol ( builder . ToString ( ) ) . ConfigureAwait ( false ) ;
274
291
ResetPayload ( ) ;
275
292
}
@@ -294,33 +311,39 @@ private async Task<bool> SendSymbol(string symbol)
294
311
return await _api . SendBatchAsync ( new ArraySegment < byte > ( _payload ) ) . ConfigureAwait ( false ) ;
295
312
}
296
313
297
- private int SerializeClass ( Model . Scope classScope , StringBuilder sb )
314
+ private bool TrySerializeClass ( Model . Scope classScope , StringBuilder sb , int currentBytes , out int newTotalBytes )
298
315
{
299
- if ( sb . Length != 0 )
316
+ // Calculate the serialized string first
317
+ var symbolAsString = JsonConvert . SerializeObject ( classScope , _jsonSerializerSettings ) ;
318
+ var classBytes = Encoding . UTF8 . GetByteCount ( symbolAsString ) ;
319
+
320
+ newTotalBytes = currentBytes ;
321
+ if ( sb . Length > 0 )
300
322
{
301
- sb . Append ( ',' ) ;
323
+ classBytes += 1 ; // for comma
302
324
}
303
325
304
- var symbolAsString = JsonConvert . SerializeObject ( classScope , _jsonSerializerSettings ) ;
326
+ newTotalBytes += classBytes ;
305
327
306
- try
328
+ if ( newTotalBytes > _thresholdInBytes )
307
329
{
308
- sb . Append ( symbolAsString ) ;
330
+ return false ;
309
331
}
310
- catch ( ArgumentOutOfRangeException )
332
+
333
+ // Safe to append
334
+ if ( sb . Length > 0 )
311
335
{
312
- return 0 ;
336
+ sb . Append ( ',' ) ;
313
337
}
314
338
315
- return Encoding . UTF8 . GetByteCount ( symbolAsString ) ;
339
+ sb . Append ( symbolAsString ) ;
340
+ return true ;
316
341
}
317
342
318
- private void FinalizeSymbolForSend ( Root root , StringBuilder sb )
343
+ private void FinalizeSymbolForSend ( string rootAsString , StringBuilder sb )
319
344
{
320
345
const string classScopeString = "\" scopes\" :null" ;
321
346
322
- var rootAsString = JsonConvert . SerializeObject ( root ) ;
323
-
324
347
var classesIndex = rootAsString . IndexOf ( classScopeString , StringComparison . Ordinal ) ;
325
348
326
349
sb . Insert ( 0 , rootAsString . Substring ( 0 , classesIndex + classScopeString . Length - "null" . Length ) + "[" ) ;
0 commit comments