Updated contributors, upgraded to AsyncKeyedLocker 6.3.0 which now supports non-keyed locking using a similar interface and changed SemaphoreSlim-based locks to using AsyncNonKeyedLocker.

pull/10801/head
Mark Cilia Vincenti 4 months ago
parent 6a257e1b40
commit e47144e7c7

@ -77,6 +77,7 @@
- [Marenz](https://github.com/Marenz) - [Marenz](https://github.com/Marenz)
- [marius-luca-87](https://github.com/marius-luca-87) - [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro) - [mark-monteiro](https://github.com/mark-monteiro)
- [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti)
- [Matt07211](https://github.com/Matt07211) - [Matt07211](https://github.com/Matt07211)
- [Maxr1998](https://github.com/Maxr1998) - [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00) - [mcarlton00](https://github.com/mcarlton00)

@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.--> <!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies"> <ItemGroup Label="Package Dependencies">
<PackageVersion Include="AsyncKeyedLock" Version="6.2.6" /> <PackageVersion Include="AsyncKeyedLock" Version="6.3.0" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" /> <PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" /> <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" /> <PackageVersion Include="AutoFixture" Version="4.18.1" />

@ -11,6 +11,7 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Library
private readonly IDirectoryService _directoryService; private readonly IDirectoryService _directoryService;
private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private readonly AsyncNonKeyedLocker _liveStreamLocker = new(1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
@ -467,12 +468,10 @@ namespace Emby.Server.Implementations.Library
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
{ {
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
MediaSourceInfo mediaSource; MediaSourceInfo mediaSource;
ILiveStream liveStream; ILiveStream liveStream;
try using (await _liveStreamLocker.LockAsync(cancellationToken).ConfigureAwait(false))
{ {
var (provider, keyId) = GetProvider(request.OpenToken); var (provider, keyId) = GetProvider(request.OpenToken);
@ -492,10 +491,6 @@ namespace Emby.Server.Implementations.Library
_openStreams[mediaSource.LiveStreamId] = liveStream; _openStreams[mediaSource.LiveStreamId] = liveStream;
} }
finally
{
_liveStreamSemaphore.Release();
}
try try
{ {
@ -836,9 +831,7 @@ namespace Emby.Server.Implementations.Library
{ {
ArgumentException.ThrowIfNullOrEmpty(id); ArgumentException.ThrowIfNullOrEmpty(id);
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); using (await _liveStreamLocker.LockAsync().ConfigureAwait(false))
try
{ {
if (_openStreams.TryGetValue(id, out ILiveStream liveStream)) if (_openStreams.TryGetValue(id, out ILiveStream liveStream))
{ {
@ -857,10 +850,6 @@ namespace Emby.Server.Implementations.Library
} }
} }
} }
finally
{
_liveStreamSemaphore.Release();
}
} }
private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key) private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
@ -897,7 +886,7 @@ namespace Emby.Server.Implementations.Library
CloseLiveStream(key).GetAwaiter().GetResult(); CloseLiveStream(key).GetAwaiter().GetResult();
} }
_liveStreamSemaphore.Dispose(); _liveStreamLocker.Dispose();
} }
} }
} }

@ -26,6 +26,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AsyncKeyedLock" />
<PackageReference Include="EFCoreSecondLevelCacheInterceptor" /> <PackageReference Include="EFCoreSecondLevelCacheInterceptor" />
<PackageReference Include="System.Linq.Async" /> <PackageReference Include="System.Linq.Async" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />

@ -6,6 +6,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -37,7 +38,7 @@ public class TrickplayManager : ITrickplayManager
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider; private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private static readonly SemaphoreSlim _resourcePool = new(1, 1); private static readonly AsyncNonKeyedLocker _resourcePool = new(1);
private static readonly string[] _trickplayImgExtensions = { ".jpg" }; private static readonly string[] _trickplayImgExtensions = { ".jpg" };
/// <summary> /// <summary>
@ -107,93 +108,92 @@ public class TrickplayManager : ITrickplayManager
var imgTempDir = string.Empty; var imgTempDir = string.Empty;
var outputDir = GetTrickplayDirectory(video, width); var outputDir = GetTrickplayDirectory(video, width);
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
try
{ {
if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(width)) try
{
_logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting.", video.Id);
return;
}
// Extract images
// Note: Media sources under parent items exist as their own video/item as well. Only use this video stream for trickplay.
var mediaSource = video.GetMediaSources(false).Find(source => Guid.Parse(source.Id).Equals(video.Id));
if (mediaSource is null)
{ {
_logger.LogDebug("Found no matching media source for item {ItemId}", video.Id); if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(width))
return; {
} _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting.", video.Id);
return;
}
var mediaPath = mediaSource.Path; // Extract images
var mediaStream = mediaSource.VideoStream; // Note: Media sources under parent items exist as their own video/item as well. Only use this video stream for trickplay.
var container = mediaSource.Container; var mediaSource = video.GetMediaSources(false).Find(source => Guid.Parse(source.Id).Equals(video.Id));
_logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", width, mediaPath, video.Id); if (mediaSource is null)
imgTempDir = await _mediaEncoder.ExtractVideoImagesOnIntervalAccelerated( {
mediaPath, _logger.LogDebug("Found no matching media source for item {ItemId}", video.Id);
container, return;
mediaSource, }
mediaStream,
width,
TimeSpan.FromMilliseconds(options.Interval),
options.EnableHwAcceleration,
options.ProcessThreads,
options.Qscale,
options.ProcessPriority,
_encodingHelper,
cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(imgTempDir) || !Directory.Exists(imgTempDir)) var mediaPath = mediaSource.Path;
{ var mediaStream = mediaSource.VideoStream;
throw new InvalidOperationException("Null or invalid directory from media encoder."); var container = mediaSource.Container;
}
_logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", width, mediaPath, video.Id);
imgTempDir = await _mediaEncoder.ExtractVideoImagesOnIntervalAccelerated(
mediaPath,
container,
mediaSource,
mediaStream,
width,
TimeSpan.FromMilliseconds(options.Interval),
options.EnableHwAcceleration,
options.ProcessThreads,
options.Qscale,
options.ProcessPriority,
_encodingHelper,
cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(imgTempDir) || !Directory.Exists(imgTempDir))
{
throw new InvalidOperationException("Null or invalid directory from media encoder.");
}
var images = _fileSystem.GetFiles(imgTempDir, _trickplayImgExtensions, false, false) var images = _fileSystem.GetFiles(imgTempDir, _trickplayImgExtensions, false, false)
.Select(i => i.FullName) .Select(i => i.FullName)
.OrderBy(i => i) .OrderBy(i => i)
.ToList(); .ToList();
// Create tiles // Create tiles
var trickplayInfo = CreateTiles(images, width, options, outputDir); var trickplayInfo = CreateTiles(images, width, options, outputDir);
// Save tiles info // Save tiles info
try try
{
if (trickplayInfo is not null)
{ {
trickplayInfo.ItemId = video.Id; if (trickplayInfo is not null)
await SaveTrickplayInfo(trickplayInfo).ConfigureAwait(false); {
trickplayInfo.ItemId = video.Id;
await SaveTrickplayInfo(trickplayInfo).ConfigureAwait(false);
_logger.LogInformation("Finished creation of trickplay files for {0}", mediaPath); _logger.LogInformation("Finished creation of trickplay files for {0}", mediaPath);
}
else
{
throw new InvalidOperationException("Null trickplay tiles info from CreateTiles.");
}
} }
else catch (Exception ex)
{ {
throw new InvalidOperationException("Null trickplay tiles info from CreateTiles."); _logger.LogError(ex, "Error while saving trickplay tiles info.");
// Make sure no files stay in metadata folders on failure
// if tiles info wasn't saved.
Directory.Delete(outputDir, true);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error while saving trickplay tiles info."); _logger.LogError(ex, "Error creating trickplay images.");
// Make sure no files stay in metadata folders on failure
// if tiles info wasn't saved.
Directory.Delete(outputDir, true);
} }
} finally
catch (Exception ex)
{
_logger.LogError(ex, "Error creating trickplay images.");
}
finally
{
_resourcePool.Release();
if (!string.IsNullOrEmpty(imgTempDir))
{ {
Directory.Delete(imgTempDir, true); if (!string.IsNullOrEmpty(imgTempDir))
{
Directory.Delete(imgTempDir, true);
}
} }
} }
} }

