Fix parallel extraction false positives
This commit is contained in:
parent
d8535990ae
commit
113b34fadf
@ -651,6 +651,23 @@ export function classifyExtractionError(errorText: string): ExtractErrorCategory
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
export function shouldSerialRetryParallelFailures(
|
||||
extractedCount: number,
|
||||
failedCategories: ExtractErrorCategory[]
|
||||
): boolean {
|
||||
if (failedCategories.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (extractedCount > 0) {
|
||||
return true;
|
||||
}
|
||||
return failedCategories.every((category) =>
|
||||
category === "crc_error"
|
||||
|| category === "wrong_password"
|
||||
|| category === "unknown"
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldFallbackLegacyRarToJvm(
|
||||
archivePath: string,
|
||||
configuredMode: ExtractBackendMode,
|
||||
@ -3001,6 +3018,7 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
||||
let learnedPassword = cachedPackagePassword;
|
||||
let packageNeedsFlatMode = false;
|
||||
const extractedArchives = new Set<string>();
|
||||
const failedArchiveCategories = new Map<string, ExtractErrorCategory>();
|
||||
for (const archivePath of candidates) {
|
||||
if (resumeCompleted.has(archiveNameKey(path.basename(archivePath)))) {
|
||||
extractedArchives.add(archivePath);
|
||||
@ -3201,6 +3219,7 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
||||
}
|
||||
extracted += 1;
|
||||
extractedArchives.add(archivePath);
|
||||
failedArchiveCategories.delete(archivePath);
|
||||
resumeCompleted.add(archiveResumeKey);
|
||||
await writeExtractResumeState(options.packageDir, resumeCompleted, options.packageId);
|
||||
logger.info(`Entpacken erfolgreich: ${path.basename(archivePath)}`);
|
||||
@ -3224,6 +3243,7 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
||||
failed += 1;
|
||||
lastError = errorText;
|
||||
const errorCategory = classifyExtractionError(errorText);
|
||||
failedArchiveCategories.set(archivePath, errorCategory);
|
||||
const hintedError = error as ExtractionErrorWithHints;
|
||||
options.onArchiveFailure?.({
|
||||
archiveName,
|
||||
@ -3329,6 +3349,33 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
|
||||
|
||||
if (abortError) throw new Error("aborted:extract");
|
||||
|
||||
if (failed > 0 && extracted === 0) {
|
||||
const failedArchives = parallelQueue.filter((ap) => !extractedArchives.has(ap) && !resumeCompleted.has(archiveNameKey(path.basename(ap))));
|
||||
const failedCategories = failedArchives.map((archivePath) => failedArchiveCategories.get(archivePath) || "unknown");
|
||||
if (failedArchives.length > 0 && shouldSerialRetryParallelFailures(extracted, failedCategories)) {
|
||||
const categorySummary = [...new Set(failedCategories)].join(",");
|
||||
logger.info(
|
||||
`Serielle Wiederholung nach Parallel-Fehlstart: ${failedArchives.length} Archive werden einzeln wiederholt ` +
|
||||
`(categories=${categorySummary || "unknown"})`
|
||||
);
|
||||
let retryRecovered = 0;
|
||||
for (const archivePath of failedArchives) {
|
||||
if (options.signal?.aborted || noExtractorEncountered) break;
|
||||
try {
|
||||
failed -= 1;
|
||||
await extractSingleArchive(archivePath);
|
||||
retryRecovered += 1;
|
||||
} catch (retryError) {
|
||||
const errText = String(retryError);
|
||||
if (isExtractAbortError(errText)) throw retryError;
|
||||
}
|
||||
}
|
||||
if (retryRecovered > 0) {
|
||||
logger.info(`Serielle Wiederholung nach Parallel-Fehlstart: ${retryRecovered}/${failedArchives.length} Archive erfolgreich entpackt`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Retry failed wrong_password archives serially ──
|
||||
// Parallel UnRAR processes writing to the same target directory can cause
|
||||
// CRC mismatches that are misreported as "Incorrect password".
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
archiveFilenamePasswords,
|
||||
detectArchiveSignature,
|
||||
classifyExtractionError,
|
||||
shouldSerialRetryParallelFailures,
|
||||
findArchiveCandidates,
|
||||
orderExtractorCandidatesForArchive,
|
||||
resolveExtractorBackendModeForArchive,
|
||||
@ -1048,6 +1049,19 @@ describe("extractor", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldSerialRetryParallelFailures", () => {
|
||||
it("keeps serial recovery enabled after mixed parallel results", () => {
|
||||
expect(shouldSerialRetryParallelFailures(1, ["wrong_password"])).toBe(true);
|
||||
expect(shouldSerialRetryParallelFailures(2, ["missing_parts"])).toBe(true);
|
||||
});
|
||||
|
||||
it("only retries a total parallel wipe-out for contention-like failures", () => {
|
||||
expect(shouldSerialRetryParallelFailures(0, ["crc_error", "wrong_password", "unknown"])).toBe(true);
|
||||
expect(shouldSerialRetryParallelFailures(0, ["missing_parts"])).toBe(false);
|
||||
expect(shouldSerialRetryParallelFailures(0, ["unsupported_format", "crc_error"])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("password discovery", () => {
|
||||
it("reports per-archive failures through onArchiveFailure", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-extract-failure-"));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user