// --------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // --------------------------------------------------------------------------- // --------------------------------------------------------------------- // // // --------------------------------------------------------------------- namespace Microsoft.Database.Isam { using System; using Microsoft.Isam.Esent.Interop; using Miei = Microsoft.Isam.Esent.Interop; /// /// An Instance represents the unit of recoverability for the ISAM. It is /// used to manage the set of files that comprise the transaction logs and /// databases used by the ISAM. /// public class IsamInstance : IDisposable { /// /// The read only /// private bool readOnly = false; /// /// The instance /// private JET_INSTANCE instance; /// /// The system parameters /// private IsamSystemParameters isamSystemParameters; /// /// The cleanup instance /// private bool cleanupInstance = false; /// /// The instance initialized /// private bool instanceInitialized = false; /// /// The cleanup temporary tables /// private bool cleanupTempTables = false; /// /// The disposed /// private bool disposed = false; /// /// The temporary table handle collection /// private TempTableHandleCollection tempTableHandleCollection = null; /// /// Initializes a new instance of the class. /// /// /// The directory (relative or absolute) that will contain all the files managed by the ISAM. The path must have a trailing backslash. /// public IsamInstance(string workingDirectory) { this.Create(workingDirectory, workingDirectory, workingDirectory, "ese", string.Empty, false, 8192); } /// /// Initializes a new instance of the class. /// /// /// The directory (relative or absolute) that will contain all the files managed by the ISAM. The path must have a trailing backslash. /// /// /// Set to true when this instance will only be used to access read only databases /// public IsamInstance(string workingDirectory, bool readOnly) { // if this is a RO instance then we will create a unique temp db // name so that it is easy to use the same workingDirectory for // all instances that wish to use the same db string tempDb = workingDirectory + (readOnly ? Guid.NewGuid().ToString() + ".tmp" : string.Empty); this.Create(workingDirectory, workingDirectory, tempDb, "ese", string.Empty, readOnly, 8192); } /// /// Initializes a new instance of the class. /// /// /// The directory (relative or absolute) that will contain the checkpoint file managed by the ISAM. The path must have a trailing backslash. /// /// /// The directory (relative or absolute) that will contain the transaction log files managed by the ISAM. The path must have a trailing backslash. /// /// /// The file or directory (relative or absolute) that will contain the temporary database managed by the ISAM. If the path has a trailing backslash, it will be assumed to be a directory and a custom name for the temporary database will be created. If the path does not have a trailing backslash, then it will be assumed to be a file and the temporary database will be stored in that file. /// /// /// A three character prefix that will be used to name ISAM files. This prefix can be used to make the ISAM's file names unique so that they may share directories with other instances. /// /// /// A short name that will be used to identify this instance when the ISAM emits diagnostic data. /// public IsamInstance( string checkpointFileDirectoryPath, string logfileDirectoryPath, string temporaryDatabaseFileDirectoryPath, string baseName, string eventSource) { this.Create( checkpointFileDirectoryPath, logfileDirectoryPath, temporaryDatabaseFileDirectoryPath, baseName, eventSource, false, 8192); } /// /// Initializes a new instance of the class. /// /// /// The directory (relative or absolute) that will contain the checkpoint file managed by the ISAM. The path must have a trailing backslash. /// /// /// The directory (relative or absolute) that will contain the transaction log files managed by the ISAM. The path must have a trailing backslash. /// /// /// The file or directory (relative or absolute) that will contain the temporary database managed by the ISAM. If the path has a trailing backslash, it will be assumed to be a directory and a custom name for the temporary database will be created. If the path does not have a trailing backslash, then it will be assumed to be a file and the temporary database will be stored in that file. /// /// /// A three character prefix that will be used to name ISAM files. This prefix can be used to make the ISAM's file names unique so that they may share directories with other instances. /// /// /// A short name that will be used to identify this instance when the ISAM emits diagnostic data. /// /// /// Set to true when this instance will only be used to access read only databases /// /// /// Set to the page size that will be used by all databases managed by the instance. /// public IsamInstance( string checkpointFileDirectoryPath, string logfileDirectoryPath, string temporaryDatabaseFileDirectoryPath, string baseName, string eventSource, bool readOnly, int pageSize) { this.Create( checkpointFileDirectoryPath, logfileDirectoryPath, temporaryDatabaseFileDirectoryPath, baseName, eventSource, readOnly, pageSize); } /// /// Finalizes an instance of the IsamInstance class /// ~IsamInstance() { this.Dispose(false); } /// /// Gets a object that provides access to a boatload of parameters that can be used to /// tweak various aspects of the ISAM's behavior or performance for /// this instance. /// public IsamSystemParameters IsamSystemParameters { get { this.CheckDisposed(); return this.isamSystemParameters; } } /// /// Gets the inst. /// /// /// The inst. /// internal JET_INSTANCE Inst { get { return this.instance; } } /// /// Gets a value indicating whether [read only]. /// /// /// true if [read only]; otherwise, false. /// internal bool ReadOnly { get { return this.readOnly; } } /// /// Gets or sets a value indicating whether [disposed]. /// /// /// true if [disposed]; otherwise, false. /// internal bool Disposed { get { return this.disposed; } set { this.disposed = value; } } /// /// Gets the temporary table handles. /// /// /// The temporary table handles. /// internal TempTableHandleCollection TempTableHandles { get { this.CheckDisposed(); return this.tempTableHandleCollection; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { lock (this) { this.Dispose(true); } GC.SuppressFinalize(this); } /// /// Creates a session /// /// a session associated with this instance public IsamSession CreateSession() { lock (this) { this.CheckDisposed(); if (!this.instanceInitialized) { try { // FUTURE-2015/06/21-BrettSh - Someone should explain to me, why we have JetInit in Database.cs, in here // and in PersistentDictionary.cs. These different APIs are supposed to be consolidating. Anyways, either // someone will DatabaseConfig enable this and then this should consume DatabaseRecoveryFlags from there, or // this should be deprecated and we'll just use Database.cs going forward (which I think is plan). Api.JetInit(ref this.instance); } catch (EsentErrorException) { // if JETInit throws then we must not call JETTerm // and we must not use the instance anymore this.cleanupInstance = false; this.instance = new JET_INSTANCE(); throw; } this.instanceInitialized = true; } return new IsamSession(this); } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// void IDisposable.Dispose() { this.Dispose(); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { lock (this) { if (!this.Disposed) { if (this.cleanupTempTables) { // dispose all open temp tables for the instance before // it is shutdown // // NOTE: this is a hack to work around problems in ESE/ESENT // that we cannot fix because we must work downlevel foreach (TempTableHandle tempTableHandle in this.TempTableHandles) { Api.JetCloseTable(tempTableHandle.Sesid, tempTableHandle.Handle); } this.cleanupTempTables = false; } if (this.cleanupInstance) { // FUTURE-2015/06/21-BrettSh - Similar issue to above date, should consume DatabaseStopFlags. Api.JetTerm2(this.instance, TermGrbit.Complete); this.cleanupInstance = false; this.instanceInitialized = false; } this.Disposed = true; } } } /// /// Creates the specified checkpoint file directory path. /// /// The checkpoint file directory path. /// The logfile directory path. /// The temporary database file directory path. /// Name of the base. /// The event source. /// if set to true [is read only]. /// Size of the page. private void Create( string checkpointFileDirectoryPath, string logfileDirectoryPath, string temporaryDatabaseFileDirectoryPath, string baseName, string eventSource, bool isReadOnly, int pageSize) { lock (this) { try { Miei.SystemParameters.Configuration = 0; Miei.SystemParameters.DatabasePageSize = pageSize; } catch (EsentAlreadyInitializedException) { } this.readOnly = isReadOnly; Api.JetCreateInstance(out this.instance, eventSource); this.cleanupInstance = true; this.instanceInitialized = false; this.isamSystemParameters = new IsamSystemParameters(this); this.tempTableHandleCollection = new TempTableHandleCollection(true); this.cleanupTempTables = true; this.isamSystemParameters.CreatePathIfNotExist = true; this.isamSystemParameters.PageTempDBMin = 14; this.isamSystemParameters.LogFileSize = 64; this.isamSystemParameters.CircularLog = true; this.isamSystemParameters.SystemPath = checkpointFileDirectoryPath; this.isamSystemParameters.LogFilePath = logfileDirectoryPath; this.isamSystemParameters.TempPath = temporaryDatabaseFileDirectoryPath; this.isamSystemParameters.BaseName = baseName; this.isamSystemParameters.EventSource = eventSource; this.isamSystemParameters.Recovery = this.readOnly ? "off" : "on"; } } /// /// Checks the disposed. /// /// /// Thrown when the object is already disposed. /// private void CheckDisposed() { lock (this) { if (this.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } } } } }