Skip to content

Commit 2b2c1d3

Browse files
committed
[CI Visibility] Release global coverage buffers per test
1 parent c7bbbbd commit 2b2c1d3

19 files changed

Lines changed: 183 additions & 1022 deletions

tracer/src/Datadog.Trace.Coverage.collector/AssemblyProcessor.cs

Lines changed: 19 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -249,24 +249,14 @@ public unsafe void Process()
249249
var reportTypeGenericInstance = new GenericInstanceType(coverageReporterTypeReference);
250250
reportTypeGenericInstance.GenericArguments.Add(moduleCoverageMetadataImplTypeDef);
251251

252-
var coverageProbeTypeDefinition = datadogTracerAssembly.MainModule.GetType(typeof(CoverageProbe).FullName);
253-
var coverageProbeTypeReference = module.ImportReference(coverageProbeTypeDefinition);
254-
var reportAcquireCountersMethod = new MethodReference("AcquireFileCounter", coverageProbeTypeReference, reportTypeGenericInstance)
252+
var reportGetCountersMethod = new MethodReference("GetFileCounter", new PointerType(module.TypeSystem.Void), reportTypeGenericInstance)
255253
{
256254
HasThis = false,
257255
Parameters =
258256
{
259257
new ParameterDefinition(module.TypeSystem.Int32) { Name = "fileIndex" }
260258
}
261259
};
262-
var probePointerGetter = new MethodReference("get_Pointer", new PointerType(module.TypeSystem.Void), coverageProbeTypeReference)
263-
{
264-
HasThis = true
265-
};
266-
var probeDisposeMethod = new MethodReference(nameof(IDisposable.Dispose), module.TypeSystem.Void, coverageProbeTypeReference)
267-
{
268-
HasThis = true
269-
};
270260

271261
// GenericInstanceMethod? arrayEmptyOfIntMethodReference = null;
272262
for (var typeIndex = 0; typeIndex < moduleTypes.Count; typeIndex++)
@@ -442,15 +432,6 @@ public unsafe void Process()
442432

443433
var methodBody = moduleTypeMethod.Body;
444434
var instructions = methodBody.Instructions;
445-
if (instructions.Any(static instruction => instruction.OpCode == OpCodes.Tail))
446-
{
447-
// The invocation probe is released from an outer fault handler and a shared return
448-
// epilogue. Wrapping a tail transfer in that protected region would invalidate its
449-
// stack-constant semantics, so leave these uncommon methods untouched.
450-
_logger.Debug($"\t\t[NO] {moduleTypeMethod.FullName}, contains a tail call.");
451-
continue;
452-
}
453-
454435
var instructionsOriginalLength = instructions.Count;
455436
if (instructions.Capacity < instructionsOriginalLength * 2)
456437
{
@@ -474,22 +455,21 @@ public unsafe void Process()
474455
}
475456
}
476457

477-
// The probe local owns the native buffer for the complete invocation. The generated
478-
// outer fault handler and shared return epilogue release it on every exit path.
479-
var probeVariable = new VariableDefinition(coverageProbeTypeReference);
480-
VariableDefinition countersVariable;
481-
if (_coverageMode == CoverageMode.LineExecution)
482-
{
483-
countersVariable = new VariableDefinition(new PointerType(module.TypeSystem.Byte));
484-
}
485-
else
458+
VariableDefinition? countersVariable = null;
459+
if (instructionsWithValidSequencePoints.Count > 1 || instructions[0] != instructionsWithValidSequencePoints[0].Instruction)
486460
{
487-
countersVariable = new VariableDefinition(new PointerType(module.TypeSystem.Int32));
488-
}
461+
// Step 3 - Modify local var to add the Coverage counters instance.
462+
if (_coverageMode == CoverageMode.LineExecution)
463+
{
464+
countersVariable = new VariableDefinition(new PointerType(module.TypeSystem.Byte));
465+
}
466+
else
467+
{
468+
countersVariable = new VariableDefinition(new PointerType(module.TypeSystem.Int32));
469+
}
489470

490-
methodBody.Variables.Add(probeVariable);
491-
methodBody.Variables.Add(countersVariable);
492-
methodBody.InitLocals = true;
471+
methodBody.Variables.Add(countersVariable);
472+
}
493473

494474
// Step 4 - Insert the counter retriever
495475
FileMetadata fileMetadata;
@@ -499,13 +479,12 @@ public unsafe void Process()
499479
fileDictionaryIndex[filePath] = fileMetadata;
500480
}
501481

