From cbd767ddcef7a857fb48d1cdb13e79e0ebf201b7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 23 Sep 2013 20:04:02 -0400 Subject: [PATCH] improve image serving performance slightly --- .../Drawing/ImageProcessor.cs | 94 +++++++++++-------- .../HttpServer/HttpServer.cs | 15 ++- 2 files changed, 67 insertions(+), 42 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 1f7361d2f6..305bede564 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Drawing } catch (IOException) { - // Cache file doesn't exist or is currently being written ro + // Cache file doesn't exist or is currently being written to } var semaphore = GetLock(cacheFilePath); @@ -129,21 +129,24 @@ namespace MediaBrowser.Server.Implementations.Drawing await semaphore.WaitAsync().ConfigureAwait(false); // Check again in case of lock contention - if (File.Exists(cacheFilePath)) + try { - try - { - using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - return; - } - } - finally + using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); semaphore.Release(); + return; } } + catch (IOException) + { + // Cache file doesn't exist or is currently being written to + } + catch + { + semaphore.Release(); + throw; + } try { @@ -188,12 +191,10 @@ namespace MediaBrowser.Server.Implementations.Drawing var bytes = outputMemoryStream.ToArray(); - var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length); + await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); // kick off a task to cache the result - var cacheTask = CacheResizedImage(cacheFilePath, bytes); - - await Task.WhenAll(outputTask, cacheTask).ConfigureAwait(false); + CacheResizedImage(cacheFilePath, bytes, semaphore); } } } @@ -202,12 +203,51 @@ namespace MediaBrowser.Server.Implementations.Drawing } } } - finally + catch { semaphore.Release(); + + throw; } } + /// + /// Caches the resized image. + /// + /// The cache file path. + /// The bytes. + /// The semaphore. + private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore) + { + Task.Run(async () => + { + try + { + var parentPath = Path.GetDirectoryName(cacheFilePath); + + if (!Directory.Exists(parentPath)) + { + Directory.CreateDirectory(parentPath); + } + + // Save to the cache location + using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + // Save to the filestream + await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath); + } + finally + { + semaphore.Release(); + } + }); + } + /// /// Sets the color of the background. /// @@ -363,28 +403,6 @@ namespace MediaBrowser.Server.Implementations.Drawing return croppedImagePath; } - /// - /// Caches the resized image. - /// - /// The cache file path. - /// The bytes. - private async Task CacheResizedImage(string cacheFilePath, byte[] bytes) - { - var parentPath = Path.GetDirectoryName(cacheFilePath); - - if (!Directory.Exists(parentPath)) - { - Directory.CreateDirectory(parentPath); - } - - // Save to the cache location - using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - // Save to the filestream - await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - } - } - /// /// Gets the cache file path based on a set of parameters /// diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs index f6547dec17..4f795fdd5f 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs @@ -314,6 +314,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The CTX. private async void ProcessHttpRequestAsync(HttpListenerContext context) { + var date = DateTime.Now; + LogHttpRequest(context); if (context.Request.IsWebSocketRequest) @@ -360,7 +362,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer var url = context.Request.Url.ToString(); var endPoint = context.Request.RemoteEndPoint; - LogResponse(context, url, endPoint); + var duration = DateTime.Now - date; + + LogResponse(context, url, endPoint, duration); } catch (Exception ex) @@ -461,14 +465,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// The CTX. /// The URL. /// The end point. - private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint) + /// The duration. + private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration) { if (!EnableHttpRequestLogging) { return; } - var statusode = ctx.Response.StatusCode; + var statusCode = ctx.Response.StatusCode; var log = new StringBuilder(); @@ -476,7 +481,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k]))); - var msg = "Http Response Sent (" + statusode + ") to " + endPoint; + var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds); + + var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime; _logger.LogMultiline(msg, LogSeverity.Debug, log); }