/// <summary>
        /// Returns a new database loaded from the specified path. If null is
        /// returned, <see cref="GetCurrentDatabase"/> will assume the default
        /// completion DB is intended.
        /// </summary>
        /// <remarks>
        /// This is intended for overriding in derived classes. To get a
        /// queryable database instance, use <see cref="GetCurrentDatabase"/> or
        /// <see cref="CreateInterpreter"/>.
        /// </remarks>
        public virtual PythonTypeDatabase MakeTypeDatabase(string databasePath, bool includeSitePackages = true)
        {
            if (!_generating && PythonTypeDatabase.IsDatabaseVersionCurrent(databasePath))
            {
                var paths = new List <string>();
                paths.Add(databasePath);
                if (includeSitePackages)
                {
                    paths.AddRange(Directory.EnumerateDirectories(databasePath));
                }

                try {
                    return(new PythonTypeDatabase(this, paths));
                } catch (IOException) {
                } catch (UnauthorizedAccessException) {
                }
            }

            return(PythonTypeDatabase.CreateDefaultTypeDatabase(this));
        }
        /// <summary>
        /// Called to manually trigger a refresh of <see cref="IsCurrent"/>.
        /// After completion, <see cref="IsCurrentChanged"/> will always be
        /// raised, regardless of whether the values were changed.
        /// </summary>
        public virtual void RefreshIsCurrent()
        {
            try {
                if (!_isCurrentSemaphore.Wait(0))
                {
                    // Another thread is working on our state, so we will wait for
                    // them to finish and return, since the value is up to date.
                    _isCurrentSemaphore.Wait();
                    _isCurrentSemaphore.Release();
                    return;
                }
            } catch (ObjectDisposedException) {
                // We've been disposed and the call has come in from
                // externally, probably a timer.
                return;
            }

            try {
                _isCheckingDatabase = true;
                OnIsCurrentChanged();

                // Wait for a slot that will allow us to scan the disk. This
                // prevents too many threads from updating at once and causing
                // I/O saturation.
                _sharedIsCurrentSemaphore.Wait();

                try {
                    _generating     = PythonTypeDatabase.IsDatabaseRegenerating(DatabasePath);
                    WatchingLibrary = !_generating;

                    if (_generating)
                    {
                        // Skip the rest of the checks, because we know we're
                        // not current.
                    }
                    else if (!PythonTypeDatabase.IsDatabaseVersionCurrent(DatabasePath))
                    {
                        _isValid            = false;
                        _missingModules     = null;
                        _isCurrentException = null;
                    }
                    else
                    {
                        _isValid = true;
                        HashSet <string> existingDatabase = null;
                        string[]         missingModules   = null;

                        for (int retries = 3; retries > 0; --retries)
                        {
                            try {
                                existingDatabase = GetExistingDatabase(DatabasePath);
                                break;
                            } catch (UnauthorizedAccessException) {
                            } catch (IOException) {
                            }
                            Thread.Sleep(100);
                        }

                        if (existingDatabase == null)
                        {
                            // This will either succeed or throw again. If it throws
                            // then the error is reported to the user.
                            existingDatabase = GetExistingDatabase(DatabasePath);
                        }

                        for (int retries = 3; retries > 0; --retries)
                        {
                            try {
                                missingModules = GetMissingModules(existingDatabase);
                                break;
                            } catch (UnauthorizedAccessException) {
                            } catch (IOException) {
                            }
                            Thread.Sleep(100);
                        }

                        if (missingModules == null)
                        {
                            // This will either succeed or throw again. If it throws
                            // then the error is reported to the user.
                            missingModules = GetMissingModules(existingDatabase);
                        }

                        if (missingModules.Length > 0)
                        {
                            var oldModules = _missingModules;
                            if (oldModules == null ||
                                oldModules.Length != missingModules.Length ||
                                !oldModules.SequenceEqual(missingModules))
                            {
                            }
                            _missingModules = missingModules;
                        }
                        else
                        {
                            _missingModules = null;
                        }
                    }
                    _isCurrentException = null;
                } finally {
                    _sharedIsCurrentSemaphore.Release();
                }
            } catch (Exception ex) {
                // Report the exception text as the reason.
                _isCurrentException = ex.ToString();
                _missingModules     = null;
            } finally {
                _isCheckingDatabase = false;
                try {
                    _isCurrentSemaphore.Release();
                } catch (ObjectDisposedException) {
                    // The semaphore is not locked for disposal as it is only
                    // used to prevent reentrance into this function. As a
                    // result, it may have been disposed while we were in here.
                }
            }

            OnIsCurrentChanged();
        }
        public PythonInterpreterFactoryWithDatabase(
            InterpreterConfiguration config,
            InterpreterFactoryCreationOptions options
            )
        {
            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }
            if (options == null)
            {
                options = new InterpreterFactoryCreationOptions();
            }
            Configuration = config;

            _databasePath = options.DatabasePath;

            // Avoid creating a interpreter with an unsupported version.
            // https://github.com/Microsoft/PTVS/issues/706
            try {
                var langVer = config.Version.ToLanguageVersion();
            } catch (InvalidOperationException ex) {
                throw new ArgumentException(ex.Message, ex);
            }

            if (!GlobalInterpreterOptions.SuppressFileSystemWatchers && options.WatchFileSystem && !string.IsNullOrEmpty(DatabasePath))
            {
                // Assume the database is valid if the version is up to date, then
                // switch to invalid after we've checked.
                _isValid            = PythonTypeDatabase.IsDatabaseVersionCurrent(DatabasePath);
                _isCheckingDatabase = true;

                _verWatcher    = CreateDatabaseVerWatcher();
                _verDirWatcher = CreateDatabaseDirectoryWatcher();
#if DEBUG
                var creationStack = new StackTrace(true).ToString();
                Task.Delay(1000).ContinueWith(t => {
                    Debug.Assert(
                        _hasEverCheckedDatabase,
                        "Database check was not triggered for {0}".FormatUI(Configuration.Id),
                        creationStack
                        );
                });
#endif
            }
            else
            {
                // Assume the database is valid
                _isValid = true;
            }

            if (!GlobalInterpreterOptions.SuppressPackageManagers)
            {
                try {
                    var pm = options.PackageManager;
                    if (pm != null)
                    {
                        pm.SetInterpreterFactory(this);
                        pm.InstalledFilesChanged += PackageManager_InstalledFilesChanged;
                        PackageManager            = pm;
                    }
                } catch (NotSupportedException) {
                }
            }
        }