fix: run dashboard queries sequentially to avoid DbContext concurrency
DbContext is not thread-safe. Task.WhenAll caused concurrent access within the same scoped DbContext, throwing 'A second operation was started on this context instance' errors on slower machines. Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
This commit is contained in:
co-authored by
Claude Opus 4.8 noreply@anthropic.com
parent
a819322559
commit
f5ad1dec00
@@ -44,48 +44,34 @@ public sealed class DashboardService
|
||||
var todayUtcEnd = new DateTimeOffset(todayBeijingDate.ToDateTime(TimeOnly.MaxValue), ChinaTime.Zone.GetUtcOffset(beijingNow.DateTime));
|
||||
var recentErrorSince = now.AddHours(-24);
|
||||
|
||||
// Run independent queries in parallel for efficiency
|
||||
var activeRecordingTask = _recordSessionRepository.CountByStatusAsync(RecordSessionStatus.Running, cancellationToken);
|
||||
var liveRoomCountTask = _liveRoomRepository.CountByAvailabilityAsync(LiveRoomAvailabilityStatus.Live, cancellationToken);
|
||||
var offlineRoomCountTask = _liveRoomRepository.CountByAvailabilityAsync(LiveRoomAvailabilityStatus.Offline, cancellationToken);
|
||||
var totalRoomCountTask = _liveRoomRepository.CountAsync(cancellationToken);
|
||||
var activeSessionCountTask = _recordSessionRepository.CountActiveAsync(cancellationToken);
|
||||
var recentErrorCountTask = _systemLogRepository.CountRecentErrorsAsync(recentErrorSince, cancellationToken);
|
||||
var todayDurationTask = _recordTaskRepository.SumDurationSecondsAsync(todayUtcStart, todayUtcEnd, cancellationToken);
|
||||
var todayAggregateTask = _recordResultRepository.GetTodayAggregateAsync(todayUtcStart, todayUtcEnd, cancellationToken);
|
||||
var recentSessionsTask = _recordSessionRepository.ListRecentAsync(5, cancellationToken);
|
||||
var todaySessionsTask = _recordSessionRepository.ListInDateRangeAsync(todayUtcStart, todayUtcEnd, cancellationToken);
|
||||
var settingsTask = _systemSettingsService.GetAsync(cancellationToken);
|
||||
|
||||
await Task.WhenAll(
|
||||
activeRecordingTask,
|
||||
liveRoomCountTask,
|
||||
offlineRoomCountTask,
|
||||
totalRoomCountTask,
|
||||
activeSessionCountTask,
|
||||
recentErrorCountTask,
|
||||
todayDurationTask,
|
||||
todayAggregateTask,
|
||||
recentSessionsTask,
|
||||
todaySessionsTask,
|
||||
(Task)settingsTask
|
||||
);
|
||||
|
||||
var (todayTotalBytes, todayTotalDanmaku) = todayAggregateTask.Result;
|
||||
var settings = settingsTask.Result;
|
||||
// Run queries sequentially — DbContext is not thread-safe
|
||||
var activeRecordingCount = await _recordSessionRepository.CountByStatusAsync(RecordSessionStatus.Running, cancellationToken);
|
||||
var liveRoomCount = await _liveRoomRepository.CountByAvailabilityAsync(LiveRoomAvailabilityStatus.Live, cancellationToken);
|
||||
var offlineRoomCount = await _liveRoomRepository.CountByAvailabilityAsync(LiveRoomAvailabilityStatus.Offline, cancellationToken);
|
||||
var totalRoomCount = await _liveRoomRepository.CountAsync(cancellationToken);
|
||||
var activeSessionCount = await _recordSessionRepository.CountActiveAsync(cancellationToken);
|
||||
var recentErrorCount = await _systemLogRepository.CountRecentErrorsAsync(recentErrorSince, cancellationToken);
|
||||
var todayRecordingSeconds = await _recordTaskRepository.SumDurationSecondsAsync(todayUtcStart, todayUtcEnd, cancellationToken);
|
||||
var (todayTotalBytes, todayTotalDanmaku) = await _recordResultRepository.GetTodayAggregateAsync(todayUtcStart, todayUtcEnd, cancellationToken);
|
||||
var recentSessions = await _recordSessionRepository.ListRecentAsync(5, cancellationToken);
|
||||
var todaySessions = await _recordSessionRepository.ListInDateRangeAsync(todayUtcStart, todayUtcEnd, cancellationToken);
|
||||
var settings = await _systemSettingsService.GetAsync(cancellationToken);
|
||||
var storageCheck = _storageGuardService.CheckCanStartOrResume(settings);
|
||||
var pendingTranscodeCount = await _recordTaskRepository.CountByStatusAsync(RecordTaskStatus.Processing, cancellationToken);
|
||||
var pendingUploadCount = await _recordResultRepository.CountPendingUploadAsync(cancellationToken);
|
||||
var queuedDataBytes = await _recordResultRepository.SumPendingUploadBytesAsync(cancellationToken);
|
||||
|
||||
return new DashboardDto
|
||||
{
|
||||
ActiveRecordingCount = activeRecordingTask.Result,
|
||||
LiveRoomCount = liveRoomCountTask.Result,
|
||||
OfflineRoomCount = offlineRoomCountTask.Result,
|
||||
TotalRoomCount = totalRoomCountTask.Result,
|
||||
TodayRecordingSeconds = todayDurationTask.Result,
|
||||
ActiveRecordingCount = activeRecordingCount,
|
||||
LiveRoomCount = liveRoomCount,
|
||||
OfflineRoomCount = offlineRoomCount,
|
||||
TotalRoomCount = totalRoomCount,
|
||||
TodayRecordingSeconds = todayRecordingSeconds,
|
||||
TodayDataBytes = todayTotalBytes,
|
||||
TodayDanmakuCount = todayTotalDanmaku,
|
||||
ActiveSessionCount = activeSessionCountTask.Result,
|
||||
RecentErrorCount = recentErrorCountTask.Result,
|
||||
ActiveSessionCount = activeSessionCount,
|
||||
RecentErrorCount = recentErrorCount,
|
||||
StorageStatus = new StorageStatusDto
|
||||
{
|
||||
HasEnoughSpace = storageCheck.HasEnoughSpace,
|
||||
@@ -94,13 +80,13 @@ public sealed class DashboardService
|
||||
Tier = storageCheck.Tier.ToString(),
|
||||
UsagePercent = storageCheck.UsagePercent
|
||||
},
|
||||
PendingTranscodeCount = await _recordTaskRepository.CountByStatusAsync(Domain.Enums.RecordTaskStatus.Processing, cancellationToken),
|
||||
PendingUploadCount = await _recordResultRepository.CountPendingUploadAsync(cancellationToken),
|
||||
QueuedDataBytes = await _recordResultRepository.SumPendingUploadBytesAsync(cancellationToken),
|
||||
RecentSessions = recentSessionsTask.Result
|
||||
PendingTranscodeCount = pendingTranscodeCount,
|
||||
PendingUploadCount = pendingUploadCount,
|
||||
QueuedDataBytes = queuedDataBytes,
|
||||
RecentSessions = recentSessions
|
||||
.Select(MapRecentSession)
|
||||
.ToList(),
|
||||
TopRooms = ComputeTopRooms(todaySessionsTask.Result)
|
||||
TopRooms = ComputeTopRooms(todaySessions)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user