@ -100,6 +100,6 @@ public interface ITranscodeManager
/// </summary> /// </summary>
/// <param name="outputPath">The output path of the transcoded file.</param> /// <param name="outputPath">The output path of the transcoded file.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="SemaphoreSlim"/>.</returns> /// <returns>An <see cref="IDisposable"/>.</returns>
ValueTask<IDisposable> LockAsync(string outputPath, CancellationToken cancellationToken); ValueTask<IDisposable> LockAsync(string outputPath, CancellationToken cancellationToken);
} }

@ -11,6 +11,7 @@ using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using Jellyfin.Extensions.Json.Converters; using Jellyfin.Extensions.Json.Converters;
@ -60,7 +61,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly IServerConfigurationManager _serverConfig; private readonly IServerConfigurationManager _serverConfig;
private readonly string _startupOptionFFmpegPath; private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool; private readonly AsyncNonKeyedLocker _thumbnailResourcePool;
private readonly object _runningProcessesLock = new object(); private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
@ -116,7 +117,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter()); _jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter());
var semaphoreCount = 2 * Environment.ProcessorCount; var semaphoreCount = 2 * Environment.ProcessorCount;
_thumbnailResourcePool = new SemaphoreSlim(semaphoreCount, semaphoreCount); _thumbnailResourcePool = new(semaphoreCount);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -754,8 +755,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
bool ranToCompletion; bool ranToCompletion;
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); using (await _thumbnailResourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
try
{ {
StartProcess(processWrapper); StartProcess(processWrapper);
@ -776,10 +776,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
ranToCompletion = false; ranToCompletion = false;
} }
} }
finally
{
_thumbnailResourcePool.Release();
}
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
var file = _fileSystem.GetFileInfo(tempExtractPath); var file = _fileSystem.GetFileInfo(tempExtractPath);

