1
1
using System ;
2
2
using System . Diagnostics . CodeAnalysis ;
3
3
using System . IO ;
4
+ using System . Reflection ;
4
5
using System . Threading ;
5
6
6
7
namespace Coverlet . Core . Instrumentation
@@ -19,6 +20,7 @@ public static class ModuleTrackerTemplate
19
20
public static string HitsFilePath ;
20
21
public static int [ ] HitsArray ;
21
22
public static bool SingleHit ;
23
+ private static bool _enableLog = int . TryParse ( Environment . GetEnvironmentVariable ( "COVERLET_ENABLETRACKERLOG" ) , out int result ) ? result == 1 : false ;
22
24
23
25
static ModuleTrackerTemplate ( )
24
26
{
@@ -72,64 +74,93 @@ public static void RecordSingleHit(int hitLocationIndex)
72
74
73
75
public static void UnloadModule ( object sender , EventArgs e )
74
76
{
75
- // Claim the current hits array and reset it to prevent double-counting scenarios.
76
- var hitsArray = Interlocked . Exchange ( ref HitsArray , new int [ HitsArray . Length ] ) ;
77
-
78
- // The same module can be unloaded multiple times in the same process via different app domains.
79
- // Use a global mutex to ensure no concurrent access.
80
- using ( var mutex = new Mutex ( true , Path . GetFileNameWithoutExtension ( HitsFilePath ) + "_Mutex" , out bool createdNew ) )
77
+ try
81
78
{
82
- if ( ! createdNew )
83
- mutex . WaitOne ( ) ;
79
+ WriteLog ( $ "Unload called for '{ Assembly . GetExecutingAssembly ( ) . Location } '") ;
80
+ // Claim the current hits array and reset it to prevent double-counting scenarios.
81
+ var hitsArray = Interlocked . Exchange ( ref HitsArray , new int [ HitsArray . Length ] ) ;
84
82
85
- bool failedToCreateNewHitsFile = false ;
86
- try
83
+ // The same module can be unloaded multiple times in the same process via different app domains.
84
+ // Use a global mutex to ensure no concurrent access.
85
+ using ( var mutex = new Mutex ( true , Path . GetFileNameWithoutExtension ( HitsFilePath ) + "_Mutex" , out bool createdNew ) )
87
86
{
88
- using ( var fs = new FileStream ( HitsFilePath , FileMode . CreateNew ) )
89
- using ( var bw = new BinaryWriter ( fs ) )
87
+ WriteLog ( $ "Flushing hit file '{ HitsFilePath } '") ;
88
+ if ( ! createdNew )
89
+ mutex . WaitOne ( ) ;
90
+
91
+ bool failedToCreateNewHitsFile = false ;
92
+ try
90
93
{
91
- bw . Write ( hitsArray . Length ) ;
92
- foreach ( int hitCount in hitsArray )
94
+ using ( var fs = new FileStream ( HitsFilePath , FileMode . CreateNew ) )
95
+ using ( var bw = new BinaryWriter ( fs ) )
93
96
{
94
- bw . Write ( hitCount ) ;
97
+ bw . Write ( hitsArray . Length ) ;
98
+ foreach ( int hitCount in hitsArray )
99
+ {
100
+ bw . Write ( hitCount ) ;
101
+ }
95
102
}
96
103
}
97
- }
98
- catch
99
- {
100
- failedToCreateNewHitsFile = true ;
101
- }
104
+ catch
105
+ {
106
+ failedToCreateNewHitsFile = true ;
107
+ }
102
108
103
- if ( failedToCreateNewHitsFile )
104
- {
105
- // Update the number of hits by adding value on disk with the ones on memory.
106
- // This path should be triggered only in the case of multiple AppDomain unloads.
107
- using ( var fs = new FileStream ( HitsFilePath , FileMode . Open , FileAccess . ReadWrite , FileShare . None ) )
108
- using ( var br = new BinaryReader ( fs ) )
109
- using ( var bw = new BinaryWriter ( fs ) )
109
+ if ( failedToCreateNewHitsFile )
110
110
{
111
- int hitsLength = br . ReadInt32 ( ) ;
112
- if ( hitsLength != hitsArray . Length )
111
+ // Update the number of hits by adding value on disk with the ones on memory.
112
+ // This path should be triggered only in the case of multiple AppDomain unloads.
113
+ using ( var fs = new FileStream ( HitsFilePath , FileMode . Open , FileAccess . ReadWrite , FileShare . None ) )
114
+ using ( var br = new BinaryReader ( fs ) )
115
+ using ( var bw = new BinaryWriter ( fs ) )
113
116
{
114
- throw new InvalidOperationException (
115
- $ "{ HitsFilePath } has { hitsLength } entries but on memory { nameof ( HitsArray ) } has { hitsArray . Length } ") ;
116
- }
117
+ int hitsLength = br . ReadInt32 ( ) ;
118
+ if ( hitsLength != hitsArray . Length )
119
+ {
120
+ throw new InvalidOperationException (
121
+ $ "{ HitsFilePath } has { hitsLength } entries but on memory { nameof ( HitsArray ) } has { hitsArray . Length } ") ;
122
+ }
117
123
118
- for ( int i = 0 ; i < hitsLength ; ++ i )
119
- {
120
- int oldHitCount = br . ReadInt32 ( ) ;
121
- bw . Seek ( - sizeof ( int ) , SeekOrigin . Current ) ;
122
- if ( SingleHit )
123
- bw . Write ( hitsArray [ i ] + oldHitCount > 0 ? 1 : 0 ) ;
124
- else
125
- bw . Write ( hitsArray [ i ] + oldHitCount ) ;
124
+ for ( int i = 0 ; i < hitsLength ; ++ i )
125
+ {
126
+ int oldHitCount = br . ReadInt32 ( ) ;
127
+ bw . Seek ( - sizeof ( int ) , SeekOrigin . Current ) ;
128
+ if ( SingleHit )
129
+ bw . Write ( hitsArray [ i ] + oldHitCount > 0 ? 1 : 0 ) ;
130
+ else
131
+ bw . Write ( hitsArray [ i ] + oldHitCount ) ;
132
+ }
126
133
}
127
134
}
135
+
136
+ // 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
137
+ // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
138
+ mutex . ReleaseMutex ( ) ;
139
+ WriteLog ( $ "Hit file '{ HitsFilePath } ' flushed, size { new FileInfo ( HitsFilePath ) . Length } ") ;
128
140
}
141
+ }
142
+ catch ( Exception ex )
143
+ {
144
+ WriteLog ( ex . ToString ( ) ) ;
145
+ throw ;
146
+ }
147
+ }
129
148
130
- // 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
131
- // this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
132
- mutex . ReleaseMutex ( ) ;
149
+ private static void WriteLog ( string logText )
150
+ {
151
+ if ( _enableLog )
152
+ {
153
+ try
154
+ {
155
+ // We don't set path as global var to keep benign possible errors inside try/catch
156
+ // I'm not sure that location will be ok in every scenario
157
+ string location = Assembly . GetExecutingAssembly ( ) . Location ;
158
+ File . AppendAllText ( Path . Combine ( Path . GetDirectoryName ( location ) , Path . GetFileName ( location ) + "_tracker.txt" ) , $ "[{ DateTime . UtcNow } { Thread . CurrentThread . ManagedThreadId } ]{ logText } { Environment . NewLine } ") ;
159
+ }
160
+ catch
161
+ {
162
+ // do nothing if log fail
163
+ }
133
164
}
134
165
}
135
166
}
0 commit comments