From 3c51c56075d83a6571004439d610b6de8b4d6c58 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 6 Mar 2026 09:42:42 +0100 Subject: [PATCH 1/5] Configure FileStreamOptions with PreallocationSize and async access in ZipArchiveEntry extract methods Set PreallocationSize based on the entry's uncompressed size to allow the file system to pre-allocate disk space, reducing fragmentation and improving write performance. Configure FileOptions.Asynchronous when extracting from async methods so that the FileStream uses async I/O. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index 24e685e5581f74..aaa06f012f2456 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -116,6 +116,7 @@ private static void ExtractToFileInitialize(ZipArchiveEntry source, string desti Mode = overwrite ? FileMode.Create : FileMode.CreateNew, Share = FileShare.None, BufferSize = ZipFile.FileStreamBufferSize, + PreallocationSize = source.Length, Options = useAsync ? FileOptions.Asynchronous : FileOptions.None }; From 2e6a94244a72df0b8df3e24a98483964c326fbf0 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Fri, 6 Mar 2026 11:49:45 +0100 Subject: [PATCH 2/5] Read source.Length defensively for PreallocationSize ZipArchiveEntry.Length throws InvalidOperationException when the entry has been opened for writing (e.g. in Update mode). Read it in a try/catch and fall back to 0 (no preallocation) to avoid regressing that scenario. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ZipFileExtensions.ZipArchiveEntry.Extract.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index aaa06f012f2456..c803613de3ac4b 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -110,13 +110,20 @@ private static void ExtractToFileInitialize(ZipArchiveEntry source, string desti ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(destinationFileName); + long preallocationSize = 0; + try + { + preallocationSize = source.Length; + } + catch (InvalidOperationException) { } + fileStreamOptions = new() { Access = FileAccess.Write, Mode = overwrite ? FileMode.Create : FileMode.CreateNew, Share = FileShare.None, BufferSize = ZipFile.FileStreamBufferSize, - PreallocationSize = source.Length, + PreallocationSize = preallocationSize, Options = useAsync ? FileOptions.Asynchronous : FileOptions.None }; From e5a0ba1bd1433388b7c754d1f953ee5ffe5a3137 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Mon, 16 Mar 2026 09:37:46 +0100 Subject: [PATCH 3/5] Add comment --- .../IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index c803613de3ac4b..fae8218bae60a3 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -113,6 +113,8 @@ private static void ExtractToFileInitialize(ZipArchiveEntry source, string desti long preallocationSize = 0; try { + // .Length can throw if the entry stream has been opened for write. + // In that case, we fallback to 0 (no preallocation) preallocationSize = source.Length; } catch (InvalidOperationException) { } From 392b21a29cc873d31f498d4fa966d867362adbc3 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Mon, 16 Mar 2026 09:44:56 +0100 Subject: [PATCH 4/5] Add if gate against Create mode --- .../ZipFileExtensions.ZipArchiveEntry.Extract.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index fae8218bae60a3..436b2d372e0c18 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -114,8 +114,13 @@ private static void ExtractToFileInitialize(ZipArchiveEntry source, string desti try { // .Length can throw if the entry stream has been opened for write. - // In that case, we fallback to 0 (no preallocation) - preallocationSize = source.Length; + // For archives in Update mode, we have no way to check if the entry + // was opened for write, so we attempt to get the length and if it fails + // we just skip preallocation. + if (source.Archive.Mode != ZipArchiveMode.Create) + { + preallocationSize = source.Length; + } } catch (InvalidOperationException) { } From ebcce25c85b3f4c5fa6d6a9407443f6474bc131f Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:01:29 +0100 Subject: [PATCH 5/5] Apply code review suggestion Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index 436b2d372e0c18..dfc670c3394c02 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -117,7 +117,7 @@ private static void ExtractToFileInitialize(ZipArchiveEntry source, string desti // For archives in Update mode, we have no way to check if the entry // was opened for write, so we attempt to get the length and if it fails // we just skip preallocation. - if (source.Archive.Mode != ZipArchiveMode.Create) + if (source.Archive is ZipArchive archive && archive.Mode != ZipArchiveMode.Create) { preallocationSize = source.Length; }