Пример #1
0
        /// <summary>Start the Network Thread, using the specified API version level</summary>
        /// <param name="apiVersion">API version that will be used by this application. A value of 0 mean "max safe default version".</param>
        /// <remarks>This method can only be called once per process, and the API version cannot be changed until the process restarts.</remarks>
        public static void Start(int apiVersion)
        {
            if (s_started)
            {
                return;
            }

            //BUGBUG: Specs say we cannot restart the network thread anymore in the process after stopping it ! :(

            s_started = true;

            apiVersion = CheckApiVersion(apiVersion);
            if (Logging.On)
            {
                Logging.Info(typeof(Fdb), "Start", $"Selecting fdb API version {apiVersion}");
            }

            FdbError err = FdbNative.SelectApiVersion(apiVersion);

            if (err != FdbError.Success)
            {
                if (Logging.On)
                {
                    Logging.Error(typeof(Fdb), "Start", $"Failed to fdb API version {apiVersion}: {err}");
                }

                switch (err)
                {
                case FdbError.ApiVersionNotSupported:
                {                         // bad version was selected ?
                    // note: we already bound check the values before, so that means that fdb_c.dll is either an older version or an incompatible new version.
                    throw new FdbException(err, $"The API version {apiVersion} is not supported by the FoundationDB client library (fdb_c.dll) installed on this system. The binding only supports versions {GetMinApiVersion()} to {GetMaxApiVersion()}. You either need to upgrade the .NET binding or the FoundationDB client library to a newer version.");
                }

#if DEBUG
                case FdbError.ApiVersionAlreadySet:
                {                         // Temporary hack to allow multiple debugging using the cached host process in VS
                    Console.Error.WriteLine("FATAL: CANNOT REUSE EXISTING PROCESS! FoundationDB client cannot be restarted once stopped. Current process will be terminated.");
                    Environment.FailFast("FATAL: CANNOT REUSE EXISTING PROCESS! FoundationDB client cannot be restarted once stopped. Current process will be terminated.");
                    break;
                }
#endif
                }
                DieOnError(err);
            }
            s_apiVersion = apiVersion;

            if (!string.IsNullOrWhiteSpace(Fdb.Options.TracePath))
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will trace client activity in '{Fdb.Options.TracePath}'");
                }
                // create trace directory if missing...
                if (!SystemIO.Directory.Exists(Fdb.Options.TracePath))
                {
                    SystemIO.Directory.CreateDirectory(Fdb.Options.TracePath);
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TraceEnable, Fdb.Options.TracePath));
            }

#pragma warning disable 618
            if (!string.IsNullOrWhiteSpace(Fdb.Options.TLSPlugin))
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will use custom TLS plugin '{Fdb.Options.TLSPlugin}'");
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TLSPlugin, Fdb.Options.TLSPlugin));
            }
#pragma warning restore 618

            if (Fdb.Options.TLSCertificateBytes.IsPresent)
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS root certificate and private key from memory ({Fdb.Options.TLSCertificateBytes.Count} bytes)");
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TLSCertBytes, Fdb.Options.TLSCertificateBytes));
            }
            else if (!string.IsNullOrWhiteSpace(Fdb.Options.TLSCertificatePath))
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS root certificate and private key from '{Fdb.Options.TLSCertificatePath}'");
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TLSCertPath, Fdb.Options.TLSCertificatePath));
            }

            if (Fdb.Options.TLSPrivateKeyBytes.IsPresent)
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS private key from memory ({Fdb.Options.TLSPrivateKeyBytes.Count} bytes)");
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TLSKeyBytes, Fdb.Options.TLSPrivateKeyBytes));
            }
            else if (!string.IsNullOrWhiteSpace(Fdb.Options.TLSPrivateKeyPath))
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will load TLS private key from '{Fdb.Options.TLSPrivateKeyPath}'");
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TLSKeyPath, Fdb.Options.TLSPrivateKeyPath));
            }

            if (Fdb.Options.TLSVerificationPattern.IsPresent)
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", $"Will verify TLS peers with pattern '{Fdb.Options.TLSVerificationPattern}'");
                }

                DieOnError(SetNetworkOption(FdbNetworkOption.TLSVerifyPeers, Fdb.Options.TLSVerificationPattern));
            }

            try { }
            finally
            {
                // register with the AppDomain to ensure that everything is cleared when the process exists
                s_appDomainUnloadHandler = (sender, args) =>
                {
                    if (s_started)
                    {
                        //note: since app domain is unloading, the logger may also have already stopped...
                        if (Logging.On)
                        {
                            Logging.Verbose(typeof(Fdb), "AppDomainUnloadHandler", "AppDomain is unloading, stopping FoundationDB Network Thread...");
                        }
                        Stop();
                    }
                };
                AppDomain.CurrentDomain.DomainUnload += s_appDomainUnloadHandler;
                AppDomain.CurrentDomain.ProcessExit  += s_appDomainUnloadHandler;

                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "Start", "Setting up Network Thread...");
                }

                DieOnError(FdbNative.SetupNetwork());
                s_started = true;                 //BUGBUG: already set at the start of the method. Maybe we need state flags ?
            }

            if (Logging.On)
            {
                Logging.Info(typeof(Fdb), "Start", "Network thread has been set up");
            }

            StartEventLoop();
        }
