Exemplo n.º 1
0
        public void CancelStressTest()
        {
            foreach (bool sync in new[] { false, true })
            {
                const int N       = 64;
                long      counter = 0;
                var       mutex   = new AsyncMutex();
                Task[]    tasks   = Enumerable.Range(0, N).Select(_ => Inc()).ToArray();
                Task.WaitAll(tasks);
                Assert.AreEqual(N, counter);

                async Task Inc()
                {
                    while (true)
                    {
                        using (var cancel = new CancellationTokenSource()) {
                            Task t = mutex.LockAsync(cancel.Token);
                            await Task.Yield();

                            cancel.Cancel();
                            if (!t.IsCanceled)
                            {
                                await t;
                                break;
                            }
                        }
                    }
                    long next = counter + 1;
                    await Task.Yield();

                    counter = next;
                    mutex.Unlock(runNextSynchronously: sync);
                }
            }
        }
Exemplo n.º 2
0
        public void Lock()
        {
            var mutex = new AsyncMutex();

            mutex.Lock();
            mutex.Unlock();
        }
Exemplo n.º 3
0
        public async Task LockAsync()
        {
            var mutex = new AsyncMutex();
            await mutex.LockAsync();

            mutex.Unlock();
        }
Exemplo n.º 4
0
        public async Task Test()
        {
            var m = new AsyncMutex("TheName");
            var d = await m.WaitOneAsync();

            await Task.Delay(TimeSpan.FromSeconds(2));

            d.Dispose();
        }
Exemplo n.º 5
0
        public void LockAndUnlock()
        {
            var mutex = new AsyncMutex();

            using (mutex.LockAndUnlock())
            {
                Assert.False(mutex.Lock(0));
            }
        }