502-
var probeProtectedStart = Instruction.Create(OpCodes.Ldloca, probeVariable);
503482
instructions.Insert(0, Instruction.Create(OpCodes.Ldc_I4, fileMetadata.Index));
504-
instructions.Insert(1, Instruction.Create(OpCodes.Call, reportAcquireCountersMethod));
505-
instructions.Insert(2, Instruction.Create(OpCodes.Stloc, probeVariable));
506-
instructions.Insert(3, probeProtectedStart);
507-
instructions.Insert(4, Instruction.Create(OpCodes.Call, probePointerGetter));
508-
instructions.Insert(5, Instruction.Create(OpCodes.Stloc, countersVariable));
483+
instructions.Insert(1, Instruction.Create(OpCodes.Call, reportGetCountersMethod));
484+
if (countersVariable is not null)
485+
{
486+
instructions.Insert(2, Instruction.Create(OpCodes.Stloc, countersVariable));
487+
}
509488

510489
// Step 5 - Insert line reporter
511490
for (var i = 0; i < instructionsWithValidSequencePoints.Count; i++)
@@ -656,7 +635,6 @@ public unsafe void Process()
656635
instructions.Insert(++optIdx, currentInstructionClone);
657636
}
658637

659-
AddProbeCleanup(methodBody, probeVariable, probeProtectedStart, probeDisposeMethod);
660638
isDirty = true;
661639
}
662640
}
@@ -977,77 +955,6 @@ private static void WriteInt32LittleEndian(byte[] buffer, int offset, int value)
977955
buffer[offset + 3] = (byte)(value >> 24);
978956
}
979957

