fix: move LogInformation inside disposal guard in both GetTrack streaming arms

Prevents an OS handle leak if the logger throws after the FileStream is opened but before File() takes ownership. Also corrects a stale "finally block" comment in the lossless arm — it has always been a catch.
This commit is contained in:
daniel-c-harvey
2026-06-26 15:16:29 -04:00
parent d72263aea1
commit afa862a67b
+7 -9
View File
@@ -741,7 +741,7 @@ public class TrackController : ControllerBase
} }
// Resolve MIME and log before handing the stream to File(). // Resolve MIME and log before handing the stream to File().
// If anything here throws, the finally block disposes the wrapper // If anything here throws, the catch block disposes the wrapper
// (and its inner FileStream) so neither leaks. On the success path // (and its inner FileStream) so neither leaks. On the success path
// File() takes ownership of the inner stream; ASP.NET Core disposes // File() takes ownership of the inner stream; ASP.NET Core disposes
// it after the response body is sent. The wrapper is a thin struct // it after the response body is sent. The wrapper is a thin struct
@@ -755,16 +755,15 @@ public class TrackController : ControllerBase
streamMimeType = MimeTypeExtensions.GetMimeType(mediaStream.Extension); streamMimeType = MimeTypeExtensions.GetMimeType(mediaStream.Extension);
streamLength = mediaStream.Stream.Length; streamLength = mediaStream.Stream.Length;
innerStream = mediaStream.Stream; innerStream = mediaStream.Stream;
_logger.LogInformation(
"Streaming track from disk: {TrackId}, Size: {Size} bytes",
trackId, streamLength);
} }
catch catch
{ {
await mediaStream.DisposeAsync(); await mediaStream.DisposeAsync();
throw; throw;
} }
_logger.LogInformation(
"Streaming track from disk: {TrackId}, Size: {Size} bytes",
trackId, streamLength);
// enableRangeProcessing: true — seek is served by HTTP Range requests. // enableRangeProcessing: true — seek is served by HTTP Range requests.
// The FileStream is seekable, so ASP.NET Core honours an incoming // The FileStream is seekable, so ASP.NET Core honours an incoming
// Range header by slicing the file and responding 206 Partial Content. // Range header by slicing the file and responding 206 Partial Content.
@@ -808,6 +807,9 @@ public class TrackController : ControllerBase
// Length from the seekable FileStream — a metadata read, not a body load. // Length from the seekable FileStream — a metadata read, not a body load.
streamLength = resolved.Stream.Length; streamLength = resolved.Stream.Length;
innerStream = resolved.Stream; innerStream = resolved.Stream;
_logger.LogInformation(
"Streaming track {TrackId} as {Format} ({Size} bytes, {ContentType})",
trackId, resolved.ResolvedFormat, streamLength, contentType);
} }
catch catch
{ {
@@ -815,10 +817,6 @@ public class TrackController : ControllerBase
throw; throw;
} }
_logger.LogInformation(
"Streaming track {TrackId} as {Format} ({Size} bytes, {ContentType})",
trackId, resolved.ResolvedFormat, streamLength, contentType);
return File(innerStream, contentType, enableRangeProcessing: true); return File(innerStream, contentType, enableRangeProcessing: true);
} }
catch (Exception ex) catch (Exception ex)