Exemplo n.º 6
0
        public async Task LockAndUnlockAsync()
        {
            var mutex = new AsyncMutex();

            using (await mutex.LockAndUnlockAsync())
            {
                Assert.False(await mutex.LockAsync(0));
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Sends command using the hwi process.
        /// </summary>
        /// <param name="cancellationToken">Cancel the current operation.</param>
        /// <param name="command">Command to send in string format.</param>
        /// <param name="isMutexPriority">You can add priority to your command among hwi calls. For example if multiply wasabi instances are running both use the same hwi process with isMutexPriority you will more likely to get it.</param>
        /// <returns>JToken the answer recevied from hwi process.</returns>
        /// <exception cref="TimeoutException">Thrown when the process cannot be finished in time.</exception>
        /// <exception cref="IOException">Thrown when Mutex on hwi process could not be acquired.</exception>
        public static async Task <JToken> SendCommandAsync(string command, CancellationToken cancellationToken, bool isMutexPriority = false)
        {
            try
            {
                var rand  = Random.Next(1, 100);
                var delay = isMutexPriority ? (100 + rand) : (1000 + rand);
                using (await AsyncMutex.LockAsync(cancellationToken, delay))                 // It could be even improved more if this Mutex would also look at which hardware wallet the operation is going towards (enumerate sends to all.)
                {
                    if (!File.Exists(HwiPath))
                    {
                        var exeName = Path.GetFileName(HwiPath);
                        throw new FileNotFoundException($"{exeName} not found at `{HwiPath}`. Maybe it was removed by an antivirus software!");
                    }

                    using (var process = Process.Start(
                               new ProcessStartInfo
                    {
                        FileName = HwiPath,
                        Arguments = command,
                        RedirectStandardOutput = true,
                        UseShellExecute = false,
                        CreateNoWindow = true,
                        WindowStyle = ProcessWindowStyle.Hidden
                    }
                               ))
                    {
                        await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);                         // TODO: https://github.com/zkSNACKs/WalletWasabi/issues/1452;

                        if (process.ExitCode != 0)
                        {
                            throw new IOException($"Command: {command} exited with exit code: {process.ExitCode}, instead of 0.");
                        }

                        string response = await process.StandardOutput.ReadToEndAsync();

                        var jToken = JToken.Parse(response);
                        if (jToken is JObject json)
                        {
                            if (json.TryGetValue("error", out JToken err))
                            {
                                var errString = err.ToString();
                                throw new IOException(errString);
                            }
                        }

                        return(jToken);
                    }
                }
            }
            catch (Exception ex) when(ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException)
            {
                throw new TimeoutException("Timeout occurred during the hwi operation.", ex);
            }
        }
Exemplo n.º 8
0
        public async Task Basic()
        {
            // Create a mutex and then several tasks that acquire the mutex for
            // a period of time, verifying that each obtains exclusive
            // access.

            var taskCount = 12;
            var refCount  = 0;
            var error     = false;
            var tasks     = new List <Task>();
            var stopwatch = new Stopwatch();
            var testTime  = defaultTimeout - TimeSpan.FromSeconds(2);

            stopwatch.Start();

            using (var mutex = new AsyncMutex())
            {
                for (int i = 0; i < taskCount; i++)
                {
                    tasks.Add(Task.Run(
                                  async() =>
                    {
                        while (stopwatch.Elapsed < testTime)
                        {
                            using (await mutex.AcquireAsync())
                            {
                                if (refCount > 0)
                                {
                                    // This means that we don't have exclusive access indicating
                                    // that the mutex must be broken.

                                    error = true;
                                }

                                try
                                {
                                    Interlocked.Increment(ref refCount);

                                    await Task.Delay(TimeSpan.FromMilliseconds(250));
                                }
                                finally
                                {
                                    Interlocked.Decrement(ref refCount);
                                }
                            }
                        }
                    }));

                    await NeonHelper.WaitAllAsync(tasks, defaultTimeout);
                }

                Assert.False(error);
            }
        }
Exemplo n.º 9
0
        public void Lock_Timeout()
        {
            var mutex = new AsyncMutex();

            mutex.Lock();

            var start        = Environment.TickCount;
            var capturedLock = mutex.Lock(100);

            Assert.False(capturedLock);
            Assert.True(Environment.TickCount - start >= 90);
        }
Exemplo n.º 10
0
        public async Task LockAsync_Timeout()
        {
            var mutex = new AsyncMutex();

            mutex.Lock();

            var start        = Environment.TickCount;
            var capturedLock = await mutex.LockAsync(100);

            Assert.False(capturedLock);
            Assert.True(Environment.TickCount - start >= 90);
        }
Exemplo n.º 11
0
        public void GetLockAsync_NoLock_ReturnsCompletedTask()
        {
            // Arrange

            var mutex = new AsyncMutex();

            // Act

            var task = mutex.GetLockAsync();

            // Assert

            Assert.True(task.IsCompleted);
        }
Exemplo n.º 12
0
        private static async Task TestMutexConcurrencyAsync(AsyncMutex asyncMutex, AsyncMutex asyncMutex2)
        {
            // Concurrency test with the same AsyncMutex object.
            using var phase1 = new AutoResetEvent(false);
            using var phase2 = new AutoResetEvent(false);
            using var phase3 = new AutoResetEvent(false);
            // Acquire the Mutex with a background thread.
            var myTask = Task.Run(async() =>
            {
                using (await asyncMutex.LockAsync())
                {
                    // Phase 1: signal that the mutex has been acquired.
                    phase1.Set();

                    // Phase 2: wait for exclusion.
                    Assert.True(phase2.WaitOne(TimeSpan.FromSeconds(20)));
                }
                // Phase 3: release the mutex.
                phase3.Set();
            });

            // Phase 1: wait for the first Task to acquire the mutex.
            Assert.True(phase1.WaitOne(TimeSpan.FromSeconds(20)));

            // Phase 2: check mutual exclusion.
            using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
            {
                await Assert.ThrowsAsync <IOException>(async() =>
                {
                    using (await asyncMutex2.LockAsync(cancellationToken: cts.Token))
                    {
                        throw new InvalidOperationException("Mutex should not be acquired here.");
                    };
                });
            }
            phase2.Set();

            // Phase 3: wait for release and acquire the mutex
            Assert.True(phase3.WaitOne(TimeSpan.FromSeconds(20)));

            using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
            {
                // We should get this immediately.
                using (await asyncMutex2.LockAsync())
                {
                };
            }

            Assert.True(myTask.IsCompletedSuccessfully);
        }
Exemplo n.º 13
0
        public async Task GetLockAsync_WithCancellationToken_Cancels()
        {
            // Arrange

            var mutex = new AsyncMutex();
            await mutex.GetLockAsync();

            using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));

            // Act/Assert

            var ex = await Assert.ThrowsAsync <TaskCanceledException>(() => mutex.GetLockAsync(cts.Token).AsTask());

            Assert.Equal(cts.Token, ex.CancellationToken);
        }
        public MutexIoManager(string filePath) : base(filePath)
        {
            var shortHash = HashHelpers.GenerateSha256Hash(FilePath).Substring(0, 7);

            // https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=netframework-4.8
            // On a server that is running Terminal Services, a named system mutex can have two levels of visibility.
            // If its name begins with the prefix "Global\", the mutex is visible in all terminal server sessions.
            // If its name begins with the prefix "Local\", the mutex is visible only in the terminal server session where it was created.
            // In that case, a separate mutex with the same name can exist in each of the other terminal server sessions on the server.
            // If you do not specify a prefix when you create a named mutex, it takes the prefix "Local\".
            // Within a terminal server session, two mutexes whose names differ only by their prefixes are separate mutexes,
            // and both are visible to all processes in the terminal server session.
            // That is, the prefix names "Global\" and "Local\" describe the scope of the mutex name relative to terminal server sessions, not relative to processes.
            Mutex = new AsyncMutex($"{FileNameWithoutExtension}-{shortHash}");
        }
