RAR5 (and maybe others) async methods are different#1203
RAR5 (and maybe others) async methods are different#1203adamhathcock merged 6 commits intomasterfrom
Conversation
|
@copilot see if you have enough to make RAR tests that exercise fully the Unpack5Async code path |
|
@adamhathcock I've opened a new pull request, #1205, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR refactors RAR unpacking to better align async and sync implementations (motivated by #1201), primarily by moving large async implementations into dedicated *.Async.cs / *_async.cs partials and adjusting some unpack logic in the RAR5 path.
Changes:
- Split multiple inline async unpack methods out of existing “cpp” translation units into new async partial-class files for both
UnpackV2017andUnpackV1. - Refactored some RAR5 (v2017) table/header reading helpers to use shared instance fields rather than passing
refstate around. - Updated RAR5 unpack logic to use the 32-bit bitreader and correct copy behavior in fragmented window scenarios.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 39 comments.
Show a summary per file
| File | Description |
|---|---|
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack_cpp.cs | Minor async signature cleanup (Task/CancellationToken using directives). |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_cpp.cs | Removes embedded async methods and refactors block/table helpers to use instance fields. |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack50_async.cs | Adds dedicated async RAR5 unpack implementation and async block/table readers. |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_cpp.cs | Removes embedded async methods. |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs | Adds dedicated async RAR2.x unpack implementation. |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_cpp.cs | Removes embedded async methods. |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs | Adds dedicated async RAR1.5 unpack implementation. |
| src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs | Normalizes async method signatures and CancellationToken usage. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.cs | Removes embedded async routines. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs | Adds dedicated async routines for RAR5 unpack in v1 implementation. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.cs | Removes embedded async routines. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs | Adds dedicated async routines for RAR2.x unpack in v1 implementation. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.cs | Removes embedded async routines. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs | Adds dedicated async routines for RAR1.5 unpack in v1 implementation. |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs | Removes async entrypoints from the main file (moved to async partial). |
| src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs | Adds async entrypoints and async implementations for v1 unpacking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) | ||
| { | ||
| oldUnpWriteBuf(); | ||
| if (suspended) | ||
| { | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
unpack15Async flushes using oldUnpWriteBuf() (sync Stream.Write) instead of the available async variant. This blocks the async extraction path and ignores the provided CancellationToken. Use await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false) here.
| if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) | ||
| { | ||
| UnpWriteBuf(); | ||
| if (destUnpSize < 0) | ||
| { | ||
| return; | ||
| } | ||
| if (suspended) | ||
| { | ||
| FileExtracted = false; | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
Inside Unpack29Async, the async code path flushes via UnpWriteBuf() (sync Stream.Write) even though UnpWriteBufAsync(...) is implemented just below and a CancellationToken is available. Consider switching this flush to await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false) so async extraction doesn’t block and supports async-only write streams.
| if (((WriteBorder - UnpPtr) & MaxWinMask) < MAX_LZ_MATCH + 3 && WriteBorder != UnpPtr) | ||
| { | ||
| await UnpWriteBufAsync(cancellationToken); | ||
| if (WrittenFileSize > DestUnpSize) | ||
| { | ||
| return; | ||
| } |
There was a problem hiding this comment.
These awaits don’t use .ConfigureAwait(false) (await UnpWriteBufAsync(...)) while the rest of the async implementation consistently does. For library code this can cause unnecessary context capture. Consider using await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false) here (and at the final flush).
| continue; | ||
| } | ||
| } | ||
| await UnpWriteBufAsync(cancellationToken); |
There was a problem hiding this comment.
Final flush uses await UnpWriteBufAsync(cancellationToken); without .ConfigureAwait(false), which is inconsistent with the rest of the async code here. Consider adding .ConfigureAwait(false) to avoid capturing a synchronization context in library code.
| await UnpWriteBufAsync(cancellationToken); | |
| await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); |
| if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) | ||
| { | ||
| UnpWriteBuf20(); | ||
| if (Suspended) | ||
| { | ||
| return; | ||
| } | ||
| } |
There was a problem hiding this comment.
Unpack20Async flushes output via UnpWriteBuf20() (sync Stream.Write) even though this is the async code path and a CancellationToken is available. This defeats async I/O and can break with streams that only support async writes. Use await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false) here so writes are truly async and cancellable.
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) | ||
| { | ||
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
These 'if' statements can be combined.
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 16) | |
| { | |
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | |
| { | |
| return false; | |
| } | |
| if ( | |
| !Inp.ExternalBuffer | |
| && Inp.InAddr > ReadTop - 16 | |
| && !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false) | |
| ) | |
| { | |
| return false; |
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) | ||
| { | ||
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
These 'if' statements can be combined.
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 7) | |
| { | |
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | |
| { | |
| return false; | |
| } | |
| if (!Inp.ExternalBuffer | |
| && Inp.InAddr > ReadTop - 7 | |
| && !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | |
| { | |
| return false; |
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) | ||
| { | ||
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
These 'if' statements can be combined.
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25) | |
| { | |
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | |
| { | |
| return false; | |
| } | |
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 25 && | |
| !await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | |
| { | |
| return false; |
| if (!Inp.ExternalBuffer && Inp.InAddr > ReadTop - 5) | ||
| { | ||
| if (!await UnpReadBufAsync(cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
These 'if' statements can be combined.
Yet the tests still pass. Probably need better tests
#1201