未验证 提交 bc098b56 编写于 作者: M msftbot[bot] 提交者: GitHub

Merge pull request #42551 from CyrusNajmabadi/sqliteFallbackToNoOp

If we fail to create the sqlite db, don't continually retry for every client that wants it.
...@@ -55,8 +55,7 @@ public void GlobalSetup() ...@@ -55,8 +55,7 @@ public void GlobalSetup()
_workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options _workspace.TryApplyChanges(_workspace.CurrentSolution.WithOptions(_workspace.Options
.WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite))); .WithChangedOption(StorageOptions.Database, StorageDatabase.SQLite)));
_storageService = new SQLitePersistentStorageService( _storageService = new SQLitePersistentStorageService(new LocationService());
_workspace.Services.GetService<IOptionService>(), new LocationService());
_storage = _storageService.GetStorageWorker(_workspace.CurrentSolution); _storage = _storageService.GetStorageWorker(_workspace.CurrentSolution);
if (_storage == NoOpPersistentStorage.Instance) if (_storage == NoOpPersistentStorage.Instance)
......
...@@ -33,10 +33,6 @@ public enum Size ...@@ -33,10 +33,6 @@ public enum Size
private const string PersistentFolderPrefix = "PersistentStorageTests_"; private const string PersistentFolderPrefix = "PersistentStorageTests_";
private readonly Encoding _encoding = Encoding.UTF8; private readonly Encoding _encoding = Encoding.UTF8;
internal readonly IOptionService _persistentEnabledOptionService = new OptionServiceMock(new Dictionary<IOption, object>
{
{ PersistentStorageOptions.Enabled, true },
});
private AbstractPersistentStorageService _storageService; private AbstractPersistentStorageService _storageService;
private readonly string _persistentFolder; private readonly string _persistentFolder;
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.SQLite; using Microsoft.CodeAnalysis.SQLite;
using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Storage;
...@@ -18,10 +20,10 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices ...@@ -18,10 +20,10 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices
public class SQLitePersistentStorageTests : AbstractPersistentStorageTests public class SQLitePersistentStorageTests : AbstractPersistentStorageTests
{ {
internal override AbstractPersistentStorageService GetStorageService(IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector faultInjector) internal override AbstractPersistentStorageService GetStorageService(IPersistentStorageLocationService locationService, IPersistentStorageFaultInjector faultInjector)
=> new SQLitePersistentStorageService(_persistentEnabledOptionService, locationService, faultInjector); => new SQLitePersistentStorageService(locationService, faultInjector);
[Fact] [Fact]
public void TestCrashInNewConnection() public async Task TestCrashInNewConnection()
{ {
var solution = CreateOrOpenSolution(nullPaths: true); var solution = CreateOrOpenSolution(nullPaths: true);
...@@ -34,11 +36,22 @@ public void TestCrashInNewConnection() ...@@ -34,11 +36,22 @@ public void TestCrashInNewConnection()
}, },
onFatalError: e => throw e); onFatalError: e => throw e);
using (var storage = GetStorage(solution, faultInjector))
{
// Because instantiating the connection will fail, we will not get back // Because instantiating the connection will fail, we will not get back
// a working persistent storage. // a working persistent storage.
Assert.IsType<NoOpPersistentStorage>(storage); using (var storage = GetStorage(solution, faultInjector))
using (var memStream = new MemoryStream())
using (var streamWriter = new StreamWriter(memStream))
{
streamWriter.WriteLine("contents");
streamWriter.Flush();
memStream.Position = 0;
await storage.WriteStreamAsync("temp", memStream);
var readStream = await storage.ReadStreamAsync("temp");
// Because we don't have a real storage service, we should get back
// null even when trying to read something we just wrote.
Assert.Null(readStream);
} }
Assert.True(hitInjector); Assert.True(hitInjector);
......
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#nullable enable
using System; using System;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities; using Roslyn.Utilities;
...@@ -20,26 +21,26 @@ namespace Microsoft.CodeAnalysis.Storage ...@@ -20,26 +21,26 @@ namespace Microsoft.CodeAnalysis.Storage
/// </summary> /// </summary>
internal abstract partial class AbstractPersistentStorageService : IChecksummedPersistentStorageService internal abstract partial class AbstractPersistentStorageService : IChecksummedPersistentStorageService
{ {
private readonly IOptionService _optionService;
private readonly IPersistentStorageLocationService _locationService; private readonly IPersistentStorageLocationService _locationService;
/// <summary> /// <summary>
/// This lock guards all mutable fields in this type. /// This lock guards all mutable fields in this type.
/// </summary> /// </summary>
private readonly object _lock = new object(); private readonly object _lock = new object();
private ReferenceCountedDisposable<IChecksummedPersistentStorage> _currentPersistentStorage; private ReferenceCountedDisposable<IChecksummedPersistentStorage>? _currentPersistentStorage;
private SolutionId _currentPersistentStorageSolutionId; private SolutionId? _currentPersistentStorageSolutionId;
protected AbstractPersistentStorageService( protected AbstractPersistentStorageService(IPersistentStorageLocationService locationService)
IOptionService optionService, => _locationService = locationService;
IPersistentStorageLocationService locationService)
{
_optionService = optionService;
_locationService = locationService;
}
protected abstract string GetDatabaseFilePath(string workingFolderPath); protected abstract string GetDatabaseFilePath(string workingFolderPath);
protected abstract bool TryOpenDatabase(Solution solution, string workingFolderPath, string databaseFilePath, out IChecksummedPersistentStorage storage);
/// <summary>
/// Can throw. If it does, the caller (<see cref="CreatePersistentStorage"/>) will attempt
/// to delete the database and retry opening one more time. If that fails again, the <see
/// cref="NoOpPersistentStorage"/> instance will be used.
/// </summary>
protected abstract IChecksummedPersistentStorage? TryOpenDatabase(Solution solution, string workingFolderPath, string databaseFilePath);
protected abstract bool ShouldDeleteDatabase(Exception exception); protected abstract bool ShouldDeleteDatabase(Exception exception);
IPersistentStorage IPersistentStorageService.GetStorage(Solution solution) IPersistentStorage IPersistentStorageService.GetStorage(Solution solution)
...@@ -68,8 +69,9 @@ internal IChecksummedPersistentStorage GetStorageWorker(Solution solution) ...@@ -68,8 +69,9 @@ internal IChecksummedPersistentStorage GetStorageWorker(Solution solution)
// Do we already have storage for this? // Do we already have storage for this?
if (solution.Id == _currentPersistentStorageSolutionId) if (solution.Id == _currentPersistentStorageSolutionId)
{ {
// We do, great // We do, great. Increment our ref count for our caller. They'll decrement it
return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage); // when done with it.
return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage!);
} }
var workingFolder = _locationService.TryGetStorageLocation(solution); var workingFolder = _locationService.TryGetStorageLocation(solution);
...@@ -83,21 +85,27 @@ internal IChecksummedPersistentStorage GetStorageWorker(Solution solution) ...@@ -83,21 +85,27 @@ internal IChecksummedPersistentStorage GetStorageWorker(Solution solution)
{ {
var storageToDispose = _currentPersistentStorage; var storageToDispose = _currentPersistentStorage;
// Kick off a task to actually go dispose the previous cached storage instance.
// This will remove the single ref count we ourselves added when we cached the
// instance. Then once all other existing clients who are holding onto this
// instance let go, it will finally get truly disposed.
Task.Run(() => storageToDispose.Dispose()); Task.Run(() => storageToDispose.Dispose());
_currentPersistentStorage = null; _currentPersistentStorage = null;
_currentPersistentStorageSolutionId = null; _currentPersistentStorageSolutionId = null;
} }
_currentPersistentStorage = TryCreatePersistentStorage(solution, workingFolder); var storage = CreatePersistentStorage(solution, workingFolder);
Contract.ThrowIfNull(storage);
if (_currentPersistentStorage == null)
{
return NoOpPersistentStorage.Instance;
}
// Create and cache a new storage instance associated with this particular solution.
// It will initially have a ref-count of 1 due to our reference to it.
_currentPersistentStorage = new ReferenceCountedDisposable<IChecksummedPersistentStorage>(storage);
_currentPersistentStorageSolutionId = solution.Id; _currentPersistentStorageSolutionId = solution.Id;
// Now increment the reference count and return to our caller. The current ref
// count for this instance will be 2. Until all the callers *and* us decrement
// the refcounts, this instance will not be actually disposed.
return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage); return PersistentStorageReferenceCountedDisposableWrapper.AddReferenceCountToAndCreateWrapper(_currentPersistentStorage);
} }
} }
...@@ -118,39 +126,25 @@ private bool DatabaseSupported(Solution solution, bool checkBranchId) ...@@ -118,39 +126,25 @@ private bool DatabaseSupported(Solution solution, bool checkBranchId)
return true; return true;
} }
private ReferenceCountedDisposable<IChecksummedPersistentStorage> TryCreatePersistentStorage(Solution solution, string workingFolderPath) private IChecksummedPersistentStorage CreatePersistentStorage(Solution solution, string workingFolderPath)
{ {
// Attempt to create the database up to two times. The first time we may encounter // Attempt to create the database up to two times. The first time we may encounter
// some sort of issue (like DB corruption). We'll then try to delete the DB and can // some sort of issue (like DB corruption). We'll then try to delete the DB and can
// try to create it again. If we can't create it the second time, then there's nothing // try to create it again. If we can't create it the second time, then there's nothing
// we can do and we have to store things in memory. // we can do and we have to store things in memory.
if (TryCreatePersistentStorage(solution, workingFolderPath, out var persistentStorage) || return TryCreatePersistentStorage(solution, workingFolderPath) ??
TryCreatePersistentStorage(solution, workingFolderPath, out persistentStorage)) TryCreatePersistentStorage(solution, workingFolderPath) ??
{ NoOpPersistentStorage.Instance;
return new ReferenceCountedDisposable<IChecksummedPersistentStorage>(persistentStorage);
} }
// okay, can't recover, then use no op persistent service private IChecksummedPersistentStorage? TryCreatePersistentStorage(
// so that things works old way (cache everything in memory)
return null;
}
private bool TryCreatePersistentStorage(
Solution solution, Solution solution,
string workingFolderPath, string workingFolderPath)
out IChecksummedPersistentStorage persistentStorage)
{ {
persistentStorage = null;
var databaseFilePath = GetDatabaseFilePath(workingFolderPath); var databaseFilePath = GetDatabaseFilePath(workingFolderPath);
try try
{ {
if (!TryOpenDatabase(solution, workingFolderPath, databaseFilePath, out persistentStorage)) return TryOpenDatabase(solution, workingFolderPath, databaseFilePath);
{
return false;
}
return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
...@@ -164,13 +158,13 @@ private ReferenceCountedDisposable<IChecksummedPersistentStorage> TryCreatePersi ...@@ -164,13 +158,13 @@ private ReferenceCountedDisposable<IChecksummedPersistentStorage> TryCreatePersi
IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath), recursive: true)); IOUtilities.PerformIO(() => Directory.Delete(Path.GetDirectoryName(databaseFilePath), recursive: true));
} }
return false; return null;
} }
} }
private void Shutdown() private void Shutdown()
{ {
ReferenceCountedDisposable<IChecksummedPersistentStorage> storage = null; ReferenceCountedDisposable<IChecksummedPersistentStorage>? storage = null;
lock (_lock) lock (_lock)
{ {
...@@ -217,7 +211,9 @@ private PersistentStorageReferenceCountedDisposableWrapper(ReferenceCountedDispo ...@@ -217,7 +211,9 @@ private PersistentStorageReferenceCountedDisposableWrapper(ReferenceCountedDispo
public static IChecksummedPersistentStorage AddReferenceCountToAndCreateWrapper(ReferenceCountedDisposable<IChecksummedPersistentStorage> storage) public static IChecksummedPersistentStorage AddReferenceCountToAndCreateWrapper(ReferenceCountedDisposable<IChecksummedPersistentStorage> storage)
{ {
return new PersistentStorageReferenceCountedDisposableWrapper(storage.TryAddReference()); // This should only be called from a caller that has a non-null storage that it
// already has a reference on. So .TryAddReference cannot fail.
return new PersistentStorageReferenceCountedDisposableWrapper(storage.TryAddReference() ?? throw ExceptionUtilities.Unreachable);
} }
public void Dispose() public void Dispose()
......
...@@ -32,9 +32,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) ...@@ -32,9 +32,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
case StorageDatabase.SQLite: case StorageDatabase.SQLite:
var locationService = workspaceServices.GetService<IPersistentStorageLocationService>(); var locationService = workspaceServices.GetService<IPersistentStorageLocationService>();
if (locationService != null) if (locationService != null)
{ return new SQLitePersistentStorageService(locationService);
return new SQLitePersistentStorageService(optionService, locationService);
}
break; break;
} }
......
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#nullable enable
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.Storage;
using Roslyn.Utilities; using Roslyn.Utilities;
...@@ -19,7 +20,7 @@ internal partial class SQLitePersistentStorageService : AbstractPersistentStorag ...@@ -19,7 +20,7 @@ internal partial class SQLitePersistentStorageService : AbstractPersistentStorag
private const string StorageExtension = "sqlite3"; private const string StorageExtension = "sqlite3";
private const string PersistentStorageFileName = "storage.ide"; private const string PersistentStorageFileName = "storage.ide";
private readonly IPersistentStorageFaultInjector _faultInjectorOpt; private readonly IPersistentStorageFaultInjector? _faultInjectorOpt;
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
private static extern IntPtr LoadLibrary(string dllToLoad); private static extern IntPtr LoadLibrary(string dllToLoad);
...@@ -41,6 +42,8 @@ private static bool TryInitializeLibrariesLazy() ...@@ -41,6 +42,8 @@ private static bool TryInitializeLibrariesLazy()
{ {
var myFolder = Path.GetDirectoryName( var myFolder = Path.GetDirectoryName(
typeof(SQLitePersistentStorage).Assembly.Location); typeof(SQLitePersistentStorage).Assembly.Location);
if (myFolder == null)
return false;
var is64 = IntPtr.Size == 8; var is64 = IntPtr.Size == 8;
var subfolder = is64 ? "x64" : "x86"; var subfolder = is64 ? "x64" : "x86";
...@@ -62,18 +65,15 @@ private static bool TryInitializeLibrariesLazy() ...@@ -62,18 +65,15 @@ private static bool TryInitializeLibrariesLazy()
return true; return true;
} }
public SQLitePersistentStorageService( public SQLitePersistentStorageService(IPersistentStorageLocationService locationService)
IOptionService optionService, : base(locationService)
IPersistentStorageLocationService locationService)
: base(optionService, locationService)
{ {
} }
public SQLitePersistentStorageService( public SQLitePersistentStorageService(
IOptionService optionService,
IPersistentStorageLocationService locationService, IPersistentStorageLocationService locationService,
IPersistentStorageFaultInjector faultInjector) IPersistentStorageFaultInjector faultInjector)
: this(optionService, locationService) : this(locationService)
{ {
_faultInjectorOpt = faultInjector; _faultInjectorOpt = faultInjector;
} }
...@@ -84,25 +84,23 @@ protected override string GetDatabaseFilePath(string workingFolderPath) ...@@ -84,25 +84,23 @@ protected override string GetDatabaseFilePath(string workingFolderPath)
return Path.Combine(workingFolderPath, StorageExtension, PersistentStorageFileName); return Path.Combine(workingFolderPath, StorageExtension, PersistentStorageFileName);
} }
protected override bool TryOpenDatabase( protected override IChecksummedPersistentStorage? TryOpenDatabase(
Solution solution, string workingFolderPath, string databaseFilePath, out IChecksummedPersistentStorage storage) Solution solution, string workingFolderPath, string databaseFilePath)
{ {
if (!TryInitializeLibraries()) if (!TryInitializeLibraries())
{ {
// SQLite is not supported on the current platform // SQLite is not supported on the current platform
storage = null; return null;
return false;
} }
// try to get db ownership lock. if someone else already has the lock. it will throw // try to get db ownership lock. if someone else already has the lock. it will throw
var dbOwnershipLock = TryGetDatabaseOwnership(databaseFilePath); var dbOwnershipLock = TryGetDatabaseOwnership(databaseFilePath);
if (dbOwnershipLock == null) if (dbOwnershipLock == null)
{ {
storage = null; return null;
return false;
} }
SQLitePersistentStorage sqlStorage = null; SQLitePersistentStorage? sqlStorage = null;
try try
{ {
sqlStorage = new SQLitePersistentStorage( sqlStorage = new SQLitePersistentStorage(
...@@ -110,6 +108,7 @@ protected override string GetDatabaseFilePath(string workingFolderPath) ...@@ -110,6 +108,7 @@ protected override string GetDatabaseFilePath(string workingFolderPath)
sqlStorage.Initialize(solution); sqlStorage.Initialize(solution);
return sqlStorage;
} }
catch (Exception) catch (Exception)
{ {
...@@ -126,22 +125,26 @@ protected override string GetDatabaseFilePath(string workingFolderPath) ...@@ -126,22 +125,26 @@ protected override string GetDatabaseFilePath(string workingFolderPath)
} }
throw; throw;
} }
storage = sqlStorage;
return true;
} }
private static IDisposable TryGetDatabaseOwnership(string databaseFilePath) /// <summary>
/// Returns null in the case where an IO exception prevented us from being able to acquire
/// the db lock file.
/// </summary>
private static IDisposable? TryGetDatabaseOwnership(string databaseFilePath)
{ {
return IOUtilities.PerformIO<IDisposable>(() => return IOUtilities.PerformIO<IDisposable?>(() =>
{ {
// make sure directory exist first. // make sure directory exist first.
EnsureDirectory(databaseFilePath); EnsureDirectory(databaseFilePath);
var directoryName = Path.GetDirectoryName(databaseFilePath);
Contract.ThrowIfNull(directoryName);
return File.Open( return File.Open(
Path.Combine(Path.GetDirectoryName(databaseFilePath), LockFile), Path.Combine(directoryName, LockFile),
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}); }, defaultValue: null);
} }
private static void EnsureDirectory(string databaseFilePath) private static void EnsureDirectory(string databaseFilePath)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册