Skip to content

Commit

Permalink
Replace Environment.Exit with ProcessExitSource and cancelation token
Browse files Browse the repository at this point in the history
  • Loading branch information
LukaszRozmej committed Apr 27, 2023
1 parent f6dd83d commit df6dbb2
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 48 deletions.
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Api/IBasicApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public interface IBasicApi
IBetterPeerStrategy? BetterPeerStrategy { get; set; }
ITimestamper Timestamper { get; }
ITimerFactory TimerFactory { get; }
IProcessExitSource ProcessExit { get; set; }

public IConsensusPlugin? GetConsensusPlugin() =>
Plugins
Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Api/NethermindApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,5 +229,6 @@ public ISealEngine SealEngine
public IList<IPublisher> Publishers { get; } = new List<IPublisher>(); // this should be called publishers
public CompositePruningTrigger PruningTrigger { get; } = new();
public ISnapProvider SnapProvider { get; set; }
public IProcessExitSource ProcessExit { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using FluentAssertions;
using Nethermind.Blockchain.FullPruning;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
Expand All @@ -31,8 +32,8 @@ namespace Nethermind.Blockchain.Test.FullPruning
[Parallelizable(ParallelScope.All)]
public class FullPrunerTests
{
private int _fullPrunerMemoryBudgetMb;
private int _degreeOfParallelism;
private readonly int _fullPrunerMemoryBudgetMb;
private readonly int _degreeOfParallelism;

public FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism)
{
Expand Down Expand Up @@ -84,6 +85,28 @@ public async Task pruning_keeps_new_db_on_success()
test.CopyDb.Count.Should().Be(count);
}

[Timeout(Timeout.MaxTestTime)]
[TestCase(true, FullPruningCompletionBehavior.None, false)]
[TestCase(true, FullPruningCompletionBehavior.ShutdownOnSuccess, true)]
[TestCase(true, FullPruningCompletionBehavior.AlwaysShutdown, true)]
[TestCase(false, FullPruningCompletionBehavior.None, false)]
[TestCase(false, FullPruningCompletionBehavior.ShutdownOnSuccess, false)]
[TestCase(false, FullPruningCompletionBehavior.AlwaysShutdown, true)]
public async Task pruning_shuts_down_node(bool success, FullPruningCompletionBehavior behavior, bool expectedShutdown)
{
TestContext test = CreateTest(successfulPruning: success, completionBehavior: behavior);
await test.WaitForPruning();

if (expectedShutdown)
{
test.ProcessExitSource.Received(1).Exit(ExitCodes.Ok);
}
else
{
test.ProcessExitSource.DidNotReceiveWithAnyArgs().Exit(ExitCodes.Ok);
}
}

[Test, Timeout(Timeout.MaxTestTime)]
public async Task can_not_start_pruning_when_other_is_in_progress()
{
Expand Down Expand Up @@ -136,7 +159,8 @@ public async Task should_duplicate_writes_to_batches_while_pruning()
test.FullPruningDb[key].Should().BeEquivalentTo(key);
}

private TestContext CreateTest(bool successfulPruning = true, bool clearPrunedDb = false) => new(successfulPruning, clearPrunedDb, _fullPrunerMemoryBudgetMb, _degreeOfParallelism);
private TestContext CreateTest(bool successfulPruning = true, bool clearPrunedDb = false, FullPruningCompletionBehavior completionBehavior = FullPruningCompletionBehavior.None) =>
new(successfulPruning, clearPrunedDb, completionBehavior, _fullPrunerMemoryBudgetMb, _degreeOfParallelism);

private class TestContext
{
Expand All @@ -151,7 +175,14 @@ private class TestContext
public MemDb TrieDb { get; }
public MemDb CopyDb { get; }

public TestContext(bool successfulPruning, bool clearPrunedDb = false, int fullScanMemoryBudgetMb = 0, int degreeOfParallelism = 0)
public IProcessExitSource ProcessExitSource { get; } = Substitute.For<IProcessExitSource>();

public TestContext(
bool successfulPruning,
bool clearPrunedDb = false,
FullPruningCompletionBehavior completionBehavior = FullPruningCompletionBehavior.None,
int fullScanMemoryBudgetMb = 0,
int degreeOfParallelism = 0)
{
BlockTree.OnUpdateMainChain += (_, e) => _head = e.Blocks[^1].Number;
_clearPrunedDb = clearPrunedDb;
Expand All @@ -169,7 +200,8 @@ public TestContext(bool successfulPruning, bool clearPrunedDb = false, int fullS
{
FullPruningMaxDegreeOfParallelism = degreeOfParallelism,
FullPruningMemoryBudgetMb = fullScanMemoryBudgetMb,
}, BlockTree, StateReader, LimboLogs.Instance);
FullPruningCompletionBehavior = completionBehavior
}, BlockTree, StateReader, ProcessExitSource, LimboLogs.Instance);
}