980-
private static void AddProbeCleanup(
981-
Mono.Cecil.Cil.MethodBody methodBody,
982-
VariableDefinition probeVariable,
983-
Instruction protectedStart,
984-
MethodReference probeDisposeMethod)
985-
{
986-
var instructions = methodBody.Instructions;
987-
var returnInstructions = instructions.Where(static instruction => instruction.OpCode == OpCodes.Ret).ToArray();
988-
VariableDefinition? returnVariable = null;
989-
if (returnInstructions.Length > 0 && methodBody.Method.ReturnType.MetadataType != MetadataType.Void)
990-
{
991-
returnVariable = new VariableDefinition(methodBody.Method.ReturnType);
992-
methodBody.Variables.Add(returnVariable);
993-
}
994-
995-
var faultStart = Instruction.Create(OpCodes.Ldloca, probeVariable);
996-
var faultDispose = Instruction.Create(OpCodes.Call, probeDisposeMethod);
997-
var faultEnd = Instruction.Create(OpCodes.Endfinally);
998-
var epilogueStart = returnInstructions.Length == 0 ? null : Instruction.Create(OpCodes.Ldloca, probeVariable);
999-
1000-
foreach (var exceptionHandler in methodBody.ExceptionHandlers)
1001-
{
1002-
// Cecil represents "until the end of the method" with null. The generated outer
1003-
// fault handler extends the body, so existing regions must keep their original end.
1004-
exceptionHandler.TryEnd ??= faultStart;
1005-
exceptionHandler.HandlerEnd ??= faultStart;
1006-
}
1007-
1008-
if (epilogueStart is not null)
1009-
{
1010-
foreach (var returnInstruction in returnInstructions)
1011-
{
1012-
if (returnVariable is not null)
1013-
{
1014-
returnInstruction.OpCode = OpCodes.Stloc;
1015-
returnInstruction.Operand = returnVariable;
1016-
instructions.Insert(instructions.IndexOf(returnInstruction) + 1, Instruction.Create(OpCodes.Leave, epilogueStart));
1017-
}
1018-
else
1019-
{
1020-
returnInstruction.OpCode = OpCodes.Leave;
1021-
returnInstruction.Operand = epilogueStart;
1022-
}
1023-
}
1024-
}
1025-
1026-
instructions.Add(faultStart);
1027-
instructions.Add(faultDispose);
1028-
instructions.Add(faultEnd);
1029-
if (epilogueStart is not null)
1030-
{
1031-
instructions.Add(epilogueStart);
1032-
instructions.Add(Instruction.Create(OpCodes.Call, probeDisposeMethod));
1033-
if (returnVariable is not null)
1034-
{
1035-
instructions.Add(Instruction.Create(OpCodes.Ldloc, returnVariable));
1036-
}
1037-
1038-
instructions.Add(Instruction.Create(OpCodes.Ret));
1039-
}
1040-
1041-
methodBody.ExceptionHandlers.Add(
1042-
new ExceptionHandler(ExceptionHandlerType.Fault)
1043-
{
1044-
TryStart = protectedStart,
1045-
TryEnd = faultStart,
1046-
HandlerStart = faultStart,
1047-
HandlerEnd = epilogueStart,
1048-
});
1049-
}
1050-
1051958
private static void RemoveShortOpCodes(Instruction instruction)
1052959
{
1053960
if (instruction.OpCode == OpCodes.Br_S) { instruction.OpCode = OpCodes.Br; }

tracer/src/Datadog.Trace/Ci/Coverage/CoverageContextContainer.cs

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal sealed class CoverageContextContainer : IDisposable
2323
private ModuleValue? _currentModuleValue;
2424
private int _closed;
2525
private int _disposed;
26-
private int _retired;
2726

2827
public CoverageContextContainer(object? state = null, ModuleValue.BufferKind bufferKind = ModuleValue.BufferKind.Context)
2928
{
@@ -37,12 +36,14 @@ public CoverageContextContainer(object? state = null, ModuleValue.BufferKind buf
3736

3837
public ModuleValue? GetModuleValue(Module module)
3938
{
40-
if (IsClosed)
39+
// This is the normal probe path. Session closure is coordinated by the test lifecycle,
40+
// so preserve the historical lock-free lookup and reserve the lock for cache misses.
41+
if (_closed != 0)
4142
{
4243
return null;
4344
}
4445

45-
if (Volatile.Read(ref _currentModuleValue) is { } current && current.Module == module)
46+
if (_currentModuleValue is { } current && current.Module == module)
4647
{
4748
return current;
4849
}
@@ -134,28 +135,6 @@ public ModuleValue[] SnapshotModules(int maximumModules = int.MaxValue)
134135

135136
public void Clear() => Dispose();
136137

137-
public void RetireModules(
138-
Action? onRetirementPending,
139-
Action<ModuleValue, bool>? onRetirementCompleted,
140-
bool mergeOnCompletion)
141-
{
142-
lock (_gate)
143-
{
144-
if (_retired != 0)
145-
{
146-
return;
147-
}
148-
149-
_retired = 1;
150-
_closed = 1;
151-
_currentModuleValue = null;
152-
foreach (var moduleValue in _modules)
153-
{
154-
moduleValue.Retire(onRetirementPending, onRetirementCompleted, mergeOnCompletion);
155-
}
156-
}
157-
}
158-
159138
public void Dispose()
160139
{
161140
ExceptionDispatchInfo? firstException = null;
@@ -171,15 +150,6 @@ public void Dispose()
171150
_currentModuleValue = null;
172151
try
173152
{
174-
if (_retired == 0)
175-
{
176-
_retired = 1;
177-
foreach (var moduleValue in _modules)
178-
{
179-
moduleValue.Retire(onRetirementPending: null, onRetirementCompleted: null, mergeOnCompletion: false);
180-
}
181-
}
182-
183153
foreach (var moduleValue in _modules)
184154
{
185155
try

tracer/src/Datadog.Trace/Ci/Coverage/CoverageEventHandler.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,11 @@ namespace Datadog.Trace.Ci.Coverage;
1919
internal abstract class CoverageEventHandler
2020
{
2121
private readonly AsyncLocal<CoverageContextContainer?> _asyncContext = new();
22-
private readonly CoverageContextContainer _discardContainer = new(bufferKind: ModuleValue.BufferKind.GlobalFallback);
2322
private readonly CoverageContextContainer _globalContainer = new(bufferKind: ModuleValue.BufferKind.GlobalFallback);
2423
private readonly CoverageContextDiagnostics _contextDiagnostics = new();
2524

2625
public CoverageContextContainer? Container => _asyncContext.Value;
2726

28-
public CoverageContextContainer DiscardContainer => _discardContainer;
29-
3027
public CoverageContextContainer GlobalContainer => _globalContainer;
3128

3229
public CoverageContextDiagnosticSnapshot ContextDiagnostics => _contextDiagnostics.GetSnapshot();
@@ -100,7 +97,6 @@ public CoverageSessionHandle StartSession(string? testingFramework = null)
10097
}
10198

10299
_contextDiagnostics.RecordClosed();
103-
context.RetireModules(GetRetirementPendingCallback(), GetRetirementCompletedCallback(), mergeOnCompletion: false);
104100
try
105101
{
106102
var sessionEndData = OnSessionFinished(context, modules);
@@ -182,10 +178,6 @@ protected virtual bool TryBeginSessionStartAdmission(out CoverageContextAdmissio
182178

183179
protected virtual CoverageContextContainer CreateContext(object? state) => new(state);
184180

185-
protected virtual Action? GetRetirementPendingCallback() => null;
186-
187-
protected virtual Action<ModuleValue, bool>? GetRetirementCompletedCallback() => null;
188-
189181
protected virtual void InstallContext(CoverageContextContainer context) => _asyncContext.Value = context;
190182

191183
protected virtual void MarkGlobalCoverageIncomplete(GlobalCoverageFailureReason reason)

tracer/src/Datadog.Trace/Ci/Coverage/CoverageProbe.cs

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)