From 1702daa444dd4dfc6c9d688d7db860dfb1065c4e Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 3 Dec 2025 12:47:51 +1300 Subject: [PATCH 1/2] Revert: Implement process and instance isolation Resolves #4775 - https://github.com/getsentry/sentry-dotnet/issues/4775 --- CHANGELOG.md | 5 + src/Sentry/GlobalSessionManager.cs | 5 +- .../Internal/CacheDirectoryCoordinator.cs | 146 ----------- src/Sentry/Internal/FileSystemBase.cs | 4 - src/Sentry/Internal/Http/CachingTransport.cs | 223 +++-------------- src/Sentry/Internal/IFileSystem.cs | 2 - src/Sentry/Internal/InitCounter.cs | 23 -- src/Sentry/Internal/Polyfills.cs | 30 --- src/Sentry/Internal/ReadOnlyFilesystem.cs | 6 - src/Sentry/Internal/ReadWriteFileSystem.cs | 20 -- src/Sentry/SentryOptions.cs | 43 ++-- src/Sentry/SentrySdk.cs | 2 - .../SentryMauiAppBuilderExtensionsTests.cs | 4 - test/Sentry.Testing/FakeFileSystem.cs | 13 - test/Sentry.Tests/HubTests.cs | 5 - .../CacheDirectoryCoordinatorTests.cs | 99 -------- .../Internals/Http/CachingTransportTests.cs | 230 +----------------- .../Internals/InitCounterTests.cs | 55 ----- test/Sentry.Tests/SentryClientTests.cs | 5 - test/Sentry.Tests/SentryOptionsTests.cs | 53 ---- test/Sentry.Tests/SentrySdkTests.cs | 35 +-- 21 files changed, 76 insertions(+), 932 deletions(-) delete mode 100644 src/Sentry/Internal/CacheDirectoryCoordinator.cs delete mode 100644 src/Sentry/Internal/InitCounter.cs delete mode 100644 test/Sentry.Tests/Internals/CacheDirectoryCoordinatorTests.cs delete mode 100644 test/Sentry.Tests/Internals/InitCounterTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index dd464bae93..d703f8cb0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ ### Features - Add support for _Structured Logs_ in `Sentry.Google.Cloud.Functions` ([#4700](https://github.com/getsentry/sentry-dotnet/pull/4700)) +- The SDK now makes use of the new SessionEndStatus `Unhandled` when capturing an unhandled but non-terminal exception, i.e. through the UnobservedTaskExceptionIntegration ([#4633](https://github.com/getsentry/sentry-dotnet/pull/4633), [#4653](https://github.com/getsentry/sentry-dotnet/pull/4653)) +- The SDK now provides a `IsSessionActive` to allow checking the session state ([#4662](https://github.com/getsentry/sentry-dotnet/pull/4662)) +- The SDK now makes use of the new SessionEndStatus `Unhandled` when capturing an unhandled but non-terminal exception, i.e. through the UnobservedTaskExceptionIntegration ([#4633](https://github.com/getsentry/sentry-dotnet/pull/4633)) +- Added experimental support for Session Replay on iOS ([#4664](https://github.com/getsentry/sentry-dotnet/pull/4664)) +- Extended the App context by `app_memory` that can hold the amount of memory used by the application in bytes. ([#4707](https://github.com/getsentry/sentry-dotnet/pull/4707)) ### Fixes diff --git a/src/Sentry/GlobalSessionManager.cs b/src/Sentry/GlobalSessionManager.cs index cfa27fe89b..8fb6cb8413 100644 --- a/src/Sentry/GlobalSessionManager.cs +++ b/src/Sentry/GlobalSessionManager.cs @@ -33,7 +33,10 @@ public GlobalSessionManager( _clock = clock ?? SystemClock.Clock; _persistedSessionProvider = persistedSessionProvider ?? (filePath => Json.Load(_options.FileSystem, filePath, PersistedSessionUpdate.FromJson)); - _persistenceDirectoryPath = options.GetIsolatedCacheDirectoryPath(); + + // TODO: session file should really be process-isolated, but we + // don't have a proper mechanism for that right now. + _persistenceDirectoryPath = options.TryGetDsnSpecificCacheDirectoryPath(); } // Take pause timestamp directly instead of referencing _lastPauseTimestamp to avoid diff --git a/src/Sentry/Internal/CacheDirectoryCoordinator.cs b/src/Sentry/Internal/CacheDirectoryCoordinator.cs deleted file mode 100644 index 3f8cb09cfd..0000000000 --- a/src/Sentry/Internal/CacheDirectoryCoordinator.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Sentry.Extensibility; -using Sentry.Internal.Extensions; - -namespace Sentry.Internal; - -internal class CacheDirectoryCoordinator : IDisposable -{ - private readonly IDiagnosticLogger? _logger; - private readonly IFileSystem _fileSystem; - private readonly Lock _lock = new(); - - private Stream? _lockStream; - private readonly string _lockFilePath; - - private volatile bool _acquired; - private volatile bool _disposed; - - public CacheDirectoryCoordinator(string cacheDir, IDiagnosticLogger? logger, IFileSystem fileSystem) - { - _logger = logger; - _fileSystem = fileSystem; - // Note this creates a lock file in the cache directory's parent directory... not in the cache directory itself - _lockFilePath = $"{cacheDir}.lock"; - } - - public bool TryAcquire() - { - if (_acquired) - { - return true; - } - - lock (_lock) - { - if (_acquired) - { - return true; - } - - if (_disposed) - { - return false; - } - - try - { - var baseDir = Path.GetDirectoryName(_lockFilePath); - if (!string.IsNullOrWhiteSpace(baseDir)) - { - _fileSystem.CreateDirectory(baseDir); - } - _acquired = _fileSystem.TryCreateLockFile(_lockFilePath, out _lockStream); - return _acquired; - } - catch (Exception ex) - { - _logger?.LogDebug("Unable to acquire cache directory lock", ex); - } - finally - { - if (!_acquired && _lockStream is not null) - { - try - { _lockStream.Dispose(); } - catch - { - // Ignore - } - _lockStream = null; - } - } - - return false; - } - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - lock (_lock) - { - _disposed = true; - - if (_acquired) - { - try - { - _lockStream?.Close(); - } - catch (Exception ex) - { - _logger?.LogError("Error releasing the cache directory file lock.", ex); - } - } - - try - { - _lockStream?.Dispose(); - } - catch (Exception ex) - { - _logger?.LogError("Error disposing cache lock stream.", ex); - } - _lockStream = null; - } - } -} - -internal static class CacheDirectoryHelper -{ - public const string IsolatedCacheDirectoryPrefix = "isolated_"; - - internal static string? GetBaseCacheDirectoryPath(this SentryOptions options) => - string.IsNullOrWhiteSpace(options.CacheDirectoryPath) - ? null - : Path.Combine(options.CacheDirectoryPath, "Sentry"); - - internal static string? GetIsolatedFolderName(this SentryOptions options) - { - var stringBuilder = new StringBuilder(IsolatedCacheDirectoryPrefix); -#if IOS || ANDROID - // On iOS or Android the app is already sandboxed, so there's no risk of sending data to another Sentry's DSN. - // However, users may still initiate the SDK multiple times within the process, so we need an InitCounter - stringBuilder.Append(options.InitCounter.Count); -#else - if (string.IsNullOrWhiteSpace(options.Dsn)) - { - return null; - } - var processId = options.ProcessIdResolver.Invoke() ?? 0; - stringBuilder.AppendJoin('_', options.Dsn.GetHashString(), processId.ToString(), - options.InitCounter.Count.ToString()); -#endif - return stringBuilder.ToString(); - } - - internal static string? GetIsolatedCacheDirectoryPath(this SentryOptions options) => - GetBaseCacheDirectoryPath(options) is not { } baseCacheDir - || GetIsolatedFolderName(options) is not { } isolatedFolderName - ? null - : Path.Combine(baseCacheDir, isolatedFolderName); -} diff --git a/src/Sentry/Internal/FileSystemBase.cs b/src/Sentry/Internal/FileSystemBase.cs index 9e377d9836..60b474a668 100644 --- a/src/Sentry/Internal/FileSystemBase.cs +++ b/src/Sentry/Internal/FileSystemBase.cs @@ -2,9 +2,6 @@ namespace Sentry.Internal; internal abstract class FileSystemBase : IFileSystem { - public IEnumerable EnumerateDirectories(string path, string searchPattern) => - Directory.EnumerateDirectories(path, searchPattern); - public IEnumerable EnumerateFiles(string path) => Directory.EnumerateFiles(path); public IEnumerable EnumerateFiles(string path, string searchPattern) => @@ -26,7 +23,6 @@ public IEnumerable EnumerateFiles(string path, string searchPattern, Sea public abstract bool CreateDirectory(string path); public abstract bool DeleteDirectory(string path, bool recursive = false); public abstract bool CreateFileForWriting(string path, out Stream fileStream); - public abstract bool TryCreateLockFile(string path, out Stream fileStream); public abstract bool WriteAllTextToFile(string path, string contents); public abstract bool MoveFile(string sourceFileName, string destFileName, bool overwrite = false); public abstract bool DeleteFile(string path); diff --git a/src/Sentry/Internal/Http/CachingTransport.cs b/src/Sentry/Internal/Http/CachingTransport.cs index ebb4c9dab4..f2117f60b2 100644 --- a/src/Sentry/Internal/Http/CachingTransport.cs +++ b/src/Sentry/Internal/Http/CachingTransport.cs @@ -5,34 +5,25 @@ namespace Sentry.Internal.Http; /// -/// Transport that caches envelopes to disk and sends them in the background. +/// A transport that caches envelopes to disk and sends them in the background. /// /// -/// /// Note although this class has a /// method, it doesn't implement as this caused /// a dependency issue with Log4Net in some situations. -/// -/// See https://github.com/getsentry/sentry-dotnet/issues/3178 +/// +/// See https://github.com/getsentry/sentry-dotnet/issues/3178 /// internal class CachingTransport : ITransport, IDisposable { - internal const int ResultMigrationOk = 0; - internal const int ResultMigrationNoBaseCacheDir = 1; - internal const int ResultMigrationAlreadyMigrated = 2; - internal const int ResultMigrationLockNotAcquired = 3; - internal const int ResultMigrationCancelled = 4; - private const string EnvelopeFileExt = "envelope"; private const string ProcessingFolder = "__processing"; private readonly ITransport _innerTransport; private readonly SentryOptions _options; private readonly bool _failStorage; - private readonly int _keepCount; - - private readonly CacheDirectoryCoordinator _cacheCoordinator; private readonly string _isolatedCacheDirectoryPath; + private readonly int _keepCount; // When a file is getting processed, it's moved to a child directory // to avoid getting picked up by other threads. @@ -54,7 +45,6 @@ internal class CachingTransport : ITransport, IDisposable private readonly CancellationTokenSource _workerCts = new(); private Task _worker = null!; - private Task? _recycler = null; private ManualResetEventSlim? _initCacheResetEvent = new(); private ManualResetEventSlim? _preInitCacheResetEvent = new(); @@ -85,13 +75,8 @@ private CachingTransport(ITransport innerTransport, SentryOptions options, bool : 0; // just in case MaxCacheItems is set to an invalid value somehow (shouldn't happen) _isolatedCacheDirectoryPath = - options.GetIsolatedCacheDirectoryPath() ?? + options.TryGetProcessSpecificCacheDirectoryPath() ?? throw new InvalidOperationException("Cache directory or DSN is not set."); - _cacheCoordinator = new CacheDirectoryCoordinator(_isolatedCacheDirectoryPath, options.DiagnosticLogger, options.FileSystem); - if (!_cacheCoordinator.TryAcquire()) - { - throw new InvalidOperationException("Cache directory already locked."); - } // Sanity check: This should never happen in the first place. // We check for `DisableFileWrite` before creating the CachingTransport. @@ -105,19 +90,15 @@ private void Initialize(bool startWorker) // Restore any abandoned files from a previous session MoveUnprocessedFilesBackToCache(); + // Ensure directories exist + _fileSystem.CreateDirectory(_isolatedCacheDirectoryPath); _fileSystem.CreateDirectory(_processingDirectoryPath); + // Start a worker, if one is needed if (startWorker) { _options.LogDebug("Starting CachingTransport worker."); _worker = Task.Run(CachedTransportBackgroundTaskAsync); - - // Start a recycler in the background so as not to block initialisation - _recycler = Task.Run(() => - { - SalvageAbandonedCacheSessions(_workerCts.Token); - MigrateVersion5Cache(_workerCts.Token); - }, _workerCts.Token); } else { @@ -197,48 +178,6 @@ private async Task CachedTransportBackgroundTaskAsync() _options.LogDebug("CachingTransport worker stopped."); } - internal void SalvageAbandonedCacheSessions(CancellationToken cancellationToken) - { - if (_options.GetBaseCacheDirectoryPath() is not { } baseCacheDir) - { - return; - } - - const string searchPattern = $"{CacheDirectoryHelper.IsolatedCacheDirectoryPrefix}*"; - foreach (var dir in _fileSystem.EnumerateDirectories(baseCacheDir, searchPattern)) - { - if (cancellationToken.IsCancellationRequested) - { - return; // graceful exit on cancellation - } - - if (string.Equals(dir, _isolatedCacheDirectoryPath, StringComparison.OrdinalIgnoreCase)) - { - continue; // Ignore the current cache directory - } - - _options.LogDebug("Found abandoned cache: {0}", dir); - using var coordinator = new CacheDirectoryCoordinator(dir, _options.DiagnosticLogger, _options.FileSystem); - if (!coordinator.TryAcquire()) - { - continue; - } - - _options.LogDebug("Salvaging abandoned cache: {0}", dir); - foreach (var filePath in _fileSystem.EnumerateFiles(dir, "*.envelope", SearchOption.AllDirectories)) - { - if (cancellationToken.IsCancellationRequested) - { - return; // graceful exit on cancellation - } - - var destinationPath = Path.Combine(_isolatedCacheDirectoryPath, Path.GetFileName(filePath)); - _options.LogDebug("Moving abandoned file back to cache: {0} to {1}.", filePath, destinationPath); - MoveFileWithRetries(filePath, destinationPath, "abandoned"); - } - } - } - private void MoveUnprocessedFilesBackToCache() { // Processing directory may already contain some files left from previous session @@ -255,106 +194,42 @@ private void MoveUnprocessedFilesBackToCache() { var destinationPath = Path.Combine(_isolatedCacheDirectoryPath, Path.GetFileName(filePath)); _options.LogDebug("Moving unprocessed file back to cache: {0} to {1}.", filePath, destinationPath); - MoveFileWithRetries(filePath, destinationPath, "unprocessed"); - } - } - - /// - /// When migrating from version 5 to version 6, the cache directory structure changed. - /// In version 5, there were no isolated subdirectories. - /// - /// - /// An integer return code to facilitate testing. - internal int MigrateVersion5Cache(CancellationToken cancellationToken) - { - if (_options.GetBaseCacheDirectoryPath() is not { } baseCacheDir) - { - return ResultMigrationNoBaseCacheDir; - } - - // This code needs to execute only once during the first initialization of version 6. - var markerFile = Path.Combine(baseCacheDir, ".migrated"); - if (_fileSystem.FileExists(markerFile)) - { - return ResultMigrationAlreadyMigrated; - } - - var migrationLock = Path.Combine(baseCacheDir, "migration"); - using var coordinator = new CacheDirectoryCoordinator(migrationLock, _options.DiagnosticLogger, _options.FileSystem); - if (!coordinator.TryAcquire()) - { - return ResultMigrationLockNotAcquired; - } - - const string searchPattern = $"*.{EnvelopeFileExt}"; - var processingDirectoryPath = Path.Combine(baseCacheDir, ProcessingFolder); - foreach (var cacheDir in new[] { baseCacheDir, processingDirectoryPath }) - { - if (cancellationToken.IsCancellationRequested) - { - return ResultMigrationCancelled; // graceful exit on cancellation - } - - if (!_fileSystem.DirectoryExists(cacheDir)) - { - continue; - } - foreach (var filePath in _fileSystem.EnumerateFiles(cacheDir, searchPattern, SearchOption.TopDirectoryOnly)) + const int maxAttempts = 3; + for (var attempt = 1; attempt <= maxAttempts; attempt++) { - if (cancellationToken.IsCancellationRequested) - { - return ResultMigrationCancelled; // graceful exit on cancellation - } - - var destinationPath = Path.Combine(_isolatedCacheDirectoryPath, Path.GetFileName(filePath)); - _options.LogDebug("Migrating version 5 cache file: {0} to {1}.", filePath, destinationPath); - MoveFileWithRetries(filePath, destinationPath, "version 5"); - } - } - - // Leave a marker file so we don't try to migrate again - _fileSystem.WriteAllTextToFile(markerFile, "6.0.0"); - return ResultMigrationOk; - } - - private void MoveFileWithRetries(string filePath, string destinationPath, string moveType) - { - const int maxAttempts = 3; - for (var attempt = 1; attempt <= maxAttempts; attempt++) - { - try - { - _fileSystem.MoveFile(filePath, destinationPath); - - break; - } - catch (Exception ex) - { - if (!_fileSystem.FileExists(filePath)) + try { - _options.LogDebug( - "Failed to move {2} file back to cache (attempt {0}), " + - "but the file no longer exists so it must have been handled by another process: {1}", - attempt, filePath, moveType); + _fileSystem.MoveFile(filePath, destinationPath); break; } - - if (attempt < maxAttempts) + catch (Exception ex) { - _options.LogDebug( - "Failed to move {2} file back to cache (attempt {0}, retrying.): {1}", - attempt, filePath, moveType); + if (!_fileSystem.FileExists(filePath)) + { + _options.LogDebug( + "Failed to move unprocessed file back to cache (attempt {0}), " + + "but the file no longer exists so it must have been handled by another process: {1}", + attempt, filePath); + break; + } - Thread.Sleep(200); // give a small bit of time before retry - } - else - { - _options.LogError(ex, - "Failed to move {2} file back to cache (attempt {0}, done.): {1}", attempt, filePath, moveType); - } + if (attempt < maxAttempts) + { + _options.LogDebug( + "Failed to move unprocessed file back to cache (attempt {0}, retrying.): {1}", + attempt, filePath); + + Thread.Sleep(200); // give a small bit of time before retry + } + else + { + _options.LogError(ex, + "Failed to move unprocessed file back to cache (attempt {0}, done.): {1}", attempt, filePath); + } - // note: we do *not* want to re-throw the exception + // note: we do *not* want to re-throw the exception + } } } } @@ -403,7 +278,7 @@ private async Task ProcessCacheAsync(CancellationToken cancellation) // Make sure no files got stuck in the processing directory // See https://github.com/getsentry/sentry-dotnet/pull/3438#discussion_r1672524426 - if (_options.NetworkStatusListener is { Online: false }) + if (_options.NetworkStatusListener is { Online: false } listener) { MoveUnprocessedFilesBackToCache(); } @@ -664,19 +539,6 @@ public Task StopWorkerAsync() return _worker; } - private Task StopRecyclerAsync() - { - if (_recycler?.IsCompleted != false) - { - // already stopped - return Task.CompletedTask; - } - - _options.LogDebug("Stopping CachingTransport worker."); - _workerCts.Cancel(); - return _recycler; - } - public Task FlushAsync(CancellationToken cancellationToken = default) { _options.LogDebug("CachingTransport received request to flush the cache."); @@ -695,21 +557,10 @@ public async ValueTask DisposeAsync() _options.LogError(ex, "Error stopping worker during dispose."); } - try - { - await StopRecyclerAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - _options.LogError(ex, "Error in recycler during dispose."); - } - _workerSignal.Dispose(); _workerCts.Dispose(); _worker.Dispose(); - _recycler?.Dispose(); _cacheDirectoryLock.Dispose(); - _cacheCoordinator.Dispose(); _preInitCacheResetEvent?.Dispose(); _initCacheResetEvent?.Dispose(); diff --git a/src/Sentry/Internal/IFileSystem.cs b/src/Sentry/Internal/IFileSystem.cs index dd44d67b84..366bf70583 100644 --- a/src/Sentry/Internal/IFileSystem.cs +++ b/src/Sentry/Internal/IFileSystem.cs @@ -10,7 +10,6 @@ internal interface IFileSystem // Note: This is not comprehensive. If you need other filesystem methods, add to this interface, // then implement in both Sentry.Internal.FileSystem and Sentry.Testing.FakeFileSystem. - public IEnumerable EnumerateDirectories(string path, string searchPattern); public IEnumerable EnumerateFiles(string path); public IEnumerable EnumerateFiles(string path, string searchPattern); public IEnumerable EnumerateFiles(string path, string searchPattern, SearchOption searchOption); @@ -23,7 +22,6 @@ internal interface IFileSystem public bool CreateDirectory(string path); public bool DeleteDirectory(string path, bool recursive = false); public bool CreateFileForWriting(string path, out Stream fileStream); - public bool TryCreateLockFile(string path, out Stream fileStream); public bool WriteAllTextToFile(string path, string contents); public bool MoveFile(string sourceFileName, string destFileName, bool overwrite = false); public bool DeleteFile(string path); diff --git a/src/Sentry/Internal/InitCounter.cs b/src/Sentry/Internal/InitCounter.cs deleted file mode 100644 index 57155a9dc8..0000000000 --- a/src/Sentry/Internal/InitCounter.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Sentry.Internal; - -/// -/// This is used internally to track the number of times the SDK has been initialised so that different -/// Hub instances get a different cache directory path, even if they're running in the same process. -/// -internal interface IInitCounter -{ - public int Count { get; } - public void Increment(); -} - -/// -internal class InitCounter : IInitCounter -{ - internal static InitCounter Instance { get; } = new(); - - private int _count; - - public int Count => Volatile.Read(ref _count); - - public void Increment() => Interlocked.Increment(ref _count); -} diff --git a/src/Sentry/Internal/Polyfills.cs b/src/Sentry/Internal/Polyfills.cs index 4666a1f568..2113687f8d 100644 --- a/src/Sentry/Internal/Polyfills.cs +++ b/src/Sentry/Internal/Polyfills.cs @@ -1,9 +1,5 @@ // Polyfills to bridge the missing APIs in older targets. -#if !NET9_0_OR_GREATER -global using Lock = object; -#endif - #if NETFRAMEWORK || NETSTANDARD2_0 namespace System { @@ -13,32 +9,6 @@ internal static class HashCode public static int Combine(T1 value1, T2 value2, T3 value3) => (value1, value2, value3).GetHashCode(); } } - -internal static partial class PolyfillExtensions -{ - public static StringBuilder AppendJoin(this StringBuilder builder, char separator, params object?[] values) - { - if (values.Length == 0) - { - return builder; - } - - if (values[0] is {} value) - { - builder.Append(value); - } - - for (var i = 1; i < values.Length; i++) - { - builder.Append(separator); - if (values[i] is {} nextValue) - { - builder.Append(nextValue); - } - } - return builder; - } -} #endif #if NETFRAMEWORK diff --git a/src/Sentry/Internal/ReadOnlyFilesystem.cs b/src/Sentry/Internal/ReadOnlyFilesystem.cs index c71792b569..a5972b58f3 100644 --- a/src/Sentry/Internal/ReadOnlyFilesystem.cs +++ b/src/Sentry/Internal/ReadOnlyFilesystem.cs @@ -17,12 +17,6 @@ public override bool CreateFileForWriting(string path, out Stream fileStream) return false; } - public override bool TryCreateLockFile(string path, out Stream fileStream) - { - fileStream = Stream.Null; - return false; - } - public override bool WriteAllTextToFile(string path, string contents) => false; public override bool MoveFile(string sourceFileName, string destFileName, bool overwrite = false) => false; diff --git a/src/Sentry/Internal/ReadWriteFileSystem.cs b/src/Sentry/Internal/ReadWriteFileSystem.cs index ed4b58f496..6a9a08c2a3 100644 --- a/src/Sentry/Internal/ReadWriteFileSystem.cs +++ b/src/Sentry/Internal/ReadWriteFileSystem.cs @@ -25,26 +25,6 @@ public override bool CreateFileForWriting(string path, out Stream fileStream) return true; } - /// - /// Tries to create or open a file for exclusive access. - /// - /// - /// This method can throw all of the same exceptions that the constructor can throw. The - /// caller is responsible for handling those exceptions. - /// - public override bool TryCreateLockFile(string path, out Stream fileStream) - { - // Note that FileShare.None is implemented via advisory locks only on macOS/Linux... so it will stop - // other .NET processes from accessing the file but not other non-.NET processes. This should be fine - // in our case - we just want to avoid multiple instances of the SDK concurrently accessing the cache - fileStream = new FileStream( - path, - FileMode.OpenOrCreate, - FileAccess.ReadWrite, - FileShare.None); - return true; - } - public override bool WriteAllTextToFile(string path, string contents) { File.WriteAllText(path, contents); diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index a2e38f0354..c769b98370 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -814,24 +814,6 @@ public IDiagnosticLogger? DiagnosticLogger /// public string? CacheDirectoryPath { get; set; } - internal Func ProcessIdResolver - { - set => _processIdResolver = value; - get - { - return _processIdResolver ?? DefaultResolver; - int? DefaultResolver() => ProcessInfo.Instance?.GetId(this); - } - } - private Func? _processIdResolver; - - internal IInitCounter InitCounter - { - get => _initCounter ?? Sentry.Internal.InitCounter.Instance; - set => _initCounter = value; - } - private IInitCounter? _initCounter; - /// /// The SDK will only capture HTTP Client errors if it is enabled. /// can be used to configure which requests will be treated as failed. @@ -1854,6 +1836,31 @@ internal void LogDiagnosticWarning() } } + internal string? TryGetDsnSpecificCacheDirectoryPath() + { + if (string.IsNullOrWhiteSpace(CacheDirectoryPath)) + { + return null; + } + + // DSN must be set to use caching + if (string.IsNullOrWhiteSpace(Dsn)) + { + return null; + } +#if IOS || ANDROID // on iOS or Android the app is already sandboxed so there's no risk of sending data from 1 app to another Sentry's DSN + return Path.Combine(CacheDirectoryPath, "Sentry"); +#else + return Path.Combine(CacheDirectoryPath, "Sentry", Dsn.GetHashString()); +#endif + } + + internal string? TryGetProcessSpecificCacheDirectoryPath() + { + // In the future, this will most likely contain process ID + return TryGetDsnSpecificCacheDirectoryPath(); + } + internal static List GetDefaultInAppExclude() => [ "System", diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 5b610b86a9..46da99658e 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -30,8 +30,6 @@ static partial class SentrySdk internal static IHub InitHub(SentryOptions options) { - options.InitCounter.Increment(); - options.SetupLogging(); options.LogDiagnosticWarning(); diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index ae307a62d0..2ea48c1936 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -268,10 +268,6 @@ public void UseSentry_WithCaching_CanChangeCacheDirectoryPath() // Assert Assert.Equal(cachePath, options.CacheDirectoryPath); - if (options.Transport is CachingTransport cachingTransport) - { - cachingTransport.Dispose(); // Release cache lock so that the cacheDirectory can be removed - } } [Fact] diff --git a/test/Sentry.Testing/FakeFileSystem.cs b/test/Sentry.Testing/FakeFileSystem.cs index cfc98796e3..0b6c3ad94a 100644 --- a/test/Sentry.Testing/FakeFileSystem.cs +++ b/test/Sentry.Testing/FakeFileSystem.cs @@ -6,9 +6,6 @@ internal class FakeFileSystem : IFileSystem { private readonly MockFileSystem _fileSystem = new(); - public IEnumerable EnumerateDirectories(string path, string searchPattern) => - _fileSystem.Directory.EnumerateDirectories(path, searchPattern); - public IEnumerable EnumerateFiles(string path) => _fileSystem.Directory.EnumerateFiles(path); public IEnumerable EnumerateFiles(string path, string searchPattern) => @@ -33,16 +30,6 @@ public bool CreateDirectory(string path) return _fileSystem.Directory.Exists(path); } - public bool TryCreateLockFile(string path, out Stream fileStream) - { - fileStream = _fileSystem.FileStream.New( - path, - FileMode.OpenOrCreate, - FileAccess.ReadWrite, - FileShare.None); - return true; - } - public bool DeleteDirectory(string path, bool recursive = false) { _fileSystem.Directory.Delete(path, recursive); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 86edf944ab..17c3474705 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -2,7 +2,6 @@ using Sentry.Internal.Http; using Sentry.Protocol; using Sentry.Tests.Internals; -using Sentry.Tests.Internals.Http; namespace Sentry.Tests; @@ -2401,10 +2400,6 @@ public async Task FlushOnDispose_SendsEnvelope(bool cachingEnabled) await transport.Received(1) .SendEnvelopeAsync(Arg.Is(env => (string)env.Header["event_id"] == id.ToString()), Arg.Any()); - if (options.Transport is CachingTransport cachingTransport) - { - await cachingTransport.DisposeAsync(); // Release cache lock so that the cacheDirectory can be removed - } } private static Scope GetCurrentScope(Hub hub) => hub.ScopeManager.GetCurrent().Key; diff --git a/test/Sentry.Tests/Internals/CacheDirectoryCoordinatorTests.cs b/test/Sentry.Tests/Internals/CacheDirectoryCoordinatorTests.cs deleted file mode 100644 index 3d40483b4c..0000000000 --- a/test/Sentry.Tests/Internals/CacheDirectoryCoordinatorTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace Sentry.Tests.Internals; - -public class CacheDirectoryCoordinatorTests : IDisposable -{ - private readonly Fixture _fixture = new(); - - public void Dispose() - { - _fixture.CacheRoot.Dispose(); - } - - private class Fixture - { - public readonly TempDirectory CacheRoot = new(); - public ReadWriteFileSystem FileSystem { get; } = new(); - - public string CacheDirectoryPath { get; private set; } - private readonly string _isolatedDirectory = Guid.NewGuid().ToString("N"); - - public CacheDirectoryCoordinator GetSut() - { - CacheDirectoryPath = Path.Combine(CacheRoot.Path, _isolatedDirectory); - return new(CacheDirectoryPath, null, FileSystem); - } - } - - [Fact] - public void TryAcquire_FirstTime_ReturnsTrueAndCreatesLockFile() - { - // Arrange - using var coordinator = _fixture.GetSut(); - - // Act - var acquired = coordinator.TryAcquire(); - - // Assert - Assert.True(acquired); - Assert.True(_fixture.FileSystem.FileExists(_fixture.CacheDirectoryPath + ".lock")); - } - - [Fact] - public void TryAcquire_TwiceOnSameCoordinator_IdempotentTrue() - { - // Arrange - using var coordinator = _fixture.GetSut(); - - // Act - Assert.True(coordinator.TryAcquire()); - Assert.True(coordinator.TryAcquire()); - } - - [Fact] - public void TryAcquire_Locked_Fails() - { - // Arrange - using var c1 = _fixture.GetSut(); - using var c2 = _fixture.GetSut(); - Assert.True(c1.TryAcquire()); - - // Act & Assert - Assert.False(c2.TryAcquire()); - } - - [Fact] - public void TryAcquire_AfterReleasedSut_Succeeds() - { - // Arrange - var c1 = _fixture.GetSut(); - Assert.True(c1.TryAcquire()); - c1.Dispose(); - - // Act & Assert - using var c3 = _fixture.GetSut(); - Assert.True(c3.TryAcquire()); - } - - [Fact] - public void TryAcquire_AfterDispose_ReturnsFalse() - { - // Arrange - var coordinator = _fixture.GetSut(); - coordinator.Dispose(); - - // Act & Assert - Assert.False(coordinator.TryAcquire()); - } - - [Fact] - public void Dispose_CanBeCalledMultipleTimes_NoThrow() - { - // Arrange - var coordinator = _fixture.GetSut(); - coordinator.TryAcquire(); - - // Act & Assert - coordinator.Dispose(); - coordinator.Dispose(); - } -} diff --git a/test/Sentry.Tests/Internals/Http/CachingTransportTests.cs b/test/Sentry.Tests/Internals/Http/CachingTransportTests.cs index dc5922fb84..4eb29f4290 100644 --- a/test/Sentry.Tests/Internals/Http/CachingTransportTests.cs +++ b/test/Sentry.Tests/Internals/Http/CachingTransportTests.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions.TestingHelpers; using Sentry.Internal.Http; namespace Sentry.Tests.Internals.Http; @@ -222,7 +223,6 @@ public async Task AwareOfExistingFiles() // Move them all to processing and leave them there (due to FakeFailingTransport) await initialTransport.FlushAsync(); - await initialTransport.DisposeAsync(); // Act @@ -242,7 +242,7 @@ public async Task Handle_Malformed_Envelopes_Gracefully() { // Arrange var cacheDirectoryPath = - _options.GetIsolatedCacheDirectoryPath() ?? + _options.TryGetProcessSpecificCacheDirectoryPath() ?? throw new InvalidOperationException("Cache directory or DSN is not set."); var processingDirectoryPath = Path.Combine(cacheDirectoryPath, "__processing"); var fileName = $"{Guid.NewGuid()}.envelope"; @@ -623,235 +623,11 @@ public async Task DoesntWriteSentAtHeaderToCacheFile() await transport.SendEnvelopeAsync(envelope); // Assert - var isolatedCacheDir = _options.GetIsolatedCacheDirectoryPath(); var filePath = _options.FileSystem - .EnumerateFiles(isolatedCacheDir!, "*", SearchOption.AllDirectories) + .EnumerateFiles(_options.CacheDirectoryPath!, "*", SearchOption.AllDirectories) .Single(); var contents = _options.FileSystem.ReadAllTextFromFile(filePath); Assert.DoesNotContain("sent_at", contents); } - - [Fact] - public async Task SalvageAbandonedCacheSessions_MovesEnvelopesFromOtherIsolatedDirectories() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - - var currentIsolated = _options.GetIsolatedCacheDirectoryPath()!; // already validated during creation - var baseCacheDir = Directory.GetParent(currentIsolated)!.FullName; - - // Create two abandoned isolated cache directories with envelope files (including in nested folder) - void CreateAbandonedDir(int index) - { - var abandoned = Path.Combine(baseCacheDir, $"isolated_abandoned_{index}"); - _options.FileSystem.CreateDirectory(abandoned); - var nested = Path.Combine(abandoned, "nested"); - _options.FileSystem.CreateDirectory(nested); - - // root file - var rootFile = Path.Combine(abandoned, $"root_{index}.envelope"); - _options.FileSystem.WriteAllTextToFile(rootFile, "dummy content"); - // nested file - var nestedFile = Path.Combine(nested, $"nested_{index}.envelope"); - _options.FileSystem.WriteAllTextToFile(nestedFile, "dummy content"); - } - - CreateAbandonedDir(1); - CreateAbandonedDir(2); - - // Act - transport.SalvageAbandonedCacheSessions(CancellationToken.None); - - // Assert: All *.envelope files from abandoned dirs should now reside in the current isolated cache directory root - var movedFiles = _options.FileSystem - .EnumerateFiles(currentIsolated, "*.envelope", SearchOption.TopDirectoryOnly) - .Select(Path.GetFileName) - .ToArray(); - - movedFiles.Should().Contain(new[] { "root_1.envelope", "nested_1.envelope", "root_2.envelope", "nested_2.envelope" }); - - // Ensure abandoned directories no longer contain those files - var abandonedResidual = _options.FileSystem - .EnumerateFiles(baseCacheDir, "*.envelope", SearchOption.AllDirectories) - .Where(p => !p.StartsWith(currentIsolated, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - abandonedResidual.Should().BeEmpty(); - } - - [Fact] - public async Task SalvageAbandonedCacheSessions_CurrentDirectory_Skipped() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - var currentIsolated = _options.GetIsolatedCacheDirectoryPath()!; - - var currentFile = Path.Combine(currentIsolated, "current.envelope"); - _options.FileSystem.WriteAllTextToFile(currentFile, "dummy content"); - - // Act - transport.SalvageAbandonedCacheSessions(CancellationToken.None); - - // Assert: File created in current directory should remain untouched - _options.FileSystem.FileExists(currentFile).Should().BeTrue(); - } - - [Fact] - public async Task SalvageAbandonedCacheSessions_ActiveLock_Skipped() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - - var currentIsolated = _options.GetIsolatedCacheDirectoryPath()!; - var baseCacheDir = Directory.GetParent(currentIsolated)!.FullName; - - // Create an abandoned directory that matches the search pattern and acquire its lock - var lockedDir = Path.Combine(baseCacheDir, "isolated_locked"); - _options.FileSystem.CreateDirectory(lockedDir); - var lockedEnvelope = Path.Combine(lockedDir, "locked.envelope"); - _options.FileSystem.WriteAllTextToFile(lockedEnvelope, "dummy content"); - - // Acquire the lock so salvage can't take it - using (var coordinator = new CacheDirectoryCoordinator(lockedDir, _options.DiagnosticLogger, _options.FileSystem)) - { - var acquired = coordinator.TryAcquire(); - acquired.Should().BeTrue("test must hold the lock to validate skip behavior"); - - // Act - transport.SalvageAbandonedCacheSessions(CancellationToken.None); - - // Assert: File should still be in locked abandoned directory and not in current directory - _options.FileSystem.FileExists(lockedEnvelope).Should().BeTrue(); - var moved = _options.FileSystem - .EnumerateFiles(currentIsolated, "locked.envelope", SearchOption.TopDirectoryOnly) - .Any(); - moved.Should().BeFalse(); - } - } - - [Fact] - public async Task MigrateVersion5Cache_MovesEnvelopesFromBaseAndProcessing() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - - var isolatedCacheDir = _options.GetIsolatedCacheDirectoryPath()!; - - var baseCacheDir = _options.GetBaseCacheDirectoryPath()!; - var rootFile = Path.Combine(baseCacheDir, "v5_root.envelope"); - _options.FileSystem.CreateDirectory(baseCacheDir); - _options.FileSystem.WriteAllTextToFile(rootFile, "dummy content"); - - var processingDir = Path.Combine(baseCacheDir, "__processing"); - var procFile = Path.Combine(processingDir, "v5_proc.envelope"); - _options.FileSystem.CreateDirectory(processingDir); - _options.FileSystem.WriteAllTextToFile(procFile, "dummy content"); - - // Act - var result = transport.MigrateVersion5Cache(CancellationToken.None); - - // Assert - result.Should().Be(CachingTransport.ResultMigrationOk); - - var markerFile = Path.Combine(baseCacheDir, ".migrated"); - _options.FileSystem.FileExists(markerFile).Should().BeTrue(); - _options.FileSystem.FileExists(rootFile).Should().BeFalse(); - _options.FileSystem.FileExists(procFile).Should().BeFalse(); - - var movedRoot = Path.Combine(isolatedCacheDir, "v5_root.envelope"); - var movedProc = Path.Combine(isolatedCacheDir, "v5_proc.envelope"); - _options.FileSystem.FileExists(movedRoot).Should().BeTrue(); - _options.FileSystem.FileExists(movedProc).Should().BeTrue(); - } - - [Fact] - public async Task MigrateVersion5Cache_AlreadyMigrated_Skipped() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - - var baseCacheDir = _options.GetBaseCacheDirectoryPath()!; - var rootFile = Path.Combine(baseCacheDir, "v5_root.envelope"); - _options.FileSystem.CreateDirectory(baseCacheDir); - _options.FileSystem.WriteAllTextToFile(rootFile, "dummy content"); - - var processingDir = Path.Combine(baseCacheDir, "__processing"); - var procFile = Path.Combine(processingDir, "v5_proc.envelope"); - _options.FileSystem.CreateDirectory(processingDir); - _options.FileSystem.WriteAllTextToFile(procFile, "dummy content"); - - var marker = Path.Combine(baseCacheDir, ".migrated"); - _options.FileSystem.WriteAllTextToFile(marker, "6.0.0"); - - // Act - var result = transport.MigrateVersion5Cache(CancellationToken.None); - - // Assert - result.Should().Be(CachingTransport.ResultMigrationAlreadyMigrated); - _options.FileSystem.FileExists(rootFile).Should().BeTrue(); - _options.FileSystem.FileExists(procFile).Should().BeTrue(); - } - - [Fact] - public async Task MigrateVersion5Cache_MigrationLockHeld_Skipped() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - - var baseCacheDir = _options.GetBaseCacheDirectoryPath()!; - var rootFile = Path.Combine(baseCacheDir, "v5_root.envelope"); - _options.FileSystem.CreateDirectory(baseCacheDir); - _options.FileSystem.WriteAllTextToFile(rootFile, "dummy content"); - - var processingDir = Path.Combine(baseCacheDir, "__processing"); - var procFile = Path.Combine(processingDir, "v5_proc.envelope"); - _options.FileSystem.CreateDirectory(processingDir); - _options.FileSystem.WriteAllTextToFile(procFile, "dummy content"); - - var migrationLockPath = Path.Combine(baseCacheDir, "migration"); - using var coordinator = new CacheDirectoryCoordinator(migrationLockPath, _options.DiagnosticLogger, _options.FileSystem); - coordinator.TryAcquire().Should().BeTrue("test must hold the migration lock"); - - // Act - var result = transport.MigrateVersion5Cache(CancellationToken.None); - - // Assert - result.Should().Be(CachingTransport.ResultMigrationLockNotAcquired); - _options.FileSystem.FileExists(rootFile).Should().BeTrue(); - _options.FileSystem.FileExists(procFile).Should().BeTrue(); - } - - [Fact] - public async Task MigrateVersion5Cache_Cancelled_Skipped() - { - // Arrange - using var innerTransport = new FakeTransport(); - await using var transport = CachingTransport.Create(innerTransport, _options, startWorker: false); - - var baseCacheDir = _options.GetBaseCacheDirectoryPath()!; - var rootFile = Path.Combine(baseCacheDir, "v5_root.envelope"); - _options.FileSystem.CreateDirectory(baseCacheDir); - _options.FileSystem.WriteAllTextToFile(rootFile, "dummy content"); - - var processingDir = Path.Combine(baseCacheDir, "__processing"); - var procFile = Path.Combine(processingDir, "v5_proc.envelope"); - _options.FileSystem.CreateDirectory(processingDir); - _options.FileSystem.WriteAllTextToFile(procFile, "dummy content"); - - // Act - using var cts = new CancellationTokenSource(); - await cts.CancelAsync(); - var result = transport.MigrateVersion5Cache(cts.Token); - - // Assert - result.Should().Be(CachingTransport.ResultMigrationCancelled); - _options.FileSystem.FileExists(rootFile).Should().BeTrue(); - _options.FileSystem.FileExists(procFile).Should().BeTrue(); - } } diff --git a/test/Sentry.Tests/Internals/InitCounterTests.cs b/test/Sentry.Tests/Internals/InitCounterTests.cs deleted file mode 100644 index cc3f70a5f3..0000000000 --- a/test/Sentry.Tests/Internals/InitCounterTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Sentry.Tests.Internals; - -public class InitCounterTests -{ - [Fact] - public void Count_StartsAtZero() - { - // Arrange - var intCounter = new InitCounter(); - - // Act & Assert - Assert.Equal(0, intCounter.Count); - } - - [Fact] - public void Increment_IncreasesByOne() - { - // Arrange - var intCounter = new InitCounter(); - - // Act - intCounter.Increment(); - - // Assert - intCounter.Count.Should().Be(1); - } - - [Fact] - public async Task Increment_IsThreadsSafe() - { - // Arrange - const int incrementsPerTask = 1000; - const int numberOfTasks = 10; - var intCounter = new InitCounter(); - - // Act - // Spawn multiple threads to increment the counter then wait for all the threads to complete and verify the expected number of increments has been made - var tasks = new List(); - for (var i = 0; i < numberOfTasks; i++) - { - tasks.Add(Task.Run(() => - { - for (int j = 0; j < incrementsPerTask; j++) - { - intCounter.Increment(); - } - })); - } - - await Task.WhenAll(tasks); - - // Assert - Assert.Equal(incrementsPerTask * numberOfTasks, intCounter.Count); - } -} diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index d64f917354..1014a4f011 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -1663,20 +1663,15 @@ public void Ctor_KeepsCustomTransportOnOptions() [Fact] public void Ctor_WrapsCustomTransportWhenCachePathOnOptions() { - // Arrange _fixture.SentryOptions.Dsn = ValidDsn; _fixture.SentryOptions.Transport = new FakeTransport(); using var cacheDirectory = new TempDirectory(); _fixture.SentryOptions.CacheDirectoryPath = cacheDirectory.Path; - // Act using var sut = new SentryClient(_fixture.SentryOptions); - // Assert var cachingTransport = Assert.IsType(_fixture.SentryOptions.Transport); _ = Assert.IsType(cachingTransport.InnerTransport); - - cachingTransport.Dispose(); // Release cache lock so that the cacheDirectory can be removed } [Fact] diff --git a/test/Sentry.Tests/SentryOptionsTests.cs b/test/Sentry.Tests/SentryOptionsTests.cs index 38a3e27a4f..d563665bb4 100644 --- a/test/Sentry.Tests/SentryOptionsTests.cs +++ b/test/Sentry.Tests/SentryOptionsTests.cs @@ -689,57 +689,4 @@ public void CachesInstallationId() installationId2.Should().Be(installationId1); logger.Received(0).Log(SentryLevel.Debug, "Resolved installation ID '{0}'.", null, Arg.Any()); } - - [Fact] - public void TryGetIsolatedCacheDirectoryPath_NullCacheDirectory_ReturnsNull() - { - var o = new SentryOptions { CacheDirectoryPath = null, Dsn = ValidDsn }; - Assert.Null(o.GetIsolatedCacheDirectoryPath()); - } - -#if IOS || ANDROID - [Fact] - public void GetIsolatedFolderName_MissingDsn_UniqueForInitcount() - { - var o = new SentryOptions { CacheDirectoryPath = "c:\\cache", Dsn = null }; - var folder = o.GetIsolatedFolderName(); - Assert.Equal($"{CacheDirectoryHelper.IsolatedCacheDirectoryPrefix}{o.InitCounter.Count}", folder); - } -#else - [Fact] - public void GetIsolatedFolderName_MissingDsn__ReturnsNull() - { - var o = new SentryOptions { CacheDirectoryPath = "c:\\cache", Dsn = null }; - var folder = o.GetIsolatedFolderName(); - Assert.Null(folder); - } -#endif - - [Theory] - [InlineData(5, null)] - [InlineData(5, 7)] - public void GetIsolatedFolderName_UniqueForDsnInitCountAndProcessId(int initCount, int? processId) - { - // Arrange - var initCounter = Substitute.For(); - initCounter.Count.Returns(initCount); - var o = new SentryOptions - { - CacheDirectoryPath = "c:\\cache", - Dsn = ValidDsn, - InitCounter = initCounter, - ProcessIdResolver = () => processId - }; - - // Act - var path = o.GetIsolatedFolderName(); - - // Assert -#if IOS || ANDROID - var expectedFolder = $"{CacheDirectoryHelper.IsolatedCacheDirectoryPrefix}{initCount}"; -#else - var expectedFolder = $"{CacheDirectoryHelper.IsolatedCacheDirectoryPrefix}{ValidDsn.GetHashString()}_{processId ?? 0}_{initCount}"; -#endif - path.Should().Be(expectedFolder); - } } diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index cceb134d57..9c5fe1eca0 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -188,26 +188,6 @@ public void Init_EmptyDsnDisabledDiagnostics_DoesNotLogWarning() } } - [Fact] - public void Init_IncrementsInitCount() - { - // Arrange - var initCount = new InitCounter(); - - // Act - using var first = SentrySdk.Init(o => - { - o.Dsn = ValidDsn; - o.AutoSessionTracking = false; - o.BackgroundWorker = Substitute.For(); - o.InitNativeSdks = false; - o.InitCounter = initCount; - }); - - // Assert - initCount.Count.Should().Be(1); - } - [Fact] public void Init_MultipleCalls_ReplacesHubWithLatest() { @@ -266,11 +246,6 @@ public async Task Init_WithCache_BlocksUntilExistingCacheIsFlushed(bool? testDel using var cacheDirectory = new TempDirectory(); var cachePath = cacheDirectory.Path; - // Force all instances to use the same cache path - var initCounter = Substitute.For(); - initCounter.Count.Returns(1); - Func processIdResolver = () => 42; - // Pre-populate cache var initialInnerTransport = Substitute.For(); var initialTransport = CachingTransport.Create( @@ -283,8 +258,6 @@ public async Task Init_WithCache_BlocksUntilExistingCacheIsFlushed(bool? testDel CacheDirectoryPath = cachePath, AutoSessionTracking = false, InitNativeSdks = false, - InitCounter = initCounter, - ProcessIdResolver = processIdResolver }, startWorker: false); await using (initialTransport) @@ -335,8 +308,6 @@ public async Task Init_WithCache_BlocksUntilExistingCacheIsFlushed(bool? testDel o.Debug = true; o.DiagnosticLogger = _logger; o.CacheDirectoryPath = cachePath; - o.InitCounter = initCounter; - o.ProcessIdResolver = processIdResolver; o.InitCacheFlushTimeout = initFlushTimeout; o.Transport = transport; o.AutoSessionTracking = false; @@ -369,10 +340,8 @@ public async Task Init_WithCache_BlocksUntilExistingCacheIsFlushed(bool? testDel finally { // cleanup to avoid disposing/deleting the temp directory while the cache worker is still running - if (options!.Transport is CachingTransport cachingTransport) - { - await cachingTransport!.StopWorkerAsync(); - } + var cachingTransport = (CachingTransport)options!.Transport; + await cachingTransport!.StopWorkerAsync(); } } #endif From 9d694fbc4c94f887059ea7d697fb67607c6e6953 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 3 Dec 2025 12:53:51 +1300 Subject: [PATCH 2/2] Fix broken changelog --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d703f8cb0a..f7708cd89b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,6 @@ ### Features - Add support for _Structured Logs_ in `Sentry.Google.Cloud.Functions` ([#4700](https://github.com/getsentry/sentry-dotnet/pull/4700)) -- The SDK now makes use of the new SessionEndStatus `Unhandled` when capturing an unhandled but non-terminal exception, i.e. through the UnobservedTaskExceptionIntegration ([#4633](https://github.com/getsentry/sentry-dotnet/pull/4633), [#4653](https://github.com/getsentry/sentry-dotnet/pull/4653)) -- The SDK now provides a `IsSessionActive` to allow checking the session state ([#4662](https://github.com/getsentry/sentry-dotnet/pull/4662)) -- The SDK now makes use of the new SessionEndStatus `Unhandled` when capturing an unhandled but non-terminal exception, i.e. through the UnobservedTaskExceptionIntegration ([#4633](https://github.com/getsentry/sentry-dotnet/pull/4633)) -- Added experimental support for Session Replay on iOS ([#4664](https://github.com/getsentry/sentry-dotnet/pull/4664)) -- Extended the App context by `app_memory` that can hold the amount of memory used by the application in bytes. ([#4707](https://github.com/getsentry/sentry-dotnet/pull/4707)) ### Fixes @@ -76,7 +71,6 @@ - QOL features for Unity - The SDK now provides a `IsSessionActive` to allow checking the session state ([#4662](https://github.com/getsentry/sentry-dotnet/pull/4662)) - The SDK now makes use of the new SessionEndStatus `Unhandled` when capturing an unhandled but non-terminal exception, i.e. through the UnobservedTaskExceptionIntegration ([#4633](https://github.com/getsentry/sentry-dotnet/pull/4633), [#4653](https://github.com/getsentry/sentry-dotnet/pull/4653)) -- Implemented instance isolation so that multiple instances of the Sentry SDK can be instantiated inside the same process when using the Caching Transport ([#4498](https://github.com/getsentry/sentry-dotnet/pull/4498)) - Extended the App context by `app_memory` that can hold the amount of memory used by the application in bytes. ([#4707](https://github.com/getsentry/sentry-dotnet/pull/4707)) - Add support for W3C traceparent header for outgoing requests ([#4661](https://github.com/getsentry/sentry-dotnet/pull/4661)) - This feature is disabled by default. Set `PropagateTraceparent = true` when initializing the SDK if to include the W3C traceparent header on outgoing requests.