Exemplo n.º 15
0
        public void Lock_CancellationToken()
        {
            var mutex = new AsyncMutex();

            mutex.Lock();

            var start = Environment.TickCount;

            using (var cancellationTokenSource = new CancellationTokenSource(100))
            {
                Assert.Throws <OperationCanceledException>(()
                                                           => mutex.Lock(cancellationTokenSource.Token));
                Assert.True(Environment.TickCount - start >= 90);
            }
        }
Exemplo n.º 16
0
        public async Task Dispose()
        {
            // Create a mutex, acquire it, and then create another task that will
            // attempt to acquire it as well (and will fail because the mutex has
            // already been acquired).  Then dispose the mutex and verify that the
            // waiting task saw the [ObjectDisposedException].

            var mutex    = new AsyncMutex();
            var inTask   = false;
            var acquired = false;
            var disposed = false;

            await mutex.AcquireAsync();

            var task = Task.Run(
                async() =>
            {
                try
                {
                    var acquireTask = mutex.AcquireAsync();

                    inTask = true;

                    await acquireTask;

                    acquired = true;
                }
                catch (ObjectDisposedException)
                {
                    disposed = true;
                }
            });

            // Wait for the task to have called [AcquireAsync()].

            NeonHelper.WaitFor(() => inTask, defaultTimeout);

            // Dispose the mutex, wait for the task to exit and then verify
            // that it caught the [ObjectDisposedException].

            mutex.Dispose();
            task.Wait(defaultTimeout);

            Assert.False(acquired);
            Assert.True(disposed);
        }
Exemplo n.º 17
0
 public void FairnessTest()
 {
     foreach (bool sync in new[] { false, true })
     {
         var mutex = new AsyncMutex();
         var tasks = new Queue <Task>(Enumerable.Range(0, 1024).Select(_ => mutex.LockAsync()));
         while (tasks.Count > 0)
         {
             tasks.Enqueue(mutex.LockAsync());
             for (int i = 0; i != 2; ++i)
             {
                 tasks.Dequeue().Wait();
                 mutex.Unlock(sync);
             }
         }
     }
 }
Exemplo n.º 18
0
        public void GetLockAsync_TwoLocksWithReleases_ReturnsCompletedTasks()
        {
            // Arrange

            var mutex = new AsyncMutex();

            // Act/Assert

            var task = mutex.GetLockAsync();

            Assert.True(task.IsCompleted);
            mutex.ReleaseLock();

            var task2 = mutex.GetLockAsync();

            Assert.True(task2.IsCompleted);
        }
        public async Task <IDisposable> LockAsync(CancellationToken token)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(nameof(GlobalSettings));
            }
            token.ThrowIfCancellationRequested();
            if (_mutex?.IsLocked ?? false)
            {
                throw new InvalidOperationException("Lock is already taken.");
            }
            // We must use Mutex because we want to lock across different processes that might want to modify this settings file
            var escapedFilename = _globalSettingsFile.Replace("\\", "_").Replace("/", "_");
            var mutex           = await AsyncMutex.WaitAsync($"Global\\812CA7F3-7CD8-44B4-B3F0-0159355C0BD5{escapedFilename}", token).ConfigureAwait(false);

            _mutex = mutex;
            return(mutex);
        }
