/// <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("<");
    }
 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;
 }
Esempio n. 3
0
        public void ForceResetInvalidPidCtor()
        {
            var tmp = Path.GetTempFileName();

            try {
                File.WriteAllText(tmp, "-9999");
                using (var l = new PidFile(tmp, PidFileMode.Forced)) {
                    Assert.True(l.IsAquired);
                }
            }
            finally {
                File.Delete(tmp);
            }
        }
Esempio n. 4
0
        public void WillNoAquireIfExists()
        {
            var tmp = Path.GetTempFileName();

            try {
                File.WriteAllText(tmp, "data");
                using (var l = new PidFile(tmp)) {
                    Assert.False(l.IsAquired); // flag not set
                }
                Assert.True(File.Exists(tmp)); //will not delete
            }
            finally {
                File.Delete(tmp);
            }
        }
Esempio n. 5
0
        public void CanGetCurrentPID()
        {
            var tmp = Path.GetTempFileName();

            try {
                File.WriteAllText(tmp, "1234");
                using (var l = new PidFile(tmp)) {
                    Assert.False(l.IsAquired);
                    Assert.AreEqual(1234, l.GetCurrentLockPid());
                }
            }
            finally {
                File.Delete(tmp);
            }
        }
Esempio n. 6
0
        public void CanLockFileAndDeleteIt()
        {
            var tmp = Path.GetTempFileName();

            File.Delete(tmp);
            Assert.False(File.Exists(tmp));
            using (var l = new PidFile(tmp)) {
                Assert.True(l.IsAquired);      // flag set
                Assert.True(File.Exists(tmp)); //creates locker file
                var pid = Process.GetCurrentProcess().Id;
                using (var fs = new FileStream(tmp, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
                    using (var sr = new StreamReader(fs)) {
                        Assert.AreEqual(pid, Convert.ToInt32(sr.ReadToEnd())); // read shared and PID
                    }
                }
            }
            Assert.False(File.Exists(tmp));
        }
Esempio n. 7
0
        public void NotResetExistedPid()
        {
            var pid = Process.GetProcesses().First().Id;
            var tmp = Path.GetTempFileName();

            try
            {
                File.WriteAllText(tmp, pid.ToString());
                using (var l = new PidFile(tmp, PidFileMode.Forced))
                {
                    Assert.False(l.IsAquired);
                }
            }
            finally
            {
                File.Delete(tmp);
            }
        }
Esempio n. 8
0
        public void CanResetPID()
        {
            var tmp = Path.GetTempFileName();

            try {
                File.WriteAllText(tmp, "1234");
                using (var l = new PidFile(tmp)) {
                    Assert.False(l.IsAquired);
                    l.Reset();
                    Assert.False(l.IsAquired);
                    File.Delete(tmp);
                    l.Reset();
                    Assert.True(l.IsAquired);
                }
            }
            finally {
                File.Delete(tmp);
            }
        }
    /// <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;
        }
    }