Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 10 additions & 17 deletions src/coverlet.template/ModuleTrackerTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@ public static class ModuleTrackerTemplate
public static string HitsFilePath;
public static int[] HitsArray;

// Special case when instrumenting CoreLib, the static below prevents infinite loop in CoreLib
// while allowing the tracker template to call any of its types and functions.
private static bool s_isTracking;

static ModuleTrackerTemplate()
{
s_isTracking = true;
AppDomain.CurrentDomain.ProcessExit += new EventHandler(UnloadModule);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadModule);
s_isTracking = false;

// At the end of the instrumentation of a module, the instrumenter needs to add code here
// to initialize the static fields according to the values derived from the instrumentation of
Expand All @@ -38,15 +32,18 @@ static ModuleTrackerTemplate()

public static void RecordHit(int hitLocationIndex)
{
if (s_isTracking)
// Make sure to avoid recording if this is a call to RecordHit within the AppDomain setup code in an
// instrumented build of System.Private.CoreLib.
if (HitsArray is null)
return;

Interlocked.Increment(ref HitsArray[hitLocationIndex]);
}

public static void UnloadModule(object sender, EventArgs e)
{
s_isTracking = true;
// Claim the current hits array and reset it to prevent double-counting scenarios.
var hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]);

// The same module can be unloaded multiple times in the same process via different app domains.
// Use a global mutex to ensure no concurrent access.
Expand All @@ -61,8 +58,8 @@ public static void UnloadModule(object sender, EventArgs e)
using (var fs = new FileStream(HitsFilePath, FileMode.CreateNew))
using (var bw = new BinaryWriter(fs))
{
bw.Write(HitsArray.Length);
foreach (int hitCount in HitsArray)
bw.Write(hitsArray.Length);
foreach (int hitCount in hitsArray)
{
bw.Write(hitCount);
}
Expand All @@ -82,25 +79,21 @@ public static void UnloadModule(object sender, EventArgs e)
using (var bw = new BinaryWriter(fs))
{
int hitsLength = br.ReadInt32();
if (hitsLength != HitsArray.Length)
if (hitsLength != hitsArray.Length)
{
throw new InvalidOperationException(
$"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {HitsArray.Length}");
$"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {hitsArray.Length}");
}

for (int i = 0; i < hitsLength; ++i)
{
int oldHitCount = br.ReadInt32();
bw.Seek(-sizeof(int), SeekOrigin.Current);
bw.Write(HitsArray[i] + oldHitCount);
bw.Write(hitsArray[i] + oldHitCount);
}
}
}

// Prevent any double counting scenario, i.e.: UnloadModule called twice (not sure if this can happen in practice ...)
// Only an issue if DomainUnload and ProcessExit can both happens, perhaps can be removed...
Array.Clear(HitsArray, 0, HitsArray.Length);

// On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file
// this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
mutex.ReleaseMutex();
Expand Down