Exemplo n.º 20
0
        public void LockCancelBenchmark()
        {
            Console.WriteLine("Warmup:");
            Benchmark(32).Wait();

            Console.WriteLine("\nThe real thing:");
            foreach (int threads in new[] { 1, 2, 4, 32, 1024 })
            {
                Benchmark(threads).Wait();
            }

            async Task Benchmark(int threads)
            {
                long counter = 0;
                var  mutex   = new AsyncMutex();
                await mutex.LockAsync();

                TimeSpan  duration  = TimeSpan.FromSeconds(1);
                Stopwatch stopwatch = Stopwatch.StartNew();
                await Task.WhenAll(Enumerable.Range(0, threads).Select(_ => Run()));

                double ns = 1e9 * stopwatch.Elapsed.TotalSeconds / counter;

                Console.WriteLine("  Benchmark(threads: {0,5:N0}): {1,7:N1} ns/call", threads, ns);

                async Task Run()
                {
                    await Task.Yield();

                    while (stopwatch.Elapsed < duration)
                    {
                        for (int i = 0; i != 64; ++i)
                        {
                            using (var c = new CancellationTokenSource()) {
                                Task t = mutex.LockAsync(c.Token);
                                c.Cancel();
                                Debug.Assert(t.IsCanceled);
                            }
                            ++counter;
                        }
                    }
                }
            }
        }
Exemplo n.º 21
0
        public async Task GetLockAsync_TwoLocks_TriggersSecondWhenReleased()
        {
            // Arrange

            var mutex = new AsyncMutex();

            // Act/Assert

            var task = mutex.GetLockAsync();

            Assert.True(task.IsCompleted);
            var task2 = mutex.GetLockAsync();

            Assert.False(task2.IsCompleted);

            mutex.ReleaseLock();

            await task2;
        }
        public async Task CheckAsync()
        {
            if (_disposedValue)
            {
                throw new ObjectDisposedException(nameof(SingleInstanceChecker));
            }

            // The disposal of this mutex handled by AsyncMutex.WaitForAllMutexToCloseAsync().
            var mutex = new AsyncMutex(_lockName);

            try
            {
                using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));
                SingleApplicationLockHolder       = await mutex.LockAsync(cancellationToken : cts.Token).ConfigureAwait(false);
            }
            catch (IOException ex)
            {
                throw new InvalidOperationException($"Wasabi is already running on {Network}!", ex);
            }
        }
Exemplo n.º 23
0
        public void CancelTest()
        {
            var mutex = new AsyncMutex();

            using (var cancel = new CancellationTokenSource()) {
                mutex.LockAsync().Wait();
                Task t = mutex.LockAsync(cancel.Token);
                Assert.IsFalse(t.IsCompleted);
                mutex.Unlock(runNextSynchronously: true);
                t.Wait();

                t = mutex.LockAsync(cancel.Token);
                cancel.Cancel();
                Assert.AreEqual(TaskStatus.Canceled, t.Status);

                mutex.Unlock(runNextSynchronously: true);
                t = mutex.LockAsync(cancel.Token);
                Assert.AreEqual(TaskStatus.RanToCompletion, t.Status);
            }
        }
Exemplo n.º 24
0
        public async Task MutualExclusion()
        {
            int sharedValue  = 0;
            var mutex        = new AsyncMutex();
            var hundredEvent = new AsyncManualResetEvent();

            // Create 10 threads that increment a value 10 times each
            for (int n = 0; n < 10; n++)
            {
                new Thread(async() =>
                {
                    await mutex.Lock();

                    int privateValue = sharedValue;
                    for (int m = 0; m < 10; m++)
                    {
                        // If the mutex works, no other thread will increment sharedValue
                        sharedValue++;
                        privateValue++;
                        Assert.Equal(privateValue, sharedValue);

                        if (sharedValue == 100)
                        {
                            // The test case is complete
                            hundredEvent.Set();
                        }

                        // Yield the CPU to give other threads a chance to run
                        await Task.Delay(10);
                    }

                    mutex.Unlock();
                }).Start();
            }

            await hundredEvent.WaitAsync();

            Assert.Equal(100, sharedValue);
        }
Exemplo n.º 25
0
        public void ExampleTest()
        {
            var expected = new[] {
                "Worker #0: 0",
                "Worker #1: 0",
                "Worker #1: 1",
                "Worker #1: 2",
                "Worker #0: 1",
                "Worker #0: 2",
            };

            CollectionAssert.AreEqual(expected, Example().Result);

            async Task <List <string> > Example()
            {
                var res   = new List <string>();
                var mutex = new AsyncMutex();
                await mutex.LockAsync();

                Task[] tasks = Enumerable.Range(0, 2).Select(Work).ToArray();
                mutex.Unlock(runNextSynchronously: true);
                await Task.WhenAll(tasks);

                return(res);

                async Task Work(int worker)
                {
                    for (int i = 0; i != 3; ++i)
                    {
                        await mutex.LockAsync();

                        res.Add($"Worker #{worker}: {i}");
                        mutex.Unlock(runNextSynchronously: true);
                    }
                }
            }
        }