public async Task<bool> WaitForPruning()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading.Tasks;
using FluentAssertions;
using Nethermind.Blockchain.FullPruning;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
Expand Down Expand Up @@ -37,17 +38,18 @@ public class PruningTestBlockchain : TestBlockchain
public IPruningTrigger PruningTrigger { get; } = Substitute.For<IPruningTrigger>();
public FullTestPruner FullPruner { get; private set; }
public IPruningConfig PruningConfig { get; set; } = new PruningConfig();
public IProcessExitSource ProcessExitSource { get; } = Substitute.For<IProcessExitSource>();

public PruningTestBlockchain()
{
TempDirectory = TempPath.GetTempDirectory();
}

protected override async Task<TestBlockchain> Build(ISpecProvider specProvider = null, UInt256? initialValues = null)
protected override async Task<TestBlockchain> Build(ISpecProvider? specProvider = null, UInt256? initialValues = null)
{
TestBlockchain chain = await base.Build(specProvider, initialValues);
PruningDb = (IFullPruningDb)DbProvider.StateDb;
FullPruner = new FullTestPruner(PruningDb, PruningTrigger, PruningConfig, BlockTree, StateReader, LogManager);
FullPruner = new FullTestPruner(PruningDb, PruningTrigger, PruningConfig, BlockTree, StateReader, ProcessExitSource, LogManager);
return chain;
}

Expand Down Expand Up @@ -85,8 +87,9 @@ public FullTestPruner(
IPruningConfig pruningConfig,
IBlockTree blockTree,
IStateReader stateReader,
IProcessExitSource processExitSource,
ILogManager logManager)
: base(pruningDb, pruningTrigger, pruningConfig, blockTree, stateReader, logManager)
: base(pruningDb, pruningTrigger, pruningConfig, blockTree, stateReader, processExitSource, logManager)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Nethermind.Config;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
Expand All @@ -25,6 +26,7 @@ public class FullPruner : IDisposable
private readonly IPruningConfig _pruningConfig;
private readonly IBlockTree _blockTree;
private readonly IStateReader _stateReader;
private readonly IProcessExitSource _processExitSource;
private readonly ILogManager _logManager;
private IPruningContext? _currentPruning;
private int _waitingForBlockProcessed = 0;
Expand All @@ -41,13 +43,15 @@ public FullPruner(
IPruningConfig pruningConfig,
IBlockTree blockTree,
IStateReader stateReader,
IProcessExitSource processExitSource,
ILogManager logManager)
{
_fullPruningDb = fullPruningDb;
_pruningTrigger = pruningTrigger;
_pruningConfig = pruningConfig;
_blockTree = blockTree;
_stateReader = stateReader;
_processExitSource = processExitSource;
_logManager = logManager;
_pruningTrigger.Prune += OnPrune;
_logger = _logManager.GetClassLogger();
Expand Down Expand Up @@ -161,7 +165,7 @@ private void HandlePruningFinished(object? sender, PruningEventArgs e)
case FullPruningCompletionBehavior.AlwaysShutdown:
case FullPruningCompletionBehavior.ShutdownOnSuccess when e.Success:
if (_logger.IsInfo) _logger.Info($"Full Pruning completed {(e.Success ? "successfully" : "unsuccessfully")}, shutting down as requested in the configuration.");
Task.Run(() => Environment.Exit(0));
_processExitSource.Exit(ExitCodes.Ok);
break;
}
}
Expand Down
7 changes: 0 additions & 7 deletions src/Nethermind/Nethermind.Config/EnvConfigSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ public interface IEnvironment
{
string GetEnvironmentVariable(string variableName);
System.Collections.IDictionary GetEnvironmentVariables();

void Exit(int exitCode);
}

