60169b038e75 — Chris Cannam a month ago
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.
2 files changed, 23 insertions(+), 8 deletions(-)

M backing-file-fn.sml
M backing-file.sig
M backing-file-fn.sml +22 -7
@@ 89,25 89,39 @@ functor BackingFileFn (ARG : BACKING_FIL
     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 @@ functor BackingFileFn (ARG : BACKING_FIL
                      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 @@ functor TempBackingFileFn (FILE : BACKIN
            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])

          
M backing-file.sig +1 -1
@@ 40,7 40,7 @@ signature TEMP_BACKING_FILE = sig
                     }
              
     val new : new_args -> file IoResult.result
-                        
+
 end
 
 (** BACKED_VECTOR stores a vector to some representation (nominally a