Exemplo n.º 26
0
        /// <param name="digestRandomIndex">Use the random index of the line to create digest faster. -1 is special value, it means the last character. If null then hash whole file.</param>
        public IoManager(string filePath, int?digestRandomIndex = null)
        {
            DigestRandomIndex = digestRandomIndex;
            OriginalFilePath  = Guard.NotNullOrEmptyOrWhitespace(nameof(filePath), filePath, trim: true);
            OldFilePath       = $"{OriginalFilePath}{OldExtension}";
            NewFilePath       = $"{OriginalFilePath}{NewExtension}";
            DigestFilePath    = $"{OriginalFilePath}{DigestExtension}";

            FileName = Path.GetFileName(OriginalFilePath);
            var shortHash = HashHelpers.GenerateSha256Hash(OriginalFilePath).Substring(0, 7);

            FileNameWithoutExtension = Path.GetFileNameWithoutExtension(OriginalFilePath);

            // https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=netframework-4.8
            // On a server that is running Terminal Services, a named system mutex can have two levels of visibility.
            // If its name begins with the prefix "Global\", the mutex is visible in all terminal server sessions.
            // If its name begins with the prefix "Local\", the mutex is visible only in the terminal server session where it was created.
            // In that case, a separate mutex with the same name can exist in each of the other terminal server sessions on the server.
            // If you do not specify a prefix when you create a named mutex, it takes the prefix "Local\".
            // Within a terminal server session, two mutexes whose names differ only by their prefixes are separate mutexes,
            // and both are visible to all processes in the terminal server session.
            // That is, the prefix names "Global\" and "Local\" describe the scope of the mutex name relative to terminal server sessions, not relative to processes.
            Mutex = new AsyncMutex($"{FileNameWithoutExtension}-{shortHash}");
        }
Exemplo n.º 27
0
        public void LockUnlockStressTest()
        {
            foreach (bool sync in new[] { false, true })
            {
                const int N       = 64 << 10;
                long      counter = 0;
                var       mutex   = new AsyncMutex();
                Task.WhenAll(Enumerable.Range(0, N).Select(_ => Inc())).Wait();
                Assert.AreEqual(N, counter);

                async Task Inc()
                {
                    await mutex.LockAsync();

                    await Task.Yield();

                    long next = counter + 1;
                    await Task.Yield();

                    counter = next;
                    mutex.Unlock(runNextSynchronously: sync);
                }
            }
        }
Exemplo n.º 28
0
 /// <summary>
 /// Creates a new factory with the specified predicate
 /// </summary>
 /// <param name="sharedCanExecute">predicate, defaults to always return true</param>
 public AsyncCommandGroupFactory(Predicate <object> sharedCanExecute = null)
 {
     SharedCanExecute = sharedCanExecute ?? ((obj) => true);
     SharedMutex      = new AsyncMutex();
     Commands         = new List <AsyncCommandBase>();
 }
