@@ -22,9 +22,11 @@ internal class CachingTransport : ITransport, IDisposable
2222 private readonly ITransport _innerTransport ;
2323 private readonly SentryOptions _options ;
2424 private readonly bool _failStorage ;
25- private readonly string _isolatedCacheDirectoryPath ;
2625 private readonly int _keepCount ;
2726
27+ private readonly CacheDirectoryCoordinator _cacheCoordinator ;
28+ private readonly string _isolatedCacheDirectoryPath ;
29+
2830 // When a file is getting processed, it's moved to a child directory
2931 // to avoid getting picked up by other threads.
3032 private readonly string _processingDirectoryPath ;
@@ -45,6 +47,7 @@ internal class CachingTransport : ITransport, IDisposable
4547
4648 private readonly CancellationTokenSource _workerCts = new ( ) ;
4749 private Task _worker = null ! ;
50+ private Task ? _recycler = null ;
4851
4952 private ManualResetEventSlim ? _initCacheResetEvent = new ( ) ;
5053 private ManualResetEventSlim ? _preInitCacheResetEvent = new ( ) ;
@@ -77,6 +80,11 @@ private CachingTransport(ITransport innerTransport, SentryOptions options, bool
7780 _isolatedCacheDirectoryPath =
7881 options . TryGetIsolatedCacheDirectoryPath ( ) ??
7982 throw new InvalidOperationException ( "Cache directory or DSN is not set." ) ;
83+ _cacheCoordinator = new CacheDirectoryCoordinator ( _isolatedCacheDirectoryPath , options . DiagnosticLogger ) ;
84+ if ( ! _cacheCoordinator . TryAcquire ( TimeSpan . FromMilliseconds ( 100 ) ) )
85+ {
86+ throw new InvalidOperationException ( "Cache directory already locked." ) ;
87+ }
8088
8189 // Sanity check: This should never happen in the first place.
8290 // We check for `DisableFileWrite` before creating the CachingTransport.
@@ -99,6 +107,9 @@ private void Initialize(bool startWorker)
99107 {
100108 _options . LogDebug ( "Starting CachingTransport worker." ) ;
101109 _worker = Task . Run ( CachedTransportBackgroundTaskAsync ) ;
110+
111+ // Start a recycler in the background so as not to block initialisation
112+ // _recycler = Task.Run(SalvageAbandonedCacheSessions);
102113 }
103114 else
104115 {
@@ -178,6 +189,36 @@ private async Task CachedTransportBackgroundTaskAsync()
178189 _options . LogDebug ( "CachingTransport worker stopped." ) ;
179190 }
180191
192+ private void SalvageAbandonedCacheSessions ( )
193+ {
194+ if ( _options . GetBaseCacheDirectoryPath ( ) is not { } baseCacheDir )
195+ {
196+ return ;
197+ }
198+
199+ const string searchPattern = $ "{ CacheDirectoryHelper . IsolatedCacheDirectoryPrefix } *";
200+ foreach ( var dir in _fileSystem . EnumerateDirectories ( baseCacheDir , searchPattern ) )
201+ {
202+ if ( string . Equals ( dir , _isolatedCacheDirectoryPath , StringComparison . OrdinalIgnoreCase ) )
203+ {
204+ continue ; // Ignore the current cache directory
205+ }
206+
207+ _options . LogDebug ( "found abandoned cache: {0}" , dir ) ;
208+ using var coordinator = new CacheDirectoryCoordinator ( dir , _options . DiagnosticLogger ) ;
209+ if ( coordinator . TryAcquire ( TimeSpan . FromMilliseconds ( 100 ) ) )
210+ {
211+ _options . LogDebug ( "Salvaging abandoned cache: {0}" , dir ) ;
212+ foreach ( var filePath in _fileSystem . EnumerateFiles ( dir , "*.envelope" , SearchOption . AllDirectories ) )
213+ {
214+ var destinationPath = Path . Combine ( _isolatedCacheDirectoryPath , Path . GetFileName ( filePath ) ) ;
215+ _options . LogDebug ( "Moving abandoned file back to cache: {0} to {1}." , filePath , destinationPath ) ;
216+ MoveFileWithRetries ( filePath , destinationPath , "abandoned" ) ;
217+ }
218+ }
219+ }
220+ }
221+
181222 private void MoveUnprocessedFilesBackToCache ( )
182223 {
183224 // Processing directory may already contain some files left from previous session
@@ -194,42 +235,46 @@ private void MoveUnprocessedFilesBackToCache()
194235 {
195236 var destinationPath = Path . Combine ( _isolatedCacheDirectoryPath , Path . GetFileName ( filePath ) ) ;
196237 _options . LogDebug ( "Moving unprocessed file back to cache: {0} to {1}." , filePath , destinationPath ) ;
238+ MoveFileWithRetries ( filePath , destinationPath , "unprocessed" ) ;
239+ }
240+ }
197241
198- const int maxAttempts = 3 ;
199- for ( var attempt = 1 ; attempt <= maxAttempts ; attempt ++ )
242+ private void MoveFileWithRetries ( string filePath , string destinationPath , string moveType )
243+ {
244+ const int maxAttempts = 3 ;
245+ for ( var attempt = 1 ; attempt <= maxAttempts ; attempt ++ )
246+ {
247+ try
200248 {
201- try
249+ _fileSystem . MoveFile ( filePath , destinationPath ) ;
250+ break ;
251+ }
252+ catch ( Exception ex )
253+ {
254+ if ( ! _fileSystem . FileExists ( filePath ) )
202255 {
203- _fileSystem . MoveFile ( filePath , destinationPath ) ;
256+ _options . LogDebug (
257+ "Failed to move {2} file back to cache (attempt {0}), " +
258+ "but the file no longer exists so it must have been handled by another process: {1}" ,
259+ attempt , filePath , moveType ) ;
204260 break ;
205261 }
206- catch ( Exception ex )
207- {
208- if ( ! _fileSystem . FileExists ( filePath ) )
209- {
210- _options . LogDebug (
211- "Failed to move unprocessed file back to cache (attempt {0}), " +
212- "but the file no longer exists so it must have been handled by another process: {1}" ,
213- attempt , filePath ) ;
214- break ;
215- }
216262
217- if ( attempt < maxAttempts )
218- {
219- _options . LogDebug (
220- "Failed to move unprocessed file back to cache (attempt {0}, retrying.): {1}" ,
221- attempt , filePath ) ;
222-
223- Thread . Sleep ( 200 ) ; // give a small bit of time before retry
224- }
225- else
226- {
227- _options . LogError ( ex ,
228- "Failed to move unprocessed file back to cache (attempt {0}, done.): {1}" , attempt , filePath ) ;
229- }
263+ if ( attempt < maxAttempts )
264+ {
265+ _options . LogDebug (
266+ "Failed to move {2} file back to cache (attempt {0}, retrying.): {1}" ,
267+ attempt , filePath , moveType ) ;
230268
231- // note: we do *not* want to re-throw the exception
269+ Thread . Sleep ( 200 ) ; // give a small bit of time before retry
232270 }
271+ else
272+ {
273+ _options . LogError ( ex ,
274+ "Failed to move {2} file back to cache (attempt {0}, done.): {1}" , attempt , filePath , moveType ) ;
275+ }
276+
277+ // note: we do *not* want to re-throw the exception
233278 }
234279 }
235280 }
@@ -560,7 +605,9 @@ public async ValueTask DisposeAsync()
560605 _workerSignal . Dispose ( ) ;
561606 _workerCts . Dispose ( ) ;
562607 _worker . Dispose ( ) ;
608+ _recycler ? . Dispose ( ) ;
563609 _cacheDirectoryLock . Dispose ( ) ;
610+ _cacheCoordinator . Dispose ( ) ;
564611 _preInitCacheResetEvent ? . Dispose ( ) ;
565612 _initCacheResetEvent ? . Dispose ( ) ;
566613
0 commit comments