public class EnvironmentWrapper : IEnvironment
Expand All @@ -80,10 +78,5 @@ public System.Collections.IDictionary GetEnvironmentVariables()
{
return Environment.GetEnvironmentVariables();
}

public void Exit(int exitCode)
{
Environment.Exit(exitCode);
}
}
}
28 changes: 28 additions & 0 deletions src/Nethermind/Nethermind.Config/IProcessExitSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Threading;
using Nethermind.Core.Extensions;

namespace Nethermind.Config;

public interface IProcessExitSource
{
public void Exit(int exitCode);
}

public class ProcessExitSource : IProcessExitSource
{
private CancellationTokenSource _cancellationTokenSource = new();
public int ExitCode { get; set; } = ExitCodes.Ok;

public CancellationToken Token => _cancellationTokenSource!.Token;

public void Exit(int exitCode)
{
if (CancellationTokenExtensions.CancelDisposeAndClear(ref _cancellationTokenSource))
{
ExitCode = exitCode;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static Task AsTask(this CancellationToken token)
/// <remarks>
/// This method is thread-sage and uses <see cref="Interlocked.CompareExchange{T}(ref T,T,T)"/> to safely manage reference.
/// </remarks>
public static void CancelDisposeAndClear(ref CancellationTokenSource? cancellationTokenSource)
public static bool CancelDisposeAndClear(ref CancellationTokenSource? cancellationTokenSource)
{
CancellationTokenSource? source = cancellationTokenSource;
if (source is not null)
Expand All @@ -38,8 +38,11 @@ public static void CancelDisposeAndClear(ref CancellationTokenSource? cancellati
{
source.Cancel();
source.Dispose();
return true;
}
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using NUnit.Framework;
using Nethermind.Core.Extensions;
using System.Threading.Tasks;
using Nethermind.Config;
using NSubstitute.ReceivedExtensions;

namespace Nethermind.HealthChecks.Test
Expand All @@ -27,7 +28,12 @@ public void free_disk_check_ensure_free_on_startup_no_wait(float availableDiskSp
LowStorageSpaceShutdownThreshold = 1,
LowStorageSpaceWarningThreshold = 5
};
FreeDiskSpaceChecker freeDiskSpaceChecker = new(hcConfig, LimboTraceLogger.Instance, GetDriveInfos(availableDiskSpacePercent), Core.Timers.TimerFactory.Default);
FreeDiskSpaceChecker freeDiskSpaceChecker = new(
hcConfig,
GetDriveInfos(availableDiskSpacePercent),
Core.Timers.TimerFactory.Default,
Substitute.For<IProcessExitSource>(),
LimboTraceLogger.Instance);

if (exceptionExpected)
Assert.Throws<NotEnoughDiskSpaceException>(() => freeDiskSpaceChecker.EnsureEnoughFreeSpaceOnStart(Core.Timers.TimerFactory.Default));
Expand All @@ -47,20 +53,23 @@ public void free_disk_check_ensure_free_on_startup_wait_until_enough(float avail
LowStorageSpaceShutdownThreshold = 1,
LowStorageSpaceWarningThreshold = 5
};
var drives = GetDriveInfos(availableDiskSpacePercent);
IDriveInfo[] drives = GetDriveInfos(availableDiskSpacePercent);
drives[0].AvailableFreeSpace.Returns(a => _freeSpaceBytes,
a => 3 * _freeSpaceBytes);
FreeDiskSpaceChecker freeDiskSpaceChecker = new(hcConfig, LimboTraceLogger.Instance, drives, Core.Timers.TimerFactory.Default, ts.TotalMinutes);
FreeDiskSpaceChecker freeDiskSpaceChecker = new(
hcConfig,
drives,
Core.Timers.TimerFactory.Default,
Substitute.For<IProcessExitSource>(),
LimboTraceLogger.Instance,
ts.TotalMinutes);

Task t = Task.Run(() => freeDiskSpaceChecker.EnsureEnoughFreeSpaceOnStart(Core.Timers.TimerFactory.Default));
bool completed = t.Wait((int)ts.TotalMilliseconds * 2);

Assert.IsTrue(completed);

if (awaitsForFreeSpace)
_ = drives[0].Received(3).AvailableFreeSpace;
else
_ = drives[0].Received(1).AvailableFreeSpace;
_ = drives[0].Received(awaitsForFreeSpace ? 3 : 1).AvailableFreeSpace;
}

private static IDriveInfo[] GetDriveInfos(float availableDiskSpacePercent)
Expand Down
16 changes: 9 additions & 7 deletions src/Nethermind/Nethermind.HealthChecks/FreeDiskSpaceChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ public class FreeDiskSpaceChecker : IHostedService, IAsyncDisposable
private readonly IHealthChecksConfig _healthChecksConfig;
private readonly ILogger _logger;
private readonly IDriveInfo[] _drives;
private readonly IProcessExitSource _processExitSource;
private readonly ITimer _timer;
private readonly double _checkPeriodMinutes;
public static readonly int DefaultCheckPeriodMinutes = 1;

public FreeDiskSpaceChecker(IHealthChecksConfig healthChecksConfig, ILogger logger, IDriveInfo[] drives, ITimerFactory timerFactory) :
this(healthChecksConfig, logger, drives, timerFactory, DefaultCheckPeriodMinutes)
{ }

public FreeDiskSpaceChecker(IHealthChecksConfig healthChecksConfig, ILogger logger, IDriveInfo[] drives, ITimerFactory timerFactory, double checkPeriodMinutes)
public FreeDiskSpaceChecker(IHealthChecksConfig healthChecksConfig,
IDriveInfo[] drives,
ITimerFactory timerFactory,
IProcessExitSource processExitSource,
ILogger logger,
double checkPeriodMinutes = 1)
{
_healthChecksConfig = healthChecksConfig;
_logger = logger;
_drives = drives;
_processExitSource = processExitSource;
_checkPeriodMinutes = checkPeriodMinutes;
_timer = timerFactory.CreateTimer(TimeSpan.FromMinutes(_checkPeriodMinutes));
_timer.Elapsed += CheckDiskSpace;
Expand All @@ -45,7 +47,7 @@ private void CheckDiskSpace(object sender, EventArgs e)
if (freeSpacePercent < _healthChecksConfig.LowStorageSpaceShutdownThreshold)
{
if (_logger.IsError) _logger.Error($"Free disk space in '{drive.RootDirectory.FullName}' is below {_healthChecksConfig.LowStorageSpaceShutdownThreshold:0.00}% - shutting down...");
Environment.Exit(ExitCodes.LowDiskSpace);
_processExitSource.Exit(ExitCodes.LowDiskSpace);
}

if (freeSpacePercent < _healthChecksConfig.LowStorageSpaceWarningThreshold)
Expand Down
7 changes: 6 additions & 1 deletion src/Nethermind/Nethermind.HealthChecks/HealthChecksPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ public async ValueTask DisposeAsync()
public bool MustInitialize => true;

public FreeDiskSpaceChecker FreeDiskSpaceChecker => LazyInitializer.EnsureInitialized(ref _freeDiskSpaceChecker,
() => new FreeDiskSpaceChecker(_healthChecksConfig, _logger, _api.FileSystem.GetDriveInfos(_initConfig.BaseDbPath), _api.TimerFactory));
() => new FreeDiskSpaceChecker(
_healthChecksConfig,
_api.FileSystem.GetDriveInfos(_initConfig.BaseDbPath),
_api.TimerFactory,
_api.ProcessExit,
_logger));

public Task Init(INethermindApi api)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ private static void InitializeFullPruning(
api.PruningTrigger.Add(pruningTrigger);
}

FullPruner pruner = new(fullPruningDb, api.PruningTrigger, pruningConfig, api.BlockTree!, stateReader, api.LogManager);
FullPruner pruner = new(fullPruningDb, api.PruningTrigger, pruningConfig, api.BlockTree!, stateReader, api.ProcessExit, api.LogManager);
api.DisposeStack.Push(pruner);
}
}
Expand Down
Loading

0 comments on commit df6dbb2

Please sign in to comment.