public async Task StartAsync()
        {
            var ptcv = PrintToConsole ? 1 : 0;

            using var process = Start($"{NetworkTranslator.GetCommandLineArguments(Network)} -datadir={DataDir} -printtoconsole={ptcv}", false);

            await PidFile.SerializeAsync(process.Id).ConfigureAwait(false);

            CachedPid = process.Id;

            while (true)
            {
                var ex = await RpcClient.TestAsync().ConfigureAwait(false);

                if (ex is null)
                {
                    break;
                }

                if (process is null || process.HasExited)
                {
                    throw ex;
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Stops bitcoin daemon process when PID file exists.
        /// </summary>
        /// <remarks>If there is not PID file, no process is stopped.</remarks>
        /// <param name="onlyOwned">Only stop if this node owns the process.</param>
        public async Task StopAsync(bool onlyOwned)
        {
            Logger.LogDebug($"> {nameof(onlyOwned)}={onlyOwned}");

            if (Process is null)
            {
                Logger.LogDebug("< Process is null.");
                return;
            }

            // "process" variable is guaranteed to be non-null at this point.
            ProcessAsync process = Process;

            using var cts = new CancellationTokenSource(_reasonableCoreShutdownTimeout);
            int?pid = await PidFile.TryReadAsync().ConfigureAwait(false);

            // If the cached PID is PID, then we own the process.
            if (pid.HasValue && (!onlyOwned || CachedPid == pid))
            {
                Logger.LogDebug($"User is responsible for the daemon process with PID {pid}. Stop it.");

                try
                {
                    bool isKilled = false;

                    try
                    {
                        // Stop Bitcoin daemon using RPC "stop" command.
                        // The command actually only initiates the bitcoind graceful shutdown procedure.
                        // Our time budget for the bitcoind to stop is given by "ReasonableCoreShutdownTimeout".
                        await RpcClient.StopAsync().ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogWarning(ex);
                        process.Kill();
                        isKilled = true;
                    }

                    if (!isKilled)
                    {
                        Logger.LogDebug($"Wait until the process is stopped.");
                        await process.WaitForExitAsync(cts.Token).ConfigureAwait(false);
                    }
                }
                finally
                {
                    Logger.LogDebug($"Wait until the process is stopped.");
                    process.Dispose();
                    Process = null;
                    PidFile.TryDelete();
                }
            }
            else
            {
                Logger.LogDebug("User is NOT responsible for the daemon process.");
            }

            Logger.LogDebug("<");
        }
Beispiel #3
0
 public BitcoindRpcProcessBridge(IRPCClient rpc, string dataDir, bool printToConsole) : base()
 {
     RpcClient      = Guard.NotNull(nameof(rpc), rpc);
     Network        = RpcClient.Network;
     DataDir        = Guard.NotNullOrEmptyOrWhitespace(nameof(dataDir), dataDir);
     PrintToConsole = printToConsole;
     PidFile        = new PidFile(Path.Combine(DataDir, NetworkTranslator.GetDataDirPrefix(Network)), PidFileName);
     CachedPid      = null;
 }
Beispiel #4
0
 public BitcoindRpcProcessBridge(RPCClient rpc, string dataDir, bool printToConsole) : base()
 {
     RpcClient      = Guard.NotNull(nameof(rpc), rpc);
     Network        = RpcClient.Network;
     DataDir        = Guard.NotNullOrEmptyOrWhitespace(nameof(dataDir), dataDir);
     PrintToConsole = printToConsole;
     PidFile        = new PidFile(DataDir, Network);
     CachedPid      = null;
 }
Beispiel #5
0
 public BitcoindRpcProcessBridge(IRPCClient rpcClient, string dataDir, bool printToConsole)
 {
     RpcClient      = rpcClient;
     Network        = RpcClient.Network;
     DataDir        = dataDir;
     PrintToConsole = printToConsole;
     PidFile        = new PidFile(Path.Combine(DataDir, NetworkTranslator.GetDataDirPrefix(Network)), PidFileName);
     CachedPid      = null;
     Process        = null;
 }
Beispiel #6
0
        /// <summary>
        /// Stops bitcoin daemon process when PID file exists.
        /// </summary>
        /// <remarks>If there is not PID file, no process is stopped.</remarks>
        /// <param name="onlyOwned">Only stop if this node owns the process.</param>
        public async Task StopAsync(bool onlyOwned)
        {
            Logger.LogDebug($"> {nameof(onlyOwned)}={onlyOwned}");

            if (Process is null)
            {
                Logger.LogDebug("< Process is null.");
                return;
            }

            ProcessAsync process = Process;             // process is guaranteed to be non-null at this point.

            using var cts = new CancellationTokenSource(ReasonableCoreShutdownTimeout);
            int?pid = await PidFile.TryReadAsync().ConfigureAwait(false);

            // If the cached PID is PID, then we own the process.
            if (pid.HasValue && (!onlyOwned || CachedPid == pid))
            {
                Logger.LogDebug($"User is responsible for the daemon process with PID {pid}. Stop it.");

                try
                {
                    try
                    {
                        await RpcClient.StopAsync().ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogWarning(ex);
                        process.Kill();
                    }

                    Logger.LogDebug($"Wait until the process is stopped.");
                    await process.WaitForExitAsync(cts.Token).ConfigureAwait(false);
                }
                finally
                {
                    Logger.LogDebug($"Wait until the process is stopped.");
                    process.Dispose();
                    Process = null;
                    PidFile.TryDelete();
                }
            }
            else
            {
                Logger.LogDebug("User is NOT responsible for the daemon process.");
            }

            Logger.LogDebug("<");
        }
Beispiel #7
0
        /// <param name="onlyOwned">Only stop if this node owns the process.</param>
        public async Task StopAsync(bool onlyOwned)
        {
            var rpcRan = false;

            try
            {
                var reasonableCoreShutdownTimeout = TimeSpan.FromSeconds(21);

                using CancellationTokenSource cts = new CancellationTokenSource(reasonableCoreShutdownTimeout);
                int?pid = await PidFile.TryReadAsync().ConfigureAwait(false);

                // If the cached pid is pid, then we own the process.
                if (pid.HasValue && (!onlyOwned || CachedPid == pid))
                {
                    try
                    {
                        using Process process = Process.GetProcessById(pid.Value);
                        try
                        {
                            await RpcClient.StopAsync().ConfigureAwait(false);

                            rpcRan = true;
                        }
                        catch (Exception ex)
                        {
                            process.Kill();
                            Logger.LogDebug(ex);
                        }
                        await process.WaitForExitAsync(cts.Token).ConfigureAwait(false);
                    }
                    finally
                    {
                        PidFile.TryDelete();
                    }
                }
            }
            catch
            {
                if (!onlyOwned && !rpcRan)
                {
                    await RpcClient.StopAsync().ConfigureAwait(false);
                }
            }
        }
Beispiel #8
0
        public async Task StartAsync(CancellationToken cancel)
        {
            var ptcv = PrintToConsole ? 1 : 0;

            using var process = Start($"{NetworkTranslator.GetCommandLineArguments(Network)} -datadir=\"{DataDir}\" -printtoconsole={ptcv}", false);

            await PidFile.WriteFileAsync(process.Id).ConfigureAwait(false);

            CachedPid = process.Id;

            string latestFailureMessage = null;

            while (true)
            {
                var ex = await RpcClient.TestAsync().ConfigureAwait(false);

                if (ex is null)
                {
                    Logger.LogInfo($"RPC connection is successfully established.");
                    break;
                }
                else if (latestFailureMessage != ex.Message)
                {
                    latestFailureMessage = ex.Message;
                    Logger.LogInfo($"{Constants.BuiltinBitcoinNodeName} is not yet ready... Reason: {latestFailureMessage}");
                }

                if (process is null || process.HasExited)
                {
                    throw new BitcoindException($"Failed to start daemon, location: '{process?.StartInfo.FileName} {process?.StartInfo.Arguments}'", ex);
                }

                if (cancel.IsCancellationRequested)
                {
                    await StopAsync(true).ConfigureAwait(false);

                    cancel.ThrowIfCancellationRequested();
                }

                await Task.Delay(100).ConfigureAwait(false);                 // So to leave some breathing room before the next check.
            }
        }
Beispiel #9
0
        /// <summary>
        /// This method can be called only once.
        /// </summary>
        public async Task StartAsync(CancellationToken cancel)
        {
            int    ptcv            = PrintToConsole ? 1 : 0;
            string processPath     = MicroserviceHelpers.GetBinaryPath("bitcoind");
            string networkArgument = NetworkTranslator.GetCommandLineArguments(Network);

            string args = $"{networkArgument} -datadir=\"{DataDir}\" -printtoconsole={ptcv}";

            // Start bitcoind process.
            Process = new ProcessAsync(ProcessStartInfoFactory.Make(processPath, args));
            Process.Start();

            // Store PID in PID file.
            await PidFile.WriteFileAsync(Process.Id).ConfigureAwait(false);

            CachedPid = Process.Id;

            try
            {
                var exceptionTracker = new LastExceptionTracker();

                // Try to connect to bitcoin daemon RPC until we succeed.
                while (true)
                {
                    try
                    {
                        TimeSpan timeSpan = await RpcClient.UptimeAsync(cancel).ConfigureAwait(false);

                        Logger.LogInfo("RPC connection is successfully established.");
                        Logger.LogDebug($"RPC uptime is: {timeSpan}.");

                        // Bitcoin daemon is started. We are done.
                        break;
                    }
                    catch (Exception ex)
                    {
                        ExceptionInfo exceptionInfo = exceptionTracker.Process(ex);

                        // Don't log extensively.
                        if (exceptionInfo.IsFirst)
                        {
                            Logger.LogInfo($"{Constants.BuiltinBitcoinNodeName} is not yet ready... Reason: {exceptionInfo.Exception.Message}");
                        }

                        if (Process is { } p&& p.HasExited)
                        {
                            throw new BitcoindException($"Failed to start daemon, location: '{p.StartInfo.FileName} {p.StartInfo.Arguments}'", ex);
                        }
                    }

                    if (cancel.IsCancellationRequested)
                    {
                        Logger.LogDebug("Bitcoin daemon was not started yet and user requested to cancel the operation.");
                        await StopAsync(onlyOwned : true).ConfigureAwait(false);

                        cancel.ThrowIfCancellationRequested();
                    }

                    // Wait a moment before the next check.
                    await Task.Delay(100, cancel).ConfigureAwait(false);
                }
            }
            catch (Exception)
            {
                Process?.Dispose();
                throw;
            }
        }