@ -727,7 +727,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
/// </summary> /// </summary>
/// <param name="outputPath">The output path of the transcoded file.</param> /// <param name="outputPath">The output path of the transcoded file.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="SemaphoreSlim"/>.</returns> /// <returns>An <see cref="IDisposable"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ValueTask<IDisposable> LockAsync(string outputPath, CancellationToken cancellationToken) public ValueTask<IDisposable> LockAsync(string outputPath, CancellationToken cancellationToken)
{ {

@ -7,6 +7,7 @@ using System.Net.Mime;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
@ -38,7 +39,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly IImageEncoder _imageEncoder; private readonly IImageEncoder _imageEncoder;
private readonly SemaphoreSlim _parallelEncodingLimit; private readonly AsyncNonKeyedLocker _parallelEncodingLimit;
private bool _disposed; private bool _disposed;
@ -68,7 +69,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
semaphoreCount = 2 * Environment.ProcessorCount; semaphoreCount = 2 * Environment.ProcessorCount;
} }
_parallelEncodingLimit = new(semaphoreCount, semaphoreCount); _parallelEncodingLimit = new(semaphoreCount);
} }
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images"); private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
@ -193,18 +194,13 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
{ {
if (!File.Exists(cacheFilePath)) if (!File.Exists(cacheFilePath))
{ {
// Limit number of parallel (more precisely: concurrent) image encodings to prevent a high memory usage
await _parallelEncodingLimit.WaitAsync().ConfigureAwait(false);
string resultPath; string resultPath;
try
// Limit number of parallel (more precisely: concurrent) image encodings to prevent a high memory usage
using (await _parallelEncodingLimit.LockAsync().ConfigureAwait(false))
{ {
resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat); resultPath = _imageEncoder.EncodeImage(originalImagePath, dateModified, cacheFilePath, autoOrient, orientation, quality, options, outputFormat);
} }
finally
{
_parallelEncodingLimit.Release();
}
if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase)) if (string.Equals(resultPath, originalImagePath, StringComparison.OrdinalIgnoreCase))
{ {

@ -21,4 +21,8 @@
<Compile Include="..\..\SharedVersion.cs" /> <Compile Include="..\..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="AsyncKeyedLock" />
</ItemGroup>
</Project> </Project>

@ -8,6 +8,7 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -50,7 +51,7 @@ namespace Jellyfin.LiveTv.Channels
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); private readonly AsyncNonKeyedLocker _resourcePool = new(1);
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _disposed = false; private bool _disposed = false;
@ -832,9 +833,7 @@ namespace Jellyfin.LiveTv.Channels
{ {
} }
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
try
{ {
try try
{ {
@ -881,10 +880,6 @@ namespace Jellyfin.LiveTv.Channels
return result; return result;
} }
finally
{
_resourcePool.Release();
}
} }
private async Task CacheResponse(ChannelItemResult result, string path) private async Task CacheResponse(ChannelItemResult result, string path)

@ -14,6 +14,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using AsyncKeyedLock;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -68,7 +69,7 @@ namespace Jellyfin.LiveTv.EmbyTV
private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels = private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1); private readonly AsyncNonKeyedLocker _recordingDeleteSemaphore = new(1);
private bool _disposed; private bool _disposed;
@ -1447,9 +1448,7 @@ namespace Jellyfin.LiveTv.EmbyTV
return; return;
} }
await _recordingDeleteSemaphore.WaitAsync().ConfigureAwait(false); using (await _recordingDeleteSemaphore.LockAsync().ConfigureAwait(false))
try
{ {
if (_disposed) if (_disposed)
{ {
@ -1502,10 +1501,6 @@ namespace Jellyfin.LiveTv.EmbyTV
} }
} }
} }
finally
{
_recordingDeleteSemaphore.Release();
}
} }
private void DeleteLibraryItemsForTimers(List<TimerInfo> timers) private void DeleteLibraryItemsForTimers(List<TimerInfo> timers)

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
@ -11,6 +11,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AsyncKeyedLock" />
<PackageReference Include="Jellyfin.XmlTv" /> <PackageReference Include="Jellyfin.XmlTv" />
<PackageReference Include="System.Linq.Async" /> <PackageReference Include="System.Linq.Async" />
</ItemGroup> </ItemGroup>

@ -16,6 +16,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncKeyedLock;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using Jellyfin.LiveTv.Listings.SchedulesDirectDtos; using Jellyfin.LiveTv.Listings.SchedulesDirectDtos;
@ -35,7 +36,7 @@ namespace Jellyfin.LiveTv.Listings
private readonly ILogger<SchedulesDirect> _logger; private readonly ILogger<SchedulesDirect> _logger;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); private readonly AsyncNonKeyedLocker _tokenLock = new(1);
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
@ -573,27 +574,25 @@ namespace Jellyfin.LiveTv.Listings
} }
} }
await _tokenSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); using (await _tokenLock.LockAsync(cancellationToken).ConfigureAwait(false))
try
{
var result = await GetTokenInternal(username, password, cancellationToken).ConfigureAwait(false);
savedToken.Name = result;
savedToken.Value = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture);
return result;
}
catch (HttpRequestException ex)
{ {
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest) try
{ {
_tokens.Clear(); var result = await GetTokenInternal(username, password, cancellationToken).ConfigureAwait(false);
_lastErrorResponse = DateTime.UtcNow; savedToken.Name = result;
savedToken.Value = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture);
return result;
} }
catch (HttpRequestException ex)
{
if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest)
{
_tokens.Clear();
_lastErrorResponse = DateTime.UtcNow;
}
throw; throw;
} }
finally
{
_tokenSemaphore.Release();
} }
} }
@ -801,7 +800,7 @@ namespace Jellyfin.LiveTv.Listings
if (disposing) if (disposing)
{ {
_tokenSemaphore?.Dispose(); _tokenLock?.Dispose();
} }
_disposed = true; _disposed = true;

Loading…
Cancel
Save