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; } } }
/// <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 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; }
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; }
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; }
/// <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("<"); }
/// <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); } } }
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. } }
/// <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; } }