# HG changeset patch # User Chris Cannam # Date 1745340588 -3600 # Tue Apr 22 17:49:48 2025 +0100 # Node ID 60169b038e757d16e37b83592a86a1f328cae02c # Parent f8ef248a0aad85f99693991b1025393baf36df5e Retry truncated reads unless they are truncated to zero - they can be a consequence of reading into a sparse file hole, which in Poly/ML results in an early return. The Basis spec guarantees the call will block until at least one byte is available but not until the whole read is complete. diff --git a/backing-file-fn.sml b/backing-file-fn.sml --- a/backing-file-fn.sml +++ b/backing-file-fn.sml @@ -89,25 +89,39 @@ fun size (F { extent, ... } : file) = Position.toInt (Position.quot (! extent, Position.fromInt ARG.Pack.bytesPerElem)) - - fun read { file = F { setPosIn, readVec, inPos, ... }, + + fun read { file = F { setPosIn, readVec, inPos, extent, ... }, start : int, count : int } : ARG.V.vector IoResult.result = - let val bytesPerElem = ARG.Pack.bytesPerElem + let fun readVecWithRetry' n + : Word8Vector.vector list = + let val v = readVec n + val obtained = Word8Vector.length v + in + if obtained = 0 orelse obtained >= n + then [v] + else [v] @ readVecWithRetry' (n - obtained) + end + fun readVecWithRetry n = + case readVecWithRetry' n of + [v] => v + | vv => Word8Vector.concat vv + val bytesPerElem = ARG.Pack.bytesPerElem val startPosition = Position.fromInt start * Position.fromInt bytesPerElem val () = if startPosition <> !inPos then setPosIn startPosition else () val countBytes = count * bytesPerElem - val raw = readVec countBytes + val raw = readVecWithRetry countBytes val bytesRead = Word8Vector.length raw val () = inPos := startPosition + Position.fromInt bytesRead in if bytesRead < countBytes - then IoResult.ERROR ("EOF during read") + then (SignalBitsLog.warn (fn () => ["BackingFileFn.read: EOF during read with start = %1, count = %2 [bytesPerElem = %3] after %4 of %5 bytes read [extent = %6]", SignalBitsLog.I start, SignalBitsLog.I count, SignalBitsLog.I ARG.Pack.bytesPerElem, SignalBitsLog.I bytesRead, SignalBitsLog.I countBytes, SignalBitsLog.I (Position.toInt (! extent))]); + IoResult.ERROR ("EOF during read")) else IoResult.OK (ARG.V.tabulate (count, fn i => ARG.Pack.subVec (raw, i))) @@ -137,7 +151,8 @@ else () in if written < countBytes - then IoResult.ERROR ("Truncated write") + then (SignalBitsLog.warn (fn () => ["BackingFileFn.write: Write truncated with start = %1, count = %2 [bytesPerElem = %3] after %4 of %5 bytes written", SignalBitsLog.I start, SignalBitsLog.I count, SignalBitsLog.I ARG.Pack.bytesPerElem, SignalBitsLog.I written, SignalBitsLog.I countBytes]); + IoResult.ERROR ("Truncated write")) else IoResult.OK () end handle ex => @@ -187,7 +202,7 @@ forFile : string option, extension : string option } - + fun new (arg : new_args) = let val filename = FileMisc.tempFile arg val () = SignalBitsLog.info (fn () => ["TempBackingFileFn.new: using temp file \"%1\"", filename]) diff --git a/backing-file.sig b/backing-file.sig --- a/backing-file.sig +++ b/backing-file.sig @@ -40,7 +40,7 @@ } val new : new_args -> file IoResult.result - + end (** BACKED_VECTOR stores a vector to some representation (nominally a