private static bool TryInitializeLibrariesLazy() { // Attempt to load the correct version of e_sqlite.dll. That way when we call // into SQLitePCL.Batteries_V2.Init it will be able to find it. // // Only do this on Windows when we can safely do the LoadLibrary call to this // direct dll. On other platforms, it is the responsibility of the host to ensure // that the necessary sqlite library has already been loaded such that SQLitePCL.Batteries_V2 // will be able to call into it. if (Environment.OSVersion.Platform == PlatformID.Win32NT) { var myFolder = Path.GetDirectoryName( typeof(SQLitePersistentStorage).Assembly.Location); var is64 = IntPtr.Size == 8; var subfolder = is64 ? "x64" : "x86"; LoadLibrary(Path.Combine(myFolder, subfolder, "e_sqlite3.dll")); } try { // Necessary to initialize SQLitePCL. SQLitePCL.Batteries_V2.Init(); } catch (Exception e) when(e is DllNotFoundException || e is EntryPointNotFoundException) { StorageDatabaseLogger.LogException(e); return(false); } return(true); }
private void ProcessWriteQueue( SqlConnection connection, ArrayBuilder <Action <SqlConnection> > writesToProcess) { if (writesToProcess.Count == 0) { return; } if (_shutdownTokenSource.Token.IsCancellationRequested) { // Don't actually try to perform any writes if we've been asked to shutdown. return; } try { // Create a transaction and perform all writes within it. connection.RunInTransaction(() => { foreach (var action in writesToProcess) { action(connection); } }); } catch (Exception ex) { StorageDatabaseLogger.LogException(ex); } }
public override void Close() { // Flush all pending writes so that all data our features wanted written // are definitely persisted to the DB. try { FlushAllPendingWritesAsync(CancellationToken.None).Wait(); } catch (Exception e) { // Flushing may fail. We still have to close all our connections. StorageDatabaseLogger.LogException(e); } lock (_connectionGate) { // Notify any outstanding async work that it should stop. _shutdownTokenSource.Cancel(); // Go through all our pooled connections and close them. while (_connectionsPool.Count > 0) { var connection = _connectionsPool.Pop(); connection.Close_OnlyForUseBySqlPersistentStorage(); } } }
public Task <Stream> ReadStreamAsync(TKey key, CancellationToken cancellationToken) { // Note: we're technically fully synchronous. However, we're called from several // async methods. We just return a Task<stream> here so that all our callers don't // need to call Task.FromResult on us. cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { using (var pooledConnection = Storage.GetPooledConnection()) { var connection = pooledConnection.Connection; if (TryGetDatabaseId(connection, key, out var dataId)) { // Ensure all pending document writes to this name are flushed to the DB so that // we can find them below. FlushPendingWrites(connection, key); try { // Lookup the row from the DocumentData table corresponding to our dataId. return(Task.FromResult(ReadBlob(connection, dataId))); } catch (Exception ex) { StorageDatabaseLogger.LogException(ex); } } } } return(SpecializedTasks.Default <Stream>()); }
private int?TryGetStringIdFromDatabaseWorker( SqlConnection connection, string value, bool canReturnNull) { try { using var resettableStatement = connection.GetResettableStatement(_select_star_from_0_where_1_limit_one); var statement = resettableStatement.Statement; // SQLite's binding indices are 1-based. statement.BindStringParameter(parameterIndex: 1, value: value); var stepResult = statement.Step(); if (stepResult == Result.ROW) { return(statement.GetInt32At(columnIndex: 0)); } } catch (Exception ex) { // If we simply failed to even talk to the DB then we have to bail out. There's // nothing we can accomplish at this point. StorageDatabaseLogger.LogException(ex); return(null); } // No item with this value in the table. if (canReturnNull) { return(null); } // This should not be possible. We only called here if we got a constraint violation. // So how could we then not find the string in the table? throw new InvalidOperationException(); }
private int?TryGetStringIdFromDatabase(SqlConnection connection, string value) { // First, check if we can find that string in the string table. var stringId = TryGetStringIdFromDatabaseWorker(connection, value, canReturnNull: true); if (stringId != null) { // Found the value already in the db. Another process (or thread) might have added it. // We're done at this point. return(stringId); } // The string wasn't in the db string table. Add it. Note: this may fail if some // other thread/process beats us there as this table has a 'unique' constraint on the // values. try { connection.RunInTransaction(() => { stringId = InsertStringIntoDatabase_MustRunInTransaction(connection, value); }); Contract.ThrowIfTrue(stringId == null); return(stringId); } catch (SqlException ex) when(ex.Result == Result.CONSTRAINT) { // We got a constraint violation. This means someone else beat us to adding this // string to the string-table. We should always be able to find the string now. stringId = TryGetStringIdFromDatabaseWorker(connection, value, canReturnNull: false); return(stringId); } catch (Exception ex) { // Some other error occurred. Log it and return nothing. StorageDatabaseLogger.LogException(ex); } return(null); }
public async Task <Stream> ReadStreamAsync(TKey key, CancellationToken cancellationToken) { // Note: we're technically fully synchronous. However, we're called from several // async methods. We just return a Task<stream> here so that all our callers don't // need to call Task.FromResult on us. cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { bool haveDataId; TDatabaseId dataId; using (var pooledConnection = Storage.GetPooledConnection()) { haveDataId = TryGetDatabaseId(pooledConnection.Connection, key, out dataId); } if (haveDataId) { // Ensure all pending document writes to this name are flushed to the DB so that // we can find them below. await FlushPendingWritesAsync(key, cancellationToken).ConfigureAwait(false); try { using (var pooledConnection = Storage.GetPooledConnection()) { // Lookup the row from the DocumentData table corresponding to our dataId. return(ReadBlob(pooledConnection.Connection, dataId)); } } catch (Exception ex) { StorageDatabaseLogger.LogException(ex); } } } return(null); }
private async Task <Stream> ReadBlobColumnAsync( TKey key, string columnName, Checksum checksumOpt, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (!Storage._shutdownTokenSource.IsCancellationRequested) { bool haveDataId; TDatabaseId dataId; using (var pooledConnection = Storage.GetPooledConnection()) { haveDataId = TryGetDatabaseId(pooledConnection.Connection, key, out dataId); } if (haveDataId) { // Ensure all pending document writes to this name are flushed to the DB so that // we can find them below. await FlushPendingWritesAsync(key, cancellationToken).ConfigureAwait(false); try { using (var pooledConnection = Storage.GetPooledConnection()) { // Lookup the row from the DocumentData table corresponding to our dataId. return(ReadBlob( pooledConnection.Connection, dataId, columnName, checksumOpt, cancellationToken)); } } catch (Exception ex) { StorageDatabaseLogger.LogException(ex); } } } return(null); }
private bool TryGetUniqueId(string value, bool fileCheck, out int id) { id = default(int); if (string.IsNullOrWhiteSpace(value)) { return(false); } // if we already know, get the id if (_nameTableCache.TryGetValue(value, out id)) { return(true); } // we only persist for things that actually exist if (fileCheck && !File.Exists(value)) { return(false); } try { var uniqueIdValue = fileCheck ? PathUtilities.GetRelativePath(Path.GetDirectoryName(SolutionFilePath), value) : value; id = _nameTableCache.GetOrAdd(value, _esentStorage.GetUniqueId(uniqueIdValue)); return(true); } catch (Exception ex) { // if we get fatal errors from esent such as disk out of space or log file corrupted by other process and etc // don't crash VS, but let VS know it can't use esent. we will gracefully recover issue by using memory. StorageDatabaseLogger.LogException(ex); return(false); } }
private void CloseWorker() { // Flush all pending writes so that all data our features wanted written are definitely // persisted to the DB. try { FlushWritesOnClose(); } catch (Exception e) { // Flushing may fail. We still have to close all our connections. StorageDatabaseLogger.LogException(e); } lock (_connectionGate) { // Go through all our pooled connections and close them. while (_connectionsPool.Count > 0) { var connection = _connectionsPool.Pop(); connection.Close_OnlyForUseBySqlPersistentStorage(); } } }
/// <summary> /// Returns 'true' if the bulk population succeeds, or false if it doesn't. /// </summary> private bool BulkPopulateProjectIdsWorker(SqlConnection connection, Project project) { // First, in bulk, get string-ids for all the paths and names for the project and documents. if (!AddIndividualProjectAndDocumentComponentIds()) { return(false); } // Now, ensure we have the project id known locally. We cannot do this until we've // gotten all the IDs for the individual project components as the project ID is built // from a compound key using the IDs for the project's FilePath and Name. // // If this fails for any reason, we can't proceed. var projectId = TryGetProjectId(connection, project); if (projectId == null) { return(false); } // Finally, in bulk, determine the final DB IDs for all our documents. We cannot do // this until we have the project-id as the document IDs are built from a compound // ID including the project-id. return(AddDocumentIds()); // Local functions below. // Use local functions so that other members of this class don't accidentally use these. // There are invariants in the context of BulkPopulateProjectIdsWorker that these functions // can depend on. bool AddIndividualProjectAndDocumentComponentIds() { var stringsToAdd = new HashSet <string>(); AddIfUnknownId(project.FilePath, stringsToAdd); AddIfUnknownId(project.Name, stringsToAdd); foreach (var document in project.Documents) { AddIfUnknownId(document.FilePath, stringsToAdd); AddIfUnknownId(document.Name, stringsToAdd); } return(AddStrings(stringsToAdd)); } bool AddStrings(HashSet <string> stringsToAdd) { if (stringsToAdd.Count > 0) { using (var idToString = s_dictionaryPool.GetPooledObject()) { try { connection.RunInTransaction(() => { foreach (var value in stringsToAdd) { var id = InsertStringIntoDatabase_MustRunInTransaction(connection, value); idToString.Object.Add(id, value); } }); } catch (SqlException ex) when(ex.Result == Result.CONSTRAINT) { // Constraint exceptions are possible as we may be trying bulk insert // strings while some other thread/process does the same. return(false); } catch (Exception ex) { // Something failed. Log the issue, and let the caller know we should stop // with the bulk population. StorageDatabaseLogger.LogException(ex); return(false); } // We succeeded inserting all the strings. Ensure our local cache has all the // values we added. foreach (var kvp in idToString.Object) { _stringToIdMap[kvp.Value] = kvp.Key; } } } return(true); } bool AddDocumentIds() { var stringsToAdd = new HashSet <string>(); foreach (var document in project.Documents) { // Produce the string like "projId-docPathId-docNameId" so that we can get a // unique ID for it. AddIfUnknownId(GetDocumentIdString(document), stringsToAdd); } // Ensure we have unique IDs for all these document string ids. If we fail to // bulk import these strings, we can't proceed. if (!AddStrings(stringsToAdd)) { return(false); } foreach (var document in project.Documents) { // Get the integral ID for this document. It's safe to directly index into // the map as we just successfully added these strings to the DB. var id = _stringToIdMap[GetDocumentIdString(document)]; _documentIdToIdMap.TryAdd(document.Id, id); } return(true); } string GetDocumentIdString(Document document) { // We should always be able to index directly into these maps. This function is only // ever called after we called AddIndividualProjectAndDocumentComponentIds. var documentPathId = _stringToIdMap[document.FilePath]; var documentNameId = _stringToIdMap[document.Name]; var documentIdString = SQLitePersistentStorage.GetDocumentIdString( projectId.Value, documentPathId, documentNameId); return(documentIdString); } void AddIfUnknownId(string value, HashSet <string> stringsToAdd) { // Null strings are not supported at all. Just ignore these. Any read/writes // to null values will fail and will return 'false/null' to indicate failure // (which is part of the documented contract of the persistence layer API). if (value == null) { return; } if (!_stringToIdMap.TryGetValue(value, out var id)) { stringsToAdd.Add(value); } else { // We did know about this string. However, we want to ensure that the // actual string instance we're pointing to is the one produced by the // rest of the workspace, and not by the database. This way we don't // end up having tons of duplicate strings in the storage service. // // So overwrite whatever we have so far in the table so we can release // the DB strings. _stringToIdMap[value] = id; } } }