From 0196ee882a5c6a956827e84fc79fb824de51a07c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 19:09:38 +0900 Subject: [PATCH] Redirect batch imports to a separate task scheduler to avoid contention with interactive actions --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game/Database/ArchiveModelManager.cs | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index d3475de157..3ffb512b7f 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -165,10 +165,10 @@ namespace osu.Game.Tests.Online { } - public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) + public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { await AllowImport.Task; - return await (CurrentImportTask = base.Import(item, archive, cancellationToken)); + return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)); } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index cc107d61c6..03b8db2cb8 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -38,6 +38,11 @@ namespace osu.Game.Database { private const int import_queue_request_concurrency = 1; + /// + /// The size of a batch import operation before considering it a lower priority operation. + /// + private const int low_priority_import_batch_size = 1; + /// /// A singleton scheduler shared by all . /// @@ -47,6 +52,13 @@ namespace osu.Game.Database /// private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); + /// + /// A second scheduler for lower priority imports. + /// For simplicity, these will just run in parallel with normal priority imports, but a future refactor would see this implemented via a custom scheduler/queue. + /// See https://gist.github.com/peppy/f0e118a14751fc832ca30dd48ba3876b for an incomplete version of this. + /// + private static readonly ThreadedTaskScheduler import_scheduler_low_priority = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); + /// /// Set an endpoint for notifications to be posted to. /// @@ -103,8 +115,11 @@ namespace osu.Game.Database /// /// Import one or more items from filesystem . - /// This will post notifications tracking progress. /// + /// + /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority. + /// This will post notifications tracking progress. + /// /// One or more archive locations on disk. public Task Import(params string[] paths) { @@ -133,13 +148,15 @@ namespace osu.Game.Database var imported = new List(); + bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; + await Task.WhenAll(tasks.Select(async task => { notification.CancellationToken.ThrowIfCancellationRequested(); try { - var model = await Import(task, notification.CancellationToken); + var model = await Import(task, isLowPriorityImport, notification.CancellationToken); lock (imported) { @@ -193,15 +210,16 @@ namespace osu.Game.Database /// Note that this bypasses the UI flow and should only be used for special cases or testing. /// /// The containing data about the to import. + /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - internal async Task Import(ImportTask task, CancellationToken cancellationToken = default) + internal async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); TModel import; using (ArchiveReader reader = task.GetReader()) - import = await Import(reader, cancellationToken); + import = await Import(reader, lowPriority, cancellationToken); // We may or may not want to delete the file depending on where it is stored. // e.g. reconstructing/repairing database with items from default storage. @@ -229,8 +247,9 @@ namespace osu.Game.Database /// Silently import an item from an . /// /// The archive to be imported. + /// Whether this is a low priority import. /// An optional cancellation token. - public Task Import(ArchiveReader archive, CancellationToken cancellationToken = default) + public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -253,7 +272,7 @@ namespace osu.Game.Database return null; } - return Import(model, archive, cancellationToken); + return Import(model, archive, lowPriority, cancellationToken); } /// @@ -307,8 +326,9 @@ namespace osu.Game.Database /// /// The model to be imported. /// An optional archive to use for model population. + /// Whether this is a low priority import. /// An optional cancellation token. - public virtual async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => + public virtual async Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { cancellationToken.ThrowIfCancellationRequested(); @@ -383,7 +403,7 @@ namespace osu.Game.Database flushEvents(true); return item; - }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); + }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap(); /// /// Exports an item to a legacy (.zip based) package.