Пример #2
0
        private static void EventLoop()
        {
            //TODO: we need to move the crash handling logic outside this method, so that an app can hook up an event and device what to do: crash or keep running (dangerous!).
            // At least, the app would get the just to do some emergency shutdown logic before letting the process die....

            try
            {
                s_eventLoopRunning = true;

                s_eventLoopThreadId = Thread.CurrentThread.ManagedThreadId;

                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "EventLoop", $"FDB Event Loop running on thread #{Fdb.s_eventLoopThreadId.Value}...");
                }

                var err = FdbNative.RunNetwork();
                if (err != FdbError.Success)
                {
                    if (s_eventLoopStopRequested || Environment.HasShutdownStarted)
                    {                     // this was requested, or can be explained by the computer shutting down...
                        if (Logging.On)
                        {
                            Logging.Info(typeof(Fdb), "EventLoop", $"The fdb network thread returned with error code {err}: {GetErrorMessage(err)}");
                        }
                    }
                    else
                    {                     // this was NOT expected !
                        if (Logging.On)
                        {
                            Logging.Error(typeof(Fdb), "EventLoop", $"The fdb network thread returned with error code {err}: {GetErrorMessage(err)}");
                        }
#if DEBUG
                        Console.Error.WriteLine("THE FDB NETWORK EVENT LOOP HAS FAILED!");
                        Console.Error.WriteLine("=> " + err);
                        // REVIEW: should we FailFast in release mode also?
                        // => this may be a bit surprising for most users when applications unexpectedly crash for for no apparent reason.
                        Environment.FailFast("The FoundationDB Network Event Loop failed with error " + err + " and was terminated.");
#endif
                    }
                }
            }
            catch (Exception e)
            {
                if (e is ThreadAbortException)
                {                 // some other thread tried to Abort() us. This probably means that we should exit ASAP...
                    Thread.ResetAbort();
                    return;
                }

                //note: any error is this thread is BAD NEWS for the process, the the network thread usually cannot be restarted safely.

                if (e is AccessViolationException)
                {
                    // An access violation occured inside the native code. This good be caused by:
                    // - a bug in fdb_c.dll
                    // - a bug in our own marshalling code that calls into fdb_c.dll
                    // - some other random heap corruption that caused us to pass bogus data to fdb_c.dll
                    // - a random cosmic ray that flipped some bits in memory...
                    if (Debugger.IsAttached)
                    {
                        Debugger.Break();
                    }

                    // This error is VERY BAD NEWS, and means that we CANNOT continue safely running fdb in this process
                    // The only reasonable option is to exit the process immediately !

                    Console.Error.WriteLine("THE FDB NETWORK EVENT LOOP HAS CRASHED!");
                    Console.Error.WriteLine("=> " + e.ToString());
                    Environment.FailFast("The FoundationDB Network Event Loop crashed with an Access Violation, and had to be terminated. You may try to create full memory dumps, as well as attach a debugger to this process (it will automatically break when this problem occurs).", e);
                    return;
                }

                if (Logging.On)
                {
                    Logging.Exception(typeof(Fdb), "EventLoop", e);
                }

#if DEBUG
                // if we are running in DEBUG build, we want to get the attention of the developer on this.
                // the best way is to make the test runner explode in mid-air with a scary looking message!

                Console.Error.WriteLine("THE FDB NETWORK EVENT LOOP HAS CRASHED!");
                Console.Error.WriteLine("=> " + e.ToString());
                // REVIEW: should we FailFast in release mode also?
                // => this may be a bit surprising for most users when applications unexpectedly crash for for no apparent reason.
                Environment.FailFast("The FoundationDB Network Event Loop crashed and had to be terminated: " + e.Message, e);
#endif
            }
            finally
            {
                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "EventLoop", "FDB Event Loop stopped");
                }

                s_eventLoopThreadId = null;
                s_eventLoopRunning  = false;
            }
        }
