11using System ;
22using System . Collections . Generic ;
33using System . ComponentModel . Composition ;
4- using System . Globalization ;
54using System . IO ;
65using System . Text ;
76using System . Threading ;
1716namespace GitHub . Services
1817{
1918 [ Export ( typeof ( IUsageService ) ) ]
20- public class UsageService : IUsageService
19+ public sealed class UsageService : IUsageService , IDisposable
2120 {
2221 const string StoreFileName = "metrics.json" ;
2322 const string UserStoreFileName = "user.json" ;
2423 static readonly ILogger log = LogManager . ForContext < UsageService > ( ) ;
2524
2625 readonly IGitHubServiceProvider serviceProvider ;
2726 readonly IEnvironment environment ;
27+ readonly SemaphoreSlim writeSemaphoreSlim = new SemaphoreSlim ( 1 , 1 ) ;
2828
2929 string storePath ;
3030 string userStorePath ;
@@ -37,6 +37,11 @@ public UsageService(IGitHubServiceProvider serviceProvider, IEnvironment environ
3737 this . environment = environment ;
3838 }
3939
40+ public void Dispose ( )
41+ {
42+ writeSemaphoreSlim . Dispose ( ) ;
43+ }
44+
4045 public async Task < Guid > GetUserGuid ( )
4146 {
4247 await Initialize ( ) ;
@@ -102,7 +107,7 @@ public async Task<UsageData> ReadLocalData()
102107 SimpleJson . DeserializeObject < UsageData > ( json ) :
103108 new UsageData { Reports = new List < UsageModel > ( ) } ;
104109 }
105- catch ( Exception ex )
110+ catch ( Exception ex )
106111 {
107112 log . Error ( ex , "Error deserializing usage" ) ;
108113 return new UsageData { Reports = new List < UsageModel > ( ) } ;
@@ -115,11 +120,12 @@ public async Task WriteLocalData(UsageData data)
115120 {
116121 Directory . CreateDirectory ( Path . GetDirectoryName ( storePath ) ) ;
117122 var json = SimpleJson . SerializeObject ( data ) ;
123+
118124 await WriteAllTextAsync ( storePath , json ) ;
119125 }
120126 catch ( Exception ex )
121127 {
122- log . Error ( ex , "Failed to write usage data" ) ;
128+ log . Error ( ex , "Failed to write usage data" ) ;
123129 }
124130 }
125131
@@ -149,10 +155,19 @@ async Task<string> ReadAllTextAsync(string path)
149155
150156 async Task WriteAllTextAsync ( string path , string text )
151157 {
152- using ( var s = new FileStream ( path , FileMode . Create ) )
153- using ( var w = new StreamWriter ( s , Encoding . UTF8 ) )
158+ // Avoid IOException when metrics updated multiple times in quick succession
159+ await writeSemaphoreSlim . WaitAsync ( ) ;
160+ try
161+ {
162+ using ( var s = new FileStream ( path , FileMode . Create ) )
163+ using ( var w = new StreamWriter ( s , Encoding . UTF8 ) )
164+ {
165+ await w . WriteAsync ( text ) ;
166+ }
167+ }
168+ finally
154169 {
155- await w . WriteAsync ( text ) ;
170+ writeSemaphoreSlim . Release ( ) ;
156171 }
157172 }
158173
0 commit comments