Skip to content

Commit

Permalink
3 product changes and 1 test change
Browse files Browse the repository at this point in the history
#1. made persisted storage exclusive to 1 process. like before, second VS with same solution will not use persisted storage.
#2, tweaked sqlite to share cache between connections like esent.
#3, made VS and OOP to use different db files (we can make them to share if we want to, but for now, nobody requires it so we create 2 different db)

..

made OOP mock to enable persisted service.
  • Loading branch information
heejaechang committed Aug 15, 2017
1 parent 94e8e42 commit c2cde38
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public static async Task<RemoteHostClient> CreateAsync(
}
}

private static async Task RegisterWorkspaceHostAsync(Workspace workspace, RemoteHostClient client)
// internal for debugging purpose
internal static async Task RegisterWorkspaceHostAsync(Workspace workspace, RemoteHostClient client)
{
var vsWorkspace = workspace as VisualStudioWorkspaceImpl;
if (vsWorkspace == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ public async Task<RemoteHostClient> CreateAsync(Workspace workspace, Cancellatio
// this is the point where we can create different kind of remote host client in future (cloud or etc)
if (workspace.Options.GetOption(RemoteHostClientFactoryOptions.RemoteHost_InProc))
{
return await InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: true, cancellationToken: cancellationToken).ConfigureAwait(false);
var client = await InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: true, cancellationToken: cancellationToken).ConfigureAwait(false);

// register workspace host for in proc remote host client
await ServiceHubRemoteHostClient.RegisterWorkspaceHostAsync(workspace, client).ConfigureAwait(false);

return client;
}

return await ServiceHubRemoteHostClient.CreateAsync(workspace, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ internal enum OpenFlags
// SQLITE_OPEN_SUBJOURNAL = 0x00002000, /* VFS only */
// SQLITE_OPEN_MASTER_JOURNAL = 0x00004000, /* VFS only */
// SQLITE_OPEN_NOMUTEX = 0x00008000, /* Ok for sqlite3_open_v2() */
// SQLITE_OPEN_FULLMUTEX = 0x00010000, /* Ok for sqlite3_open_v2() */
// SQLITE_OPEN_SHAREDCACHE = 0x00020000, /* Ok for sqlite3_open_v2() */
SQLITE_OPEN_FULLMUTEX = 0x00010000, /* Ok for sqlite3_open_v2() */
SQLITE_OPEN_SHAREDCACHE = 0x00020000, /* Ok for sqlite3_open_v2() */
// SQLITE_OPEN_PRIVATECACHE = 0x00040000, /* Ok for sqlite3_open_v2() */
// SQLITE_OPEN_WAL = 0x00080000, /* VFS only */
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ public static SqlConnection Create(IPersistentStorageFaultInjector faultInjector
{
faultInjector?.OnNewConnection();

var flags = OpenFlags.SQLITE_OPEN_CREATE | OpenFlags.SQLITE_OPEN_READWRITE;
// Explicitly set for Serialized mode to be safe.
// Also, enable shared cache so that multiple connections inside of same process share cache
// see https://sqlite.org/threadsafe.html for more detail
var flags = OpenFlags.SQLITE_OPEN_CREATE | OpenFlags.SQLITE_OPEN_READWRITE | OpenFlags.SQLITE_OPEN_FULLMUTEX | OpenFlags.SQLITE_OPEN_SHAREDCACHE;
var result = (Result)raw.sqlite3_open_v2(databasePath, out var handle, (int)flags, vfs: null);

if (result != Result.OK)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ static SQLitePersistentStorage()

private readonly CancellationTokenSource _shutdownTokenSource = new CancellationTokenSource();

private readonly IDisposable _dbOwnershipLock;
private readonly IPersistentStorageFaultInjector _faultInjectorOpt;

// Accessors that allow us to retrieve/store data into specific DB tables. The
Expand All @@ -146,10 +147,13 @@ public SQLitePersistentStorage(
string solutionFilePath,
string databaseFile,
Action<AbstractPersistentStorage> disposer,
IDisposable dbOwnershipLock,
IPersistentStorageFaultInjector faultInjectorOpt)
: base(optionService, workingFolderPath, solutionFilePath, databaseFile, disposer)
{
_dbOwnershipLock = dbOwnershipLock;
_faultInjectorOpt = faultInjectorOpt;

_solutionAccessor = new SolutionAccessor(this);
_projectAccessor = new ProjectAccessor(this);
_documentAccessor = new DocumentAccessor(this);
Expand Down Expand Up @@ -212,6 +216,9 @@ public override void Close()
connection.Close_OnlyForUseBySqlPersistentStorage();
}
}

// let the lock go
_dbOwnershipLock.Dispose();
}

private PooledConnection GetPooledConnection()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.CodeAnalysis.SQLite
{
internal partial class SQLitePersistentStorageService : AbstractPersistentStorageService
{
private const string LockFile = "db.lock";
private const string StorageExtension = "sqlite3";
private const string PersistentStorageFileName = "storage.ide";

Expand All @@ -24,7 +25,7 @@ public SQLitePersistentStorageService(
{
}

public SQLitePersistentStorageService(IOptionService optionService, IPersistentStorageFaultInjector faultInjector)
public SQLitePersistentStorageService(IOptionService optionService, IPersistentStorageFaultInjector faultInjector)
: base(optionService, testing: true)
{
_faultInjectorOpt = faultInjector;
Expand All @@ -37,11 +38,50 @@ protected override string GetDatabaseFilePath(string workingFolderPath)
}

protected override AbstractPersistentStorage OpenDatabase(Solution solution, string workingFolderPath, string databaseFilePath)
=> new SQLitePersistentStorage(
OptionService, workingFolderPath, solution.FilePath, databaseFilePath, this.Release, _faultInjectorOpt);
{
// try to get db ownership lock. if someone else already has the lock. it will throw
var dbOwnershipLock = TryGetDatabaseOwnership(databaseFilePath);

return new SQLitePersistentStorage(
OptionService, workingFolderPath, solution.FilePath, databaseFilePath, this.Release, dbOwnershipLock, _faultInjectorOpt);
}

private static IDisposable TryGetDatabaseOwnership(string databaseFilePath)
{
try
{
// make sure directory exist first.
EnsureDirectory(databaseFilePath);

return File.Open(
Path.Combine(Path.GetDirectoryName(databaseFilePath), LockFile),
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
catch (Exception ex)
{
throw new InvalidOperationException("can't get the ownership", ex);
}
}

private static void EnsureDirectory(string databaseFilePath)
{
var directory = Path.GetDirectoryName(databaseFilePath);
if (Directory.Exists(directory))
{
return;
}

Directory.CreateDirectory(directory);
}

protected override bool ShouldDeleteDatabase(Exception exception)
{
if (exception is InvalidOperationException)
{
// db is owned by another process
return false;
}

// Error occurred when trying to open this DB. Try to remove it so we can create a good dB.
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public static void UpdateStorageLocation(SolutionId id, string storageLocation)
}
else
{
_idToStorageLocation[id] = storageLocation;
// Store the esent database in a different location for the out of proc server.
_idToStorageLocation[id] = Path.Combine(storageLocation, "Server");
}
}
}
Expand Down

0 comments on commit c2cde38

Please sign in to comment.