From 659844eebaaafdc8be43c58e97b6dce5da0663d9 Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 14 Jul 2019 17:17:00 -0400 Subject: [PATCH] New: Don't Execute Certain Command Types In Parallel (#855) --- .../Messaging/Commands/CommandQueueFixture.cs | 175 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../ImportLists/ImportListSyncCommand.cs | 2 + .../Messaging/Commands/Command.cs | 2 + .../Messaging/Commands/CommandQueue.cs | 11 +- 5 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueFixture.cs diff --git a/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueFixture.cs b/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueFixture.cs new file mode 100644 index 000000000..2e759bdeb --- /dev/null +++ b/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueFixture.cs @@ -0,0 +1,175 @@ +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Download; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Test.Framework; +using Moq; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using NzbDrone.Core.ImportLists; +using NzbDrone.Core.Update.Commands; +using NzbDrone.Core.Music.Commands; + +namespace NzbDrone.Core.Test.Messaging.Commands +{ + [TestFixture] + public class CommandQueueFixture : CoreTest + { + private void GivenStartedDiskCommand() + { + var commandModel = Builder + .CreateNew() + .With(c => c.Name = "CheckForFinishedDownload") + .With(c => c.Body = new CheckForFinishedDownloadCommand()) + .With(c => c.Status = CommandStatus.Started) + .Build(); + + Subject.Add(commandModel); + } + + private void GivenStartedTypeExclusiveCommand() + { + var commandModel = Builder + .CreateNew() + .With(c => c.Name = "ImportListSync") + .With(c => c.Body = new ImportListSyncCommand()) + .With(c => c.Status = CommandStatus.Started) + .Build(); + + Subject.Add(commandModel); + } + + private void GivenStartedExclusiveCommand() + { + var commandModel = Builder + .CreateNew() + .With(c => c.Name = "ApplicationUpdate") + .With(c => c.Body = new ApplicationUpdateCommand()) + .With(c => c.Status = CommandStatus.Started) + .Build(); + + Subject.Add(commandModel); + } + + [Test] + public void should_not_return_disk_access_command_if_another_running() + { + GivenStartedDiskCommand(); + + var newCommandModel = Builder + .CreateNew() + .With(c => c.Name = "CheckForFinishedDownload") + .With(c => c.Body = new CheckForFinishedDownloadCommand()) + .Build(); + + Subject.Add(newCommandModel); + + Subject.TryGet(out var command); + + command.Should().BeNull(); + } + + [Test] + public void should_not_return_type_exclusive_command_if_another_running() + { + GivenStartedTypeExclusiveCommand(); + + var newCommandModel = Builder + .CreateNew() + .With(c => c.Name = "ImportListSync") + .With(c => c.Body = new ImportListSyncCommand()) + .Build(); + + Subject.Add(newCommandModel); + + Subject.TryGet(out var command); + + command.Should().BeNull(); + } + + [Test] + public void should_return_type_exclusive_command_if_another_not_running() + { + GivenStartedDiskCommand(); + + var newCommandModel = Builder + .CreateNew() + .With(c => c.Name = "ImportListSync") + .With(c => c.Body = new ImportListSyncCommand()) + .Build(); + + Subject.Add(newCommandModel); + + Subject.TryGet(out var command); + + command.Should().NotBeNull(); + command.Status.Should().Be(CommandStatus.Started); + } + + [Test] + public void should_return_regular_command_if_type_exclusive_command_running() + { + GivenStartedTypeExclusiveCommand(); + + var newCommandModel = Builder + .CreateNew() + .With(c => c.Name = "RefreshArtist") + .With(c => c.Body = new RefreshArtistCommand()) + .Build(); + + Subject.Add(newCommandModel); + + Subject.TryGet(out var command); + + command.Should().NotBeNull(); + command.Status.Should().Be(CommandStatus.Started); + } + + [Test] + public void should_not_return_exclusive_command_if_any_running() + { + GivenStartedDiskCommand(); + + var newCommandModel = Builder + .CreateNew() + .With(c => c.Name = "ApplicationUpdate") + .With(c => c.Body = new ApplicationUpdateCommand()) + .Build(); + + Subject.Add(newCommandModel); + + Subject.TryGet(out var command); + + command.Should().BeNull(); + } + + [Test] + public void should_not_return_any_command_if_exclusive_running() + { + GivenStartedExclusiveCommand(); + + var newCommandModel = Builder + .CreateNew() + .With(c => c.Name = "RefreshArtist") + .With(c => c.Body = new RefreshArtistCommand()) + .Build(); + + Subject.Add(newCommandModel); + + Subject.TryGet(out var command); + + command.Should().BeNull(); + } + + [Test] + public void should_return_null_if_nothing_queued() + { + GivenStartedDiskCommand(); + + Subject.TryGet(out var command); + + command.Should().BeNull(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 20b160d6a..dce180c61 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -265,6 +265,7 @@ + diff --git a/src/NzbDrone.Core/ImportLists/ImportListSyncCommand.cs b/src/NzbDrone.Core/ImportLists/ImportListSyncCommand.cs index d08820caf..9051d205f 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListSyncCommand.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListSyncCommand.cs @@ -17,6 +17,8 @@ namespace NzbDrone.Core.ImportLists public override bool SendUpdatesToClient => true; + public override bool IsTypeExclusive => true; + public override bool UpdateScheduledTask => !DefinitionId.HasValue; } } diff --git a/src/NzbDrone.Core/Messaging/Commands/Command.cs b/src/NzbDrone.Core/Messaging/Commands/Command.cs index db80322f5..a4575ebe9 100644 --- a/src/NzbDrone.Core/Messaging/Commands/Command.cs +++ b/src/NzbDrone.Core/Messaging/Commands/Command.cs @@ -24,6 +24,8 @@ namespace NzbDrone.Core.Messaging.Commands public virtual bool RequiresDiskAccess => false; public virtual bool IsExclusive => false; + public virtual bool IsTypeExclusive => false; + public string Name { get; private set; } public DateTime? LastExecutionTime { get; set; } public CommandTrigger Trigger { get; set; } diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs b/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs index b884e6bb0..3c4a6bc47 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs @@ -130,7 +130,11 @@ namespace NzbDrone.Core.Messaging.Commands var startedCommands = _items.Where(c => c.Status == CommandStatus.Started) .ToList(); - var localItem = _items.Where(c => + var exclusiveTypes = startedCommands.Where(x => x.Body.IsTypeExclusive) + .Select(x => x.Body.Name) + .ToList(); + + var localItem = _items.Where(c => { // If an executing command requires disk access don't return a command that // requires disk access. A lower priority or later queued task could be returned @@ -141,6 +145,11 @@ namespace NzbDrone.Core.Messaging.Commands !c.Body.RequiresDiskAccess; } + // If an executing command is TypeExclusive don't return a command with same type + if (startedCommands.Any(x => x.Body.IsTypeExclusive)) { + return c.Status == CommandStatus.Queued && !exclusiveTypes.Any(x => x == c.Body.Name); + } + return c.Status == CommandStatus.Queued; }) .OrderByDescending(c => c.Priority)