From 6c9b6feba604222695f19f3008f16eccab6bc75a Mon Sep 17 00:00:00 2001 From: ksemenenko Date: Tue, 31 Mar 2026 21:38:29 +0200 Subject: [PATCH] fix browser opfs partial writes in ci --- .../wwwroot/browserStorage.worker.js | 39 ++++++++++++++++--- .../Components/Pages/StoragePlayground.razor | 9 ++++- .../Pages/StoragePlayground.razor | 9 ++++- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/Storages/ManagedCode.Storage.Browser/wwwroot/browserStorage.worker.js b/Storages/ManagedCode.Storage.Browser/wwwroot/browserStorage.worker.js index 52b6123..98e53ee 100644 --- a/Storages/ManagedCode.Storage.Browser/wwwroot/browserStorage.worker.js +++ b/Storages/ManagedCode.Storage.Browser/wwwroot/browserStorage.worker.js @@ -1,4 +1,5 @@ const sessions = new Map(); +const maxWriteBlockBytes = 1024 * 1024; self.onmessage = async (event) => { const { id, command, payload } = event.data ?? {}; @@ -59,12 +60,7 @@ async function appendChunksAsync(databaseName, blobKey, chunks) { for (const chunk of Array.isArray(chunks) ? chunks : []) { const data = chunk.data instanceof Uint8Array ? chunk.data : new Uint8Array(chunk.data ?? []); - const written = session.accessHandle.write(data, { at: session.position }); - if (written !== data.byteLength) { - throw new Error(`Short OPFS write for ${blobKey}: wrote ${written} of ${data.byteLength} bytes.`); - } - - session.position += written; + writeAllBytes(session, blobKey, data); } } @@ -156,6 +152,37 @@ function getSessionKey(databaseName, blobKey) { return `${databaseName}::${blobKey}`; } +function writeAllBytes(session, blobKey, data) { + let offset = 0; + + while (offset < data.byteLength) { + const bytesRemaining = data.byteLength - offset; + const bytesToWrite = Math.min(bytesRemaining, maxWriteBlockBytes); + const slice = data.subarray(offset, offset + bytesToWrite); + const written = normalizeBytesWritten( + session.accessHandle.write(slice, { at: session.position }), + bytesToWrite, + blobKey, + session.position); + + offset += written; + session.position += written; + } +} + +function normalizeBytesWritten(value, expectedBytes, blobKey, position) { + if (!Number.isFinite(value)) { + throw new Error(`Invalid OPFS write result for ${blobKey} at ${position}: ${String(value)}.`); + } + + const written = Math.trunc(value); + if (written <= 0 || written > expectedBytes) { + throw new Error(`Invalid OPFS write result for ${blobKey} at ${position}: wrote ${written} of ${expectedBytes} bytes.`); + } + + return written; +} + async function getDatabaseDirectoryAsync(databaseName, create) { const root = await navigator.storage.getDirectory(); return await root.getDirectoryHandle(getDatabaseDirectoryName(databaseName), { create }); diff --git a/Tests/ManagedCode.Storage.BrowserServerHost/Components/Pages/StoragePlayground.razor b/Tests/ManagedCode.Storage.BrowserServerHost/Components/Pages/StoragePlayground.razor index 9b8a5dd..3099636 100644 --- a/Tests/ManagedCode.Storage.BrowserServerHost/Components/Pages/StoragePlayground.razor +++ b/Tests/ManagedCode.Storage.BrowserServerHost/Components/Pages/StoragePlayground.razor @@ -134,6 +134,13 @@ MimeType = "application/octet-stream" }); + if (result.IsFailed) + { + largeOutput = $"generated:{stream.Position}"; + status = $"large-save-failed:{result.Problem?.Detail}"; + return; + } + if (!stream.IsCompleted) { largeOutput = $"generated:{stream.Position}"; @@ -145,7 +152,7 @@ var crc = stream.CompletedCrc; largeOutput = $"expected:{length}:{crc}"; - status = result.IsSuccess ? $"large-saved:{length}:{crc}" : $"large-save-failed:{result.Problem?.Detail}"; + status = $"large-saved:{length}:{crc}"; Logger.LogInformation("Completed browser storage save for {FileName} ({Bytes} bytes, crc {Crc}) in {ElapsedMilliseconds} ms.", resolvedFileName, length, diff --git a/Tests/ManagedCode.Storage.BrowserWasmHost/Pages/StoragePlayground.razor b/Tests/ManagedCode.Storage.BrowserWasmHost/Pages/StoragePlayground.razor index b74fc12..57a81f0 100644 --- a/Tests/ManagedCode.Storage.BrowserWasmHost/Pages/StoragePlayground.razor +++ b/Tests/ManagedCode.Storage.BrowserWasmHost/Pages/StoragePlayground.razor @@ -133,6 +133,13 @@ MimeType = "application/octet-stream" }); + if (result.IsFailed) + { + largeOutput = $"generated:{stream.Position}"; + status = $"large-save-failed:{result.Problem?.Detail}"; + return; + } + if (!stream.IsCompleted) { largeOutput = $"generated:{stream.Position}"; @@ -144,7 +151,7 @@ var crc = stream.CompletedCrc; largeOutput = $"expected:{length}:{crc}"; - status = result.IsSuccess ? $"large-saved:{length}:{crc}" : $"large-save-failed:{result.Problem?.Detail}"; + status = $"large-saved:{length}:{crc}"; Logger.LogInformation("Completed browser storage save for {FileName} ({Bytes} bytes, crc {Crc}) in {ElapsedMilliseconds} ms.", resolvedFileName, length,