@@ -6,48 +6,85 @@ namespace Sentry.Internal;
66internal class CacheDirectoryCoordinator : IDisposable
77{
88 private readonly IDiagnosticLogger ? _logger ;
9- private readonly Semaphore _semaphore ;
9+ private readonly object _gate = new ( ) ;
10+
11+ private FileStream ? _lockStream ;
12+ private readonly string _lockFilePath ;
13+
1014 private bool _acquired ;
1115 private bool _disposed ;
12- private readonly Lock _gate = new ( ) ;
1316
14- public CacheDirectoryCoordinator ( string cacheDir , IDiagnosticLogger ? logger )
17+ public CacheDirectoryCoordinator ( string cacheDir , IDiagnosticLogger ? logger , IFileSystem fileSystem )
1518 {
1619 _logger = logger ;
17- var mutexName = BuildMutexName ( cacheDir ) ;
18- _semaphore = new Semaphore ( 1 , 1 , mutexName ) ; // Named mutexes allow interprocess locks on all platforms
19- }
20+ _lockFilePath = $ "{ cacheDir } .lock";
2021
21- private static string BuildMutexName ( string path )
22- {
23- var hash = path . GetHashString ( ) ;
24- // Global\ prefix allows cross-session visibility on Windows Terminal Services
25- return $ "Global\\ SentryCache{ hash } ";
22+ try
23+ {
24+ var baseDir = Path . GetDirectoryName ( _lockFilePath ) ;
25+ if ( ! string . IsNullOrWhiteSpace ( baseDir ) )
26+ {
27+ // Not normally necessary, but just in case
28+ fileSystem . CreateDirectory ( baseDir ) ;
29+ }
30+ }
31+ catch ( Exception ex )
32+ {
33+ _logger ? . LogError ( "Failed to ensure lock directory exists for cache coordinator." , ex ) ;
34+ }
2635 }
2736
28- /// <summary>
29- /// Try to own this cache directory.
30- /// </summary>
31- /// <param name="timeout">How long to wait for the lock.</param>
32- /// <returns>True if acquired; false otherwise.</returns>
3337 public bool TryAcquire ( TimeSpan timeout )
3438 {
3539 if ( _acquired )
3640 {
3741 return true ;
3842 }
43+
3944 lock ( _gate )
4045 {
4146 if ( _acquired )
4247 {
4348 return true ;
4449 }
45- if ( ! _semaphore . WaitOne ( timeout ) )
50+
51+ if ( _disposed )
4652 {
4753 return false ;
4854 }
49- _acquired = true ;
50- return true ;
55+
56+ try
57+ {
58+ // Note that FileShare.None is implemented via advisory locks only on macOS/Linux... so it will stop
59+ // other .NET processes from accessing the file but not other non-.NET processes. This should be fine
60+ // in our case - we just want to avoid multiple instances of the SDK concurrently accessing the cache
61+ _lockStream = new FileStream (
62+ _lockFilePath ,
63+ FileMode . OpenOrCreate ,
64+ FileAccess . ReadWrite ,
65+ FileShare . None ) ;
66+
67+ _acquired = true ;
68+ return true ;
69+ }
70+ catch ( Exception ex )
71+ {
72+ _logger ? . LogDebug ( "Unable to acquire cache directory lock" , ex ) ;
73+ }
74+ finally
75+ {
76+ if ( ! _acquired && _lockStream is not null )
77+ {
78+ try { _lockStream . Dispose ( ) ; }
79+ catch
80+ {
81+ // Ignore
82+ }
83+ _lockStream = null ;
84+ }
85+ }
86+
87+ return false ;
5188 }
5289 }
5390
@@ -56,16 +93,33 @@ public void Dispose()
5693 lock ( _gate )
5794 {
5895 if ( _disposed )
96+ {
5997 return ;
98+ }
6099
61100 _disposed = true ;
101+
62102 if ( _acquired )
63103 {
64104 try
65- { _semaphore . Release ( ) ; }
66- catch ( Exception ex ) { _logger ? . LogError ( "Error releasing the cache directory semaphore." , ex ) ; }
105+ {
106+ _lockStream ? . Close ( ) ;
107+ }
108+ catch ( Exception ex )
109+ {
110+ _logger ? . LogError ( "Error releasing the cache directory file lock." , ex ) ;
111+ }
112+ }
113+
114+ try
115+ {
116+ _lockStream ? . Dispose ( ) ;
117+ }
118+ catch ( Exception ex )
119+ {
120+ _logger ? . LogError ( "Error disposing cache lock stream." , ex ) ;
67121 }
68- _semaphore . Dispose ( ) ;
122+ _lockStream = null ;
69123 }
70124 }
71125}
@@ -79,7 +133,7 @@ internal static class CacheDirectoryHelper
79133 ? null
80134 : Path . Combine ( options . CacheDirectoryPath , "Sentry" ) ;
81135
82- internal static string ? GetIsolatedFolderName ( this SentryOptions options )
136+ private static string ? GetIsolatedFolderName ( this SentryOptions options )
83137 {
84138 var stringBuilder = new StringBuilder ( IsolatedCacheDirectoryPrefix ) ;
85139#if IOS || ANDROID
0 commit comments