@@ -21,6 +21,7 @@ internal static class ModuleTrackerTemplate
2121 public static string HitsFilePath ;
2222 public static int [ ] HitsArray ;
2323 public static bool SingleHit ;
24+ public static bool FlushHitFile ;
2425 private static readonly bool _enableLog = int . TryParse ( Environment . GetEnvironmentVariable ( "COVERLET_ENABLETRACKERLOG" ) , out int result ) ? result == 1 : false ;
2526
2627 static ModuleTrackerTemplate ( )
@@ -75,84 +76,95 @@ public static void RecordSingleHit(int hitLocationIndex)
7576
7677 public static void UnloadModule ( object sender , EventArgs e )
7778 {
78- try
79+ // The same module can be unloaded multiple times in the same process via different app domains.
80+ // Use a global mutex to ensure no concurrent access.
81+ using ( var mutex = new Mutex ( true , Path . GetFileNameWithoutExtension ( HitsFilePath ) + "_Mutex" , out bool createdNew ) )
7982 {
80- WriteLog ( $ "Unload called for '{ Assembly . GetExecutingAssembly ( ) . Location } '") ;
81- // Claim the current hits array and reset it to prevent double-counting scenarios.
82- int [ ] hitsArray = Interlocked . Exchange ( ref HitsArray , new int [ HitsArray . Length ] ) ;
83-
84- // The same module can be unloaded multiple times in the same process via different app domains.
85- // Use a global mutex to ensure no concurrent access.
86- using ( var mutex = new Mutex ( true , Path . GetFileNameWithoutExtension ( HitsFilePath ) + "_Mutex" , out bool createdNew ) )
83+ if ( ! createdNew )
8784 {
88- WriteLog ( $ "Flushing hit file '{ HitsFilePath } '") ;
89- if ( ! createdNew )
90- mutex . WaitOne ( ) ;
85+ mutex . WaitOne ( ) ;
86+ }
9187
92- bool failedToCreateNewHitsFile = false ;
88+ if ( FlushHitFile )
89+ {
9390 try
9491 {
95- using ( var fs = new FileStream ( HitsFilePath , FileMode . CreateNew ) )
96- using ( var bw = new BinaryWriter ( fs ) )
92+ // Claim the current hits array and reset it to prevent double-counting scenarios.
93+ int [ ] hitsArray = Interlocked . Exchange ( ref HitsArray , new int [ HitsArray . Length ] ) ;
94+
95+ WriteLog ( $ "Unload called for '{ Assembly . GetExecutingAssembly ( ) . Location } ' by '{ sender ?? "null" } '") ;
96+ WriteLog ( $ "Flushing hit file '{ HitsFilePath } '") ;
97+
98+ bool failedToCreateNewHitsFile = false ;
99+ try
97100 {
98- bw . Write ( hitsArray . Length ) ;
99- foreach ( int hitCount in hitsArray )
101+ using ( var fs = new FileStream ( HitsFilePath , FileMode . CreateNew ) )
102+ using ( var bw = new BinaryWriter ( fs ) )
100103 {
101- bw . Write ( hitCount ) ;
104+ bw . Write ( hitsArray . Length ) ;
105+ foreach ( int hitCount in hitsArray )
106+ {
107+ bw . Write ( hitCount ) ;
108+ }
102109 }
103110 }
104- }
105- catch ( Exception ex )
106- {
107- WriteLog ( $ "Failed to create new hits file '{ HitsFilePath } '\n { ex } ") ;
108- failedToCreateNewHitsFile = true ;
109- }
110-
111- if ( failedToCreateNewHitsFile )
112- {
113- // Update the number of hits by adding value on disk with the ones on memory.
114- // This path should be triggered only in the case of multiple AppDomain unloads.
115- using ( var fs = new FileStream ( HitsFilePath , FileMode . Open , FileAccess . ReadWrite , FileShare . None ) )
116- using ( var br = new BinaryReader ( fs ) )
117- using ( var bw = new BinaryWriter ( fs ) )
111+ catch ( Exception ex )
118112 {
119- int hitsLength = br . ReadInt32 ( ) ;
120- WriteLog ( $ "Current hits found '{ hitsLength } '") ;
121-
122- if ( hitsLength != hitsArray . Length )
123- {
124- throw new InvalidOperationException (
125- $ "{ HitsFilePath } has { hitsLength } entries but on memory { nameof ( HitsArray ) } has { hitsArray . Length } ") ;
126- }
113+ WriteLog ( $ "Failed to create new hits file '{ HitsFilePath } ' -> '{ ex . Message } '") ;
114+ failedToCreateNewHitsFile = true ;
115+ }
127116
128- for ( int i = 0 ; i < hitsLength ; ++ i )
117+ if ( failedToCreateNewHitsFile )
118+ {
119+ // Update the number of hits by adding value on disk with the ones on memory.
120+ // This path should be triggered only in the case of multiple AppDomain unloads.
121+ using ( var fs = new FileStream ( HitsFilePath , FileMode . Open , FileAccess . ReadWrite , FileShare . None ) )
122+ using ( var br = new BinaryReader ( fs ) )
123+ using ( var bw = new BinaryWriter ( fs ) )
129124 {
130- int oldHitCount = br . ReadInt32 ( ) ;
131- bw . Seek ( - sizeof ( int ) , SeekOrigin . Current ) ;
132- if ( SingleHit )
133- bw . Write ( hitsArray [ i ] + oldHitCount > 0 ? 1 : 0 ) ;
134- else
135- bw . Write ( hitsArray [ i ] + oldHitCount ) ;
125+ int hitsLength = br . ReadInt32 ( ) ;
126+ WriteLog ( $ "Current hits found '{ hitsLength } '") ;
127+
128+ if ( hitsLength != hitsArray . Length )
129+ {
130+ throw new InvalidOperationException ( $ "{ HitsFilePath } has { hitsLength } entries but on memory { nameof ( HitsArray ) } has { hitsArray . Length } ") ;
131+ }
132+
133+ for ( int i = 0 ; i < hitsLength ; ++ i )
134+ {
135+ int oldHitCount = br . ReadInt32 ( ) ;
136+ bw . Seek ( - sizeof ( int ) , SeekOrigin . Current ) ;
137+ if ( SingleHit )
138+ {
139+ bw . Write ( hitsArray [ i ] + oldHitCount > 0 ? 1 : 0 ) ;
140+ }
141+ else
142+ {
143+ bw . Write ( hitsArray [ i ] + oldHitCount ) ;
144+ }
145+ }
136146 }
137147 }
138- }
139148
140- WriteHits ( ) ;
149+ WriteHits ( sender ) ;
141150
142- // 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
143- // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
144- mutex . ReleaseMutex ( ) ;
145- WriteLog ( $ "Hit file '{ HitsFilePath } ' flushed, size { new FileInfo ( HitsFilePath ) . Length } ") ;
151+ WriteLog ( $ "Hit file '{ HitsFilePath } ' flushed, size { new FileInfo ( HitsFilePath ) . Length } ") ;
152+ WriteLog ( "--------------------------------" ) ;
153+ }
154+ catch ( Exception ex )
155+ {
156+ WriteLog ( ex . ToString ( ) ) ;
157+ throw ;
158+ }
146159 }
147- }
148- catch ( Exception ex )
149- {
150- WriteLog ( ex . ToString ( ) ) ;
151- throw ;
160+
161+ // 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
162+ // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
163+ mutex . ReleaseMutex ( ) ;
152164 }
153165 }
154166
155- private static void WriteHits ( )
167+ private static void WriteHits ( object sender )
156168 {
157169 if ( _enableLog )
158170 {
@@ -172,7 +184,7 @@ private static void WriteHits()
172184 }
173185 }
174186
175- File . AppendAllText ( logFile , "Hits flushed" ) ;
187+ File . AppendAllText ( logFile , $ "Hits flushed file path { HitsFilePath } location ' { Assembly . GetExecutingAssembly ( ) . Location } ' by ' { sender ?? "null" } ' ") ;
176188 }
177189 }
178190
0 commit comments