Exemplo n.º 29
0
        public async Task AsyncMutexTestsAsync()
        {
            var mutexName1 = $"mutex1-{DateTime.Now.Ticks}";             // Randomize the name to avoid system wide collisions.

            AsyncMutex asyncMutex = new AsyncMutex(mutexName1);

            // Cannot be IDisposable because the pattern is like Nito's AsyncLock.
            Assert.False(asyncMutex is IDisposable);

            // Use the mutex two times after each other.
            using (await asyncMutex.LockAsync())
            {
                await Task.Delay(1);
            }

            using (await asyncMutex.LockAsync())
            {
                await Task.Delay(1);
            }

            // Release the Mutex from another thread.

            var disposable = await asyncMutex.LockAsync();

            var myThread = new Thread(new ThreadStart(() => disposable.Dispose()));

            myThread.Start();
            myThread.Join();

            using (await asyncMutex.LockAsync())
            {
                await Task.Delay(1);
            }

            // Standard Mutex test.
            int        cnt     = 0;
            List <int> numbers = new List <int>();
            var        rand    = new Random();

            async Task TestLockAsync()
            {
                using (await asyncMutex.LockAsync())
                {
                    cnt++;

                    await Task.Delay(rand.Next(5));

                    numbers.Add(cnt);
                }
            }

            var tasks = new List <Task>();

            for (int i = 0; i < 100; i++)
            {
                var task = TestLockAsync();

                tasks.Add(task);
            }

            await Task.WhenAll(tasks);

            Assert.Equal(100, numbers.Count);
            for (int i = 1; i < 100; i++)
            {
                var prevnum = numbers[i - 1];
                var num     = numbers[i];
                Assert.Equal(prevnum + 1, num);
            }

            var mutexName2 = $"mutex2-{DateTime.Now.Ticks}";

            // Test that asynclock cancellation is going to throw IOException.
            var mutex = new AsyncMutex(mutexName2);

            using (await mutex.LockAsync())
            {
                using var cts = new CancellationTokenSource(100);
                await Assert.ThrowsAsync <IOException>(async() =>
                {
                    using (await mutex.LockAsync(cancellationToken: cts.Token))
                    {
                    }
                });
            }

            // Test same mutex gets same asynclock.
            var mutex1 = new AsyncMutex(mutexName2);

            using (await mutex1.LockAsync())
            {
                using var cts = new CancellationTokenSource(100);
                var mutex2 = new AsyncMutex(mutexName2);
                await Assert.ThrowsAsync <IOException>(async() =>
                {
                    using (await mutex2.LockAsync(cancellationToken: cts.Token))
                    {
                    }
                });
            }

            // Concurrency test with the same AsyncMutex object.
            await TestMutexConcurrencyAsync(asyncMutex, asyncMutex);

            // Concurrency test with different AsyncMutex object but same name.
            AsyncMutex asyncMutex2 = new AsyncMutex(mutexName1);

            await TestMutexConcurrencyAsync(asyncMutex, asyncMutex2);
        }
Exemplo n.º 30
0
        private static long Dispose = 0;         // To detect redundant calls

        public static async Task DisposeAsync()
        {
            var compareRes = Interlocked.CompareExchange(ref Dispose, 1, 0);

            if (compareRes == 1)
            {
                while (Interlocked.Read(ref Dispose) != 2)
                {
                    await Task.Delay(50);
                }
                return;
            }
            else if (compareRes == 2)
            {
                return;
            }

            try
            {
                await DisposeInWalletDependentServicesAsync();

                if (RpcServer != null)
                {
                    RpcServer.Stop();
                    Logger.LogInfo($"{nameof(RpcServer)} is stopped.", nameof(Global));
                }

                if (UpdateChecker != null)
                {
                    await UpdateChecker?.StopAsync();

                    Logger.LogInfo($"{nameof(UpdateChecker)} is stopped.", nameof(Global));
                }

                if (Synchronizer != null)
                {
                    await Synchronizer?.StopAsync();

                    Logger.LogInfo($"{nameof(Synchronizer)} is stopped.", nameof(Global));
                }

                if (AddressManagerFilePath != null)
                {
                    IoHelpers.EnsureContainingDirectoryExists(AddressManagerFilePath);
                    if (AddressManager != null)
                    {
                        AddressManager?.SavePeerFile(AddressManagerFilePath, Config.Network);
                        Logger.LogInfo($"{nameof(AddressManager)} is saved to `{AddressManagerFilePath}`.", nameof(Global));
                    }
                }

                if (Nodes != null)
                {
                    Nodes?.Disconnect();
                    while (Nodes.ConnectedNodes.Any(x => x.IsConnected))
                    {
                        await Task.Delay(50);
                    }
                    Nodes?.Dispose();
                    Logger.LogInfo($"{nameof(Nodes)} are disposed.", nameof(Global));
                }

                if (RegTestMemPoolServingNode != null)
                {
                    RegTestMemPoolServingNode.Disconnect();
                    Logger.LogInfo($"{nameof(RegTestMemPoolServingNode)} is disposed.", nameof(Global));
                }

                if (TorManager != null)
                {
                    await TorManager?.StopAsync();

                    Logger.LogInfo($"{nameof(TorManager)} is stopped.", nameof(Global));
                }

                if (AsyncMutex.IsAny)
                {
                    try
                    {
                        await AsyncMutex.WaitForAllMutexToCloseAsync();

                        Logger.LogInfo($"{nameof(AsyncMutex)}(es) are stopped.", nameof(Global));
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError($"Error during stopping {nameof(AsyncMutex)}: {ex}", nameof(Global));
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogWarning(ex, nameof(Global));
            }
            finally
            {
                Interlocked.Exchange(ref Dispose, 2);
            }
        }