//----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. // //----------------------------------------------------------------------- namespace Microsoft.Isam.Esent.Interop { using System; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; /// /// Receives information about the progress of long-running operations, /// such as defragmentation, backup, or restore operations. During such /// operations, the database engine calls this callback function to give /// an update on the progress of the operation. /// /// /// The session with which the long running operation was called. /// /// The type of operation. /// The status of the operation. /// Optional data. May be a . /// An error code. public delegate JET_err JET_PFNSTATUS(JET_SESID sesid, JET_SNP snp, JET_SNT snt, object data); /// /// Receives information about the progress of long-running operations, /// such as defragmentation, backup, or restore operations. During such /// operations, the database engine calls this callback function to give /// an update on the progress of the operation. /// /// /// This is the internal version of the callback. The final parameter is /// a void* pointer, which may point to a NATIVE_SNPROG. /// /// /// The session with which the long running operation was called. /// /// The type of operation. /// The status of the operation. /// Optional . /// An error code. internal delegate JET_err NATIVE_PFNSTATUS(IntPtr nativeSesid, uint snp, uint snt, IntPtr snprog); /// /// Wraps a NATIVE_PFNSTATUS callback around a JET_PFNSTATUS. This is /// used to convert the snprog argument to a managed snprog. /// internal sealed class StatusCallbackWrapper { /// /// API call tracing. /// private static readonly TraceSwitch TraceSwitch = new TraceSwitch("ESENT StatusCallbackWrapper", "Wrapper around unmanaged ESENT status callback"); /// /// The wrapped status callback. /// private readonly JET_PFNSTATUS wrappedCallback; /// /// The native version of the callback. This will be a closure (because we are wrapping /// a non-static method) so keep track of it here to make sure it isn't garbage collected. /// private readonly NATIVE_PFNSTATUS nativeCallback; #if !MANAGEDESENT_ON_WSA /// /// Initializes static members of the class. /// static StatusCallbackWrapper() { // We don't want a JIT failure when trying to execute the callback // because that would throw an exception through ESENT, corrupting it. // It is fine for the wrapped callback to fail because CallbackImpl // will catch the exception and deal with it. RuntimeHelpers.PrepareMethod(typeof(StatusCallbackWrapper).GetMethod( "CallbackImpl", BindingFlags.NonPublic | BindingFlags.Instance).MethodHandle); } #endif /// /// Initializes a new instance of the StatusCallbackWrapper class. /// /// /// The managed callback to use. /// public StatusCallbackWrapper(JET_PFNSTATUS wrappedCallback) { this.wrappedCallback = wrappedCallback; this.nativeCallback = wrappedCallback != null ? this.CallbackImpl : (NATIVE_PFNSTATUS)null; } /// /// Gets a NATIVE_PFNSTATUS callback that wraps the managed callback. /// public NATIVE_PFNSTATUS NativeCallback { get { return this.nativeCallback; } } /// /// Gets or sets the saved exception. If the callback throws an exception /// it is saved here and should be rethrown when the API call finishes. /// private Exception SavedException { get; set; } /// /// Gets or sets a value indicating whether the thread was aborted during /// the callback. /// private bool ThreadWasAborted { get; set; } /// /// If an exception was generated during a callback throw it. /// public void ThrowSavedException() { #if !MANAGEDESENT_ON_WSA // Thread model has changed in Windows store apps. if (this.ThreadWasAborted) { Thread.CurrentThread.Abort(); } #endif if (null != this.SavedException) { throw this.SavedException; } } /// /// Callback function for native code. We don't want to throw an exception through /// unmanaged ESENT because that will corrupt ESENT's internal state. Instead we /// catch all exceptions and return an error instead. We use a CER to make catching /// the exceptions as reliable as possible. /// /// /// The session with which the long running operation was called. /// /// The type of operation. /// The status of the operation. /// Optional . /// An error code. private JET_err CallbackImpl(IntPtr nativeSesid, uint nativeSnp, uint nativeSnt, IntPtr nativeData) { RuntimeHelpers.PrepareConstrainedRegions(); try { var sesid = new JET_SESID { Value = nativeSesid }; JET_SNP snp = (JET_SNP)nativeSnp; JET_SNT snt = (JET_SNT)nativeSnt; object data = CallbackDataConverter.GetManagedData(nativeData, snp, snt); return this.wrappedCallback(sesid, snp, snt, data); } #if !MANAGEDESENT_ON_WSA // Thread model has changed in windows store apps. catch (ThreadAbortException) { Trace.WriteLineIf(TraceSwitch.TraceWarning, "Caught ThreadAbortException"); // Stop the thread abort and let the unmanaged ESENT code finish. // ThrowSavedException will call Thread.Abort() again. this.ThreadWasAborted = true; LibraryHelpers.ThreadResetAbort(); return JET_err.CallbackFailed; } #endif catch (Exception ex) { Trace.WriteLineIf( TraceSwitch.TraceWarning, string.Format(CultureInfo.InvariantCulture, "Caught Exception {0}", ex)); this.SavedException = ex; return JET_err.CallbackFailed; } // What happens if the thread is aborted here, outside of the CER? // We probably throw the exception through ESENT, which isn't good. } } }