Browse Source

LZMACompressor: Fix race in NoMoreInput check.

Previously, it checked if the input buffer was empty and then if NoMoreInput=True. That's backwards; the input buffer could become non-empty in between. If that happened, compression could end with that remaining input unprocessed, leading to a "Setup files are corrupted" error.

It's likely nobody ever ran into this, though. I could only reproduce it by adding a Sleep between the input buffer and NoMoreInput checks, and a Sleep at the end of StartEncode.

Jordan Russell 10 months ago
parent
commit
f41bfe10b5

+ 20 - 3
Projects/Src/Compression.LZMACompressor.pas

@@ -559,8 +559,17 @@ begin
       LimitedSize := Size;
       LimitedSize := Size;
     if AWrite then
     if AWrite then
       Bytes := RingBufferWrite(FShared.OutputBuffer, P^, LimitedSize)
       Bytes := RingBufferWrite(FShared.OutputBuffer, P^, LimitedSize)
-    else
+    else begin
+      if FShared.NoMoreInput then begin
+        { If NoMoreInput=True and *then* we see that the input buffer is
+          empty (ordering matters!), we know that all input has been
+          processed and that the input buffer will stay empty }
+        MemoryBarrier;
+        if FShared.InputBuffer.Count = 0 then
+          Break;
+      end;
       Bytes := RingBufferRead(FShared.InputBuffer, P^, LimitedSize);
       Bytes := RingBufferRead(FShared.InputBuffer, P^, LimitedSize);
+    end;
     if Bytes = 0 then begin
     if Bytes = 0 then begin
       if AWrite then begin
       if AWrite then begin
         { Output buffer full; wait for the main thread to flush it }
         { Output buffer full; wait for the main thread to flush it }
@@ -571,8 +580,6 @@ begin
       end
       end
       else begin
       else begin
         { Input buffer empty; wait for the main thread to fill it }
         { Input buffer empty; wait for the main thread to fill it }
-        if FShared.NoMoreInput then
-          Break;
         Result := WakeMainAndWaitUntil(FEvents.WorkerWaitingOnInputEvent,
         Result := WakeMainAndWaitUntil(FEvents.WorkerWaitingOnInputEvent,
           FEvents.EndWaitOnInputEvent);
           FEvents.EndWaitOnInputEvent);
         if Result <> S_OK then
         if Result <> S_OK then
@@ -1072,6 +1079,11 @@ procedure TLZMACompressor.DoFinish;
 begin
 begin
   StartEncode;
   StartEncode;
 
 
+  { Ensure prior InputBuffer updates are made visible before setting
+    NoMoreInput. (This isn't actually needed right now because there's
+    already a full barrier inside RingBufferWrite. But that's an
+    implementation detail.) }
+  MemoryBarrier;
   FShared.NoMoreInput := True;
   FShared.NoMoreInput := True;
   while not FEncodeFinished do begin
   while not FEncodeFinished do begin
     SatisfyWorkerWaitOnInput;
     SatisfyWorkerWaitOnInput;
@@ -1094,6 +1106,11 @@ begin
       [FShared.EncodeResult]);
       [FShared.EncodeResult]);
   end;
   end;
 
 
+  { Encoding was successful; verify that all input was consumed }
+  if FShared.InputBuffer.Count <> 0 then
+    LZMAInternalErrorFmt('Finish: Input buffer is not empty (%d)',
+      [FShared.InputBuffer.Count]);
+
   FEncodeStarted := False;
   FEncodeStarted := False;
 end;
 end;
 
 

+ 9 - 3
Projects/Src/Compression.LZMACompressor/islzma/islzma_exe.c

@@ -169,6 +169,15 @@ static HRESULT FillBuffer(const BOOL AWrite, void *Data, size_t Size,
 		if (AWrite) {
 		if (AWrite) {
 			Bytes = RingBufferWrite(&FShared->OutputBuffer, P, LimitedSize);
 			Bytes = RingBufferWrite(&FShared->OutputBuffer, P, LimitedSize);
 		} else {
 		} else {
+			if (FShared->NoMoreInput) {
+				/* If NoMoreInput=True and *then* we see that the input buffer is
+				   empty (ordering matters!), we know that all input has been
+				   processed and that the input buffer will stay empty */
+				MemoryBarrier();
+				if (FShared->InputBuffer.Count == 0) {
+					break;
+				}
+			}
 			Bytes = RingBufferRead(&FShared->InputBuffer, P, LimitedSize);
 			Bytes = RingBufferRead(&FShared->InputBuffer, P, LimitedSize);
 		}
 		}
 		if (Bytes == 0) {
 		if (Bytes == 0) {
@@ -182,9 +191,6 @@ static HRESULT FillBuffer(const BOOL AWrite, void *Data, size_t Size,
 				}
 				}
 			} else {
 			} else {
 				/* Input buffer empty; wait for the main thread to fill it */
 				/* Input buffer empty; wait for the main thread to fill it */
-				if (FShared->NoMoreInput) {
-					break;
-				}
 				Result = WakeMainAndWaitUntil(
 				Result = WakeMainAndWaitUntil(
 					THandle32ToHandle(FEvents->WorkerWaitingOnInputEvent),
 					THandle32ToHandle(FEvents->WorkerWaitingOnInputEvent),
 					THandle32ToHandle(FEvents->EndWaitOnInputEvent));
 					THandle32ToHandle(FEvents->EndWaitOnInputEvent));