liblzma: Add a new flag LZMA_FAIL_FAST for threaded decoder.

In most cases if the input file is corrupt the application won't
care about the uncompressed content at all. With this new flag
the threaded decoder will return an error as soon as any thread
has detected an error; it won't wait to copy out the data before
the location of the error.

I don't plan to use this in xz to keep the behavior consistent
between single-threaded and multi-threaded modes.
This commit is contained in:
Lasse Collin 2022-04-06 12:39:49 +03:00
parent 64b6d496dc
commit 90621da7f6
3 changed files with 56 additions and 26 deletions

View File

@ -73,7 +73,7 @@ typedef struct {
* *
* Decoder: Bitwise-or of zero or more of the decoder flags: * Decoder: Bitwise-or of zero or more of the decoder flags:
* LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK, * LZMA_TELL_NO_CHECK, LZMA_TELL_UNSUPPORTED_CHECK,
* LZMA_TELL_ANY_CHECK, LZMA_CONCATENATED * LZMA_TELL_ANY_CHECK, LZMA_CONCATENATED, LZMA_FAIL_FAST
*/ */
uint32_t flags; uint32_t flags;
@ -615,6 +615,29 @@ extern LZMA_API(lzma_ret) lzma_microlzma_encoder(
#define LZMA_CONCATENATED UINT32_C(0x08) #define LZMA_CONCATENATED UINT32_C(0x08)
/**
* This flag makes the threaded decoder report errors (like LZMA_DATA_ERROR)
* as soon as they are detected. This saves time when the application has no
* interest in a partially decompressed truncated or corrupt file. Note that
* due to timing randomness, if the same truncated or corrupt input is
* decompressed multiple times with this flag, a different amount of output
* may be produced by different runs, and even the error code might vary.
*
* Without this flag the threaded decoder will provide as much output as
* possible at first and then report the pending error. This default behavior
* matches the single-threaded decoder and provides repeatable behavior
* with truncated or corrupt input. There are a few special cases where the
* behavior can still differ like memory allocation failures (LZMA_MEM_ERROR).
*
* Single-threaded decoders currently ignore this flag.
*
* Support for this flag was added in liblzma 5.3.3alpha. Note that in older
* versions this flag isn't supported (LZMA_OPTIONS_ERROR) even by functions
* that ignore this flag in newer liblzma versions.
*/
#define LZMA_FAIL_FAST UINT32_C(0x20)
/** /**
* \brief Initialize .xz Stream decoder * \brief Initialize .xz Stream decoder
* *

View File

@ -67,14 +67,15 @@
#define LZMA_FILTER_RESERVED_START (LZMA_VLI_C(1) << 62) #define LZMA_FILTER_RESERVED_START (LZMA_VLI_C(1) << 62)
/// Supported flags that can be passed to lzma_stream_decoder() /// Supported flags that can be passed to lzma_stream_decoder(),
/// or lzma_auto_decoder(). /// lzma_auto_decoder(), or lzma_stream_decoder_mt().
#define LZMA_SUPPORTED_FLAGS \ #define LZMA_SUPPORTED_FLAGS \
( LZMA_TELL_NO_CHECK \ ( LZMA_TELL_NO_CHECK \
| LZMA_TELL_UNSUPPORTED_CHECK \ | LZMA_TELL_UNSUPPORTED_CHECK \
| LZMA_TELL_ANY_CHECK \ | LZMA_TELL_ANY_CHECK \
| LZMA_IGNORE_CHECK \ | LZMA_IGNORE_CHECK \
| LZMA_CONCATENATED ) | LZMA_CONCATENATED \
| LZMA_FAIL_FAST )
/// Largest valid lzma_action value as unsigned integer. /// Largest valid lzma_action value as unsigned integer.

View File

@ -300,6 +300,10 @@ struct lzma_stream_coder {
/// Stream Padding is a multiple of four bytes. /// Stream Padding is a multiple of four bytes.
bool concatenated; bool concatenated;
/// If true, we will return any errors immediately instead of first
/// producing all output before the location of the error.
bool fail_fast;
/// When decoding concatenated Streams, this is true as long as we /// When decoding concatenated Streams, this is true as long as we
/// are decoding the first Stream. This is needed to avoid misleading /// are decoding the first Stream. This is needed to avoid misleading
@ -711,13 +715,12 @@ read_output_and_wait(struct lzma_stream_coder *coder,
coder->pending_error coder->pending_error
= coder->thread_error; = coder->thread_error;
// FIXME? Add a flag to do this conditionally? // If LZMA_FAIL_FAST was used, report errors
// That way errors would get reported to the // from worker threads immediately.
// application without a delay. if (coder->fail_fast) {
// if (coder->fast_errors) { ret = coder->thread_error;
// ret = coder->thread_error; break;
// break; }
// }
} }
// Check if decoding of the next Block can be started. // Check if decoding of the next Block can be started.
@ -1690,22 +1693,24 @@ stream_decode_mt(void *coder_ptr, const lzma_allocator *allocator,
break; break;
case SEQ_ERROR: case SEQ_ERROR:
// Let the application get all data before the point where if (!coder->fail_fast) {
// the error was detected. This matches the behavior of // Let the application get all data before the point
// single-threaded use. // where the error was detected. This matches the
// // behavior of single-threaded use.
// FIXME? Some errors (LZMA_MEM_ERROR) don't get here, //
// they are returned immediately. Thus in rare cases the // FIXME? Some errors (LZMA_MEM_ERROR) don't get here,
// output will be less than in single-threaded mode. But // they are returned immediately. Thus in rare cases
// maybe this doesn't matter much in practice. // the output will be less than in the single-threaded
return_if_error(read_output_and_wait(coder, allocator, // mode. Maybe this doesn't matter much in practice.
out, out_pos, out_size, return_if_error(read_output_and_wait(coder, allocator,
NULL, true, &wait_abs, &has_blocked)); out, out_pos, out_size,
NULL, true, &wait_abs, &has_blocked));
// We get here only if the error happened in the main thread, // We get here only if the error happened in the main
// for example, unsupported Block Header. // thread, for example, unsupported Block Header.
if (!lzma_outq_is_empty(&coder->outq)) if (!lzma_outq_is_empty(&coder->outq))
return LZMA_OK; return LZMA_OK;
}
return coder->pending_error; return coder->pending_error;
@ -1900,6 +1905,7 @@ stream_decoder_mt_init(lzma_next_coder *next, const lzma_allocator *allocator,
coder->tell_any_check = (options->flags & LZMA_TELL_ANY_CHECK) != 0; coder->tell_any_check = (options->flags & LZMA_TELL_ANY_CHECK) != 0;
coder->ignore_check = (options->flags & LZMA_IGNORE_CHECK) != 0; coder->ignore_check = (options->flags & LZMA_IGNORE_CHECK) != 0;
coder->concatenated = (options->flags & LZMA_CONCATENATED) != 0; coder->concatenated = (options->flags & LZMA_CONCATENATED) != 0;
coder->fail_fast = (options->flags & LZMA_FAIL_FAST) != 0;
coder->first_stream = true; coder->first_stream = true;
coder->out_was_filled = false; coder->out_was_filled = false;