/// <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("<"); }
/// <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("<"); }
/// <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; } }
/// <summary>Starts Tor process if it is not running already.</summary> /// <exception cref="OperationCanceledException"/> public async Task <bool> StartAsync(CancellationToken token = default) { ThrowIfDisposed(); ProcessAsync? process = null; TorControlClient?controlClient = null; try { // Is Tor already running? Either our Tor process from previous Wasabi Wallet run or possibly user's own Tor. bool isAlreadyRunning = await TcpConnectionFactory.IsTorRunningAsync().ConfigureAwait(false); if (isAlreadyRunning) { Logger.LogInfo($"Tor is already running on {Settings.SocksEndpoint.Address}:{Settings.SocksEndpoint.Port}."); TorControlClient = await InitTorControlAsync(token).ConfigureAwait(false); return(true); } string arguments = Settings.GetCmdArguments(); process = StartProcess(arguments); bool isRunning = await EnsureRunningAsync(process, token).ConfigureAwait(false); if (!isRunning) { return(false); } controlClient = await InitTorControlAsync(token).ConfigureAwait(false); Logger.LogInfo("Tor is running."); // Only now we know that Tor process is fully started. TorProcess = process; TorControlClient = controlClient; controlClient = null; process = null; return(true); } catch (OperationCanceledException ex) { Logger.LogDebug("User canceled operation.", ex); throw; } catch (Exception ex) { Logger.LogError("Could not automatically start Tor. Try running Tor manually.", ex); } finally { if (controlClient is not null) { await controlClient.DisposeAsync().ConfigureAwait(false); } process?.Dispose(); } return(false); }