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);
            }
        }
예제 #3
0
        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();
                }
            }
        }
예제 #4
0
            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();
        }
예제 #6
0
        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);
            }
예제 #9
0
        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;
                }
            }
        }