12 KiB
Intensive Analyse: Pausen zwischen Pack-Entpackungen (10–15 Sekunden)
Nur Analyse – keine Code-Änderungen.
1. Problem
Nach dem Entpacken eines Packs (z.B. 3 Parts einer Serie) passiert ca. 10–15 Sekunden lang scheinbar nichts, bevor das nächste Pack mit dem Entpacken beginnt.
2. Steuerungslogik: Ein Slot für alle Packs
- Nur ein Pack darf gleichzeitig Post-Processing (inkl. Entpacken) machen.
- Steuerung:
acquirePostProcessSlot(packageId)/releasePostProcessSlot()indownload-manager.ts. - Weitere Packs warten in
packagePostProcessWaitersund kommen erst dran, wenn der aktive Task im finally-BlockreleasePostProcessSlot()aufruft.
private async acquirePostProcessSlot(packageId: string): Promise<void> {
const maxConcurrent = 1;
// ...
}
private releasePostProcessSlot(): void {
// ...
}
- Der Slot wird erst freigegeben, wenn die gesamte
runPackagePostProcessing-Task-Funktion durch ist – genauer: wenn ihr finally-Block läuft (dortreleasePostProcessSlot()). Alles, was vorher im gleichen Task synchron (await) läuft, blockiert den Slot und damit das nächste Pack.
3. Zwei relevante Code-Pfade
3.1 Pfad A: Hybrid-Extract (Pack noch nicht fertig)
- Bedingung:
!allDone && settings.hybridExtract && autoExtract && failed === 0 && success > 0. - Es werden nur die bereits fertigen Archive des Packs entpackt (
onlyArchives: readyArchives), mitskipPostCleanup: true(kein Post-Cleanup im Extractor). - Ablauf:
handlePackagePostProcessing→runHybridExtraction.await extractPackageArchives(..., onlyArchives, skipPostCleanup: true).- Direkt danach (im gleichen Callstack, vor Rückkehr):
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg)(Zeile 6490). - Dann return aus
handlePackagePostProcessing→ finally →releasePostProcessSlot().
Folge: Im Hybrid-Pfad blockiert Auto-Rename den Slot. Solange Rename läuft (rekursives Scannen + Umbenennen), kann das nächste Pack nicht starten. Das kann gut 10–15 Sekunden ausmachen.
3.2 Pfad B: Finales Post-Processing (Pack komplett, alle Items fertig)
- Bedingung:
allDone(alle Items completed/failed/cancelled). - Es wird das gesamte Pack entpackt (
extractPackageArchivesohneonlyArchives), ohneskipPostCleanup. - Ablauf:
await extractPackageArchives(...)– inklusive allem, was der Extractor danach noch macht (siehe Abschnitt 4).- Status-Updates,
recordPackageHistory(...)(synchron, schnell). void this.runDeferredPostExtraction(...)– wird nicht awaitet; Rename, MKV-Sammlung, Cleanup laufen im Hintergrund.handlePackagePostProcessingkehrt zurück → finally →releasePostProcessSlot().
Folge: Im Final-Pfad blockieren nicht mehr Rename/MKV/Cleanup im Download-Manager den Slot – die sind in runDeferredPostExtraction ausgelagert. Was den Slot aber noch blockiert, ist alles, was innerhalb von extractPackageArchives nach dem eigentlichen Entpacken passiert (Post-Cleanup und ggf. Nested-Extraction im Extractor).
4. Was passiert INNERHALB von extractPackageArchives (Extractor) – und blockiert
Nach dem Durchlauf über alle Kandidaten-Archive folgt im Extractor (extractor.ts) noch:
4.1 Nested-Extraction (Zeilen 2208–2284)
- Wenn
extracted > 0 && !skipPostCleanup && !onlyArchives: Es werden Archive im Zielordner gesucht (findArchiveCandidates(options.targetDir)) und nacheinander entpackt. - Pro nested-Archiv: Entpacken, ggf.
cleanupArchives([nestedArchive], ...). - Kann bei vielen/vollen Archiven deutlich Zeit kosten und den Slot blockieren.
4.2 Post-Cleanup (Zeilen 2286–2328)
- Nur wenn
!options.skipPostCleanup:- cleanupArchives(cleanupSources, cleanupMode): Entfernen/Trash der entpackten Quell-Archive (readdir pro Verzeichnis, ggf. viele
rm/rename). - removeDownloadLinkArtifacts(targetDir): Link-Artefakte im Zielordner entfernen.
- removeSampleArtifacts(targetDir): Rekursives Durchlaufen des kompletten Extract-Ordners, Erkennung von Sample-Dateien/Ordnern, Löschen.
- removeEmptyDirectoryTree(packageDir): Rekursives Auflisten aller Unterordner, dann sortiert leere Ordner von tief nach flach löschen.
- cleanupArchives(cleanupSources, cleanupMode): Entfernen/Trash der entpackten Quell-Archive (readdir pro Verzeichnis, ggf. viele
All das läuft vor dem Return von extractPackageArchives. Erst danach kommt im Download-Manager noch recordPackageHistory und void runDeferredPostExtraction. Der Slot wird also erst nach dem gesamten extractPackageArchives-Lauf (inkl. Nested + Post-Cleanup) freigegeben.
Typische Zeitfresser (10–15 s):
cleanupArchives: viele Dateien/Archive → viele I/O-Ops.removeSampleArtifacts: vollständiger rekursiver Scan des Extract-Ordners.removeEmptyDirectoryTree: rekursives readdir über die ganze Verzeichnisstruktur.- Nested-Extraction: zusätzliches Entpacken und ggf. weiteres Cleanup.
5. Was im Download-Manager NACH dem Extractor noch passiert (Final-Pfad)
- recordPackageHistory: synchron, in-memory + Callback – vernachlässigbar.
- runDeferredPostExtraction: wird mit
voidgestartet, blockiert den Slot nicht. Darin laufen (im Hintergrund):autoRenameExtractedVideoFilescleanupRemainingArchiveArtifacts(bei Hybrid-Szenario/cleanupMode)collectMkvFilesToLibraryapplyPackageDoneCleanup
Diese Schritte verursachen keine Pause mehr zwischen zwei Packs im Final-Pfad, weil der Slot schon vorher freigegeben wird.
6. Zusammenfassung: Wo entstehen die 10–15 Sekunden Pause?
| Szenario | Was blockiert den Slot (Pause bis zum nächsten Pack)? |
|---|---|
| Hybrid-Extract (Pack hat noch offene Items) | await autoRenameExtractedVideoFiles direkt nach extractPackageArchives in runHybridExtraction (Zeile 6490). Rekursives Scannen + Umbenennen aller Video-Dateien. |
| Finales Post-Processing (Pack fertig) | Alles innerhalb von extractPackageArchives: Nested-Extraction (falls vorhanden) + Post-Cleanup (cleanupArchives, removeDownloadLinkArtifacts, removeSampleArtifacts, removeEmptyDirectoryTree). Rekursive Scans und viele I/O-Ops. |
In beiden Fällen ist die Pause also die Zeit vor releasePostProcessSlot() – einmal durch Rename im Manager (Hybrid), einmal durch Post-Cleanup und Nested-Extraction im Extractor (Final).
7. Mögliche Verbesserungen (nur Konzept, keine Änderung)
-
Hybrid-Pfad:
autoRenameExtractedVideoFilesnach dem Hybrid-Extract nicht mehr awaiten, sondern (analog zurunDeferredPostExtraction) im Hintergrund starten und sofort ausrunHybridExtractionzurückkehren. Dann wird der Slot direkt nachextractPackageArchivesfreigegeben; Rename läuft parallel. -
Final-Pfad / Extractor:
Post-Cleanup (und ggf. Nested-Extraction) nicht mehr synchron am Ende vonextractPackageArchivesausführen, sondern:- Entweder: Extractor gibt nach dem letzten „eigentlichen“ Entpacken sofort zurück und eine andere Komponente (z. B. Download-Manager oder eine Queue) übernimmt Cleanup/Nested im Hintergrund; oder
- Extractor bekommt eine Option (z. B.
deferPostCleanup: true), liefert die nötigen Daten (z. B. Liste der zu löschenden Archive) zurück, und der Aufrufer führt Cleanup/Nested asynchron aus.
-
Slot-Logik unverändert:
Ein Slot bleibt sinnvoll, um I/O und CPU beim Entpacken zu bündeln. Durch die Entkopplung der „teuren“ Schritte (Rename, Cleanup, Nested) von der Slot-Holding-Zeit verkürzt sich die Pause zwischen zwei Packs ohne Parallel-Entpacken mehrerer Packs.
8. Relevante Stellen im Code (Orientierung)
- Slot:
acquirePostProcessSlot/releasePostProcessSlot(download-manager.ts, ca. 3761–3804). - Post-Processing-Task:
runPackagePostProcessing→handlePackagePostProcessing(ca. 3806–3854, 6544–6916). - Hybrid:
runHybridExtraction(ca. 6374–6542), inkl.await autoRenameExtractedVideoFiles(6490). - Final:
handlePackagePostProcessingnachextractPackageArchives(6697–6916):recordPackageHistory,void runDeferredPostExtraction, dann return. - Extractor:
extractPackageArchives(extractor.ts, ca. 1880–2353), Nested 2208–2284, Post-Cleanup 2286–2328. - Rename:
autoRenameExtractedVideoFiles(download-manager.ts, 2173–2312), nutztcollectVideoFiles(rekursiv). - MKV/Cleanup:
collectMkvFilesToLibrary(2448),cleanupRemainingArchiveArtifacts(2353),runDeferredPostExtraction(6922–6965).
9. Vergleich: JDownloader (jdownloader-source)
Im JDownloader-Quellcode (z. B. C:\Users\ploet\Desktop\jdownloader-source) ist das Entpacken so aufgebaut, dass pro Pack (3 Parts = 1 Folge) kaum schwere Arbeit nach dem eigentlichen Entpacken im gleichen Queue-Job läuft – deshalb wirkt es „ohne Pause“.
Ablauf bei JDownloader
- Ein Archiv = ein Pack (z. B. 3 RAR-Parts = 1 Archive mit
archive.getArchiveFiles()). - Eine Queue (
ExtractionQueue), ein Job pro Archiv (ExtractionControllerextendsQueueAction). - Pro Job passiert in
ExtractionController.run():extractor.extract(this)– reines Entpacken.extractor.close().- Je nach Exit-Code:
fireEvent(ExtractionEvent.Type.FINISHED)(inkl.FileCreationEvent(NEW_FILES, files)– die Dateiliste kommt vom Extractor, kein rekursives Scannen). - Im finally:
fireEvent(Type.CLEANUP)→archive.onCleanUp(). - Listener bei
CLEANUP:controller.removeArchiveFiles().
Was removeArchiveFiles() bei JDownloader macht
- Holt die bereits bekannten Archive-Dateien:
archive.getArchiveFiles()(die 3 Parts sind dem Archiv von Anfang an zugeordnet). - Löscht nur diese Dateien (z. B.
link.deleteFile(remove)pro Part). - Kein rekursives Durchsuchen von Ordnern, kein
findArchiveCandidates, kein Scannen des Extract-Ordners. - Aufwand: O(Anzahl Parts) Datei-Löschungen, typisch sehr schnell.
Was JDownloader in diesem Pfad nicht macht
- Kein Auto-Rename der entpackten Dateien im Extraction-Queue-Job (LinknameCleaner wird an anderer Stelle für Pfadsegmente genutzt, nicht als Blockierung nach Extract).
- Kein „Collect MKV to Library“ (rekursives Scannen + Verschieben) im gleichen Job.
- Kein
removeSampleArtifacts(rekursiver Scan des Extract-Ordners). - Kein
removeEmptyDirectoryTree(rekursives Auflisten aller Unterordner). - Nested-Archive (Deep-Extraction) werden als neue Archive in die Queue gestellt (
addToQueue(..., newArchive, false)), also separate Jobs, die nacheinander laufen – der aktuelle Job ist sofort fertig.
Warum es sich „flawless“ anfühlt
- Der kritische Pfad pro Pack ist: Entpacken → Event FINISHED → Event CLEANUP → nur die bekannten Archive-Dateien löschen →
run()endet. - Keine rechen- oder I/O-intensiven Schritte (keine rekursiven Scans, kein Rename, keine MKV-Sammlung) im gleichen Queue-Job.
- Das nächste Pack (nächster
ExtractionControllerin der Queue) startet direkt nachrun()return – die spürbare Pause entfällt.
Übertrag auf unser Projekt
- Um ein ähnlich „flüssiges“ Verhalten zu erreichen, sollten alle zeitaufwändigen Schritte (Rename, MKV-Sammlung, Sample-Cleanup, leere Ordner entfernen, ggf. Post-Cleanup im Extractor) nicht den Post-Process-Slot blockieren.
- Konkret: Sie entweder nach
releasePostProcessSlot()im Hintergrund ausführen (wie beim Final-Pfad bereits für Rename/MKV/Cleanup im Manager) oder den Extractor so auslegen, dass er direkt nach dem letzten eigentlichen Entpacken zurückkehrt und Cleanup/Nested in einem separaten, asynchronen Schritt erledigt wird (siehe Abschnitt 7).