Пример #3
0
 /// <summary>Return the error message matching the specified error code</summary>
 public static string GetErrorMessage(FdbError code)
 {
     return(FdbNative.GetError(code));
 }
Пример #4
0
        /// <summary>Stops the thread running the FDB event loop</summary>
        private static void StopEventLoop()
        {
            if (s_eventLoopStarted)
            {
                // We cannot be called from the network thread itself, or else we will dead lock !
                Fdb.EnsureNotOnNetworkThread();

                if (Logging.On)
                {
                    Logging.Verbose(typeof(Fdb), "StopEventLoop", "Stopping network thread...");
                }

                s_eventLoopStopRequested = true;

                var err = FdbNative.StopNetwork();
                if (err != FdbError.Success)
                {
                    if (Logging.On)
                    {
                        Logging.Warning(typeof(Fdb), "StopEventLoop", $"Failed to stop event loop: {err.ToString()}");
                    }
                }
                s_eventLoopStarted = false;

                var thread = s_eventLoop;
                if (thread != null && thread.IsAlive)
                {
                    // BUGBUG: specs says that we need to wait for the network thread to stop gracefully, or else data integrity may not be guaranteed...
                    // We should wait for a bit, and only attempt to Abort() the thread after a timeout (30sec ? more ?)

                    // keep track of how much time it took to stop...
                    var duration = Stopwatch.StartNew();

                    try
                    {
                        //TODO: replace with a ManualResetEvent that would get signaled at the end of the event loop ?
                        while (thread.IsAlive && duration.Elapsed.TotalSeconds < 5)
                        {
                            // wait a bit...
                            Thread.Sleep(250);
                        }

                        if (thread.IsAlive)
                        {
                            if (Logging.On)
                            {
                                Logging.Warning(typeof(Fdb), "StopEventLoop", $"The fdb network thread has not stopped after {duration.Elapsed.TotalSeconds:N0} seconds. Forcing shutdown...");
                            }

                            // Force a shutdown
                            thread.Abort();

                            bool stopped = thread.Join(TimeSpan.FromSeconds(30));
                            //REVIEW: is this even useful? If the thread is stuck in a native P/Invoke call, it won't get notified until it returns to managed code ...
                            // => in that case, we have a zombie thread on our hands...

                            if (!stopped)
                            {
                                if (Logging.On)
                                {
                                    Logging.Warning(typeof(Fdb), "StopEventLoop", $"The fdb network thread failed to stop after more than {duration.Elapsed.TotalSeconds:N0} seconds. Transaction integrity may not be guaranteed.");
                                }
                            }
                        }
                    }
                    catch (ThreadAbortException)
                    {
                        // Should not happen, unless we are called from a thread that is itself being stopped ?
                    }
                    finally
                    {
                        s_eventLoop = null;
                        duration.Stop();
                        if (duration.Elapsed.TotalSeconds >= 20)
                        {
                            if (Logging.On)
                            {
                                Logging.Warning(typeof(Fdb), "StopEventLoop", $"The fdb network thread took a long time to stop ({duration.Elapsed.TotalSeconds:N0} seconds).");
                            }
                        }
                    }
                }
            }
        }
Пример #5
0
 /// <summary>Returns the maximum API version currently supported by the installed client.</summary>
 /// <remarks>The version of the installed client (fdb_c.dll) can be different higher (or lower) than the version supported by this binding (FoundationDB.Client.dll)!
 /// If you want the highest possible version that is supported by both the binding and the client, you must call <see cref="GetMaxSafeApiVersion()"/>.
 /// Attempts to select an API version higher than this value will fail.
 /// </remarks>
 public static int GetMaxApiVersion()
 {
     return(FdbNative.GetMaxApiVersion());
 }