Skip to content

JCRVLT-851: Remove package tmp files through closewatcher - #434

Draft
kwin wants to merge 9 commits into
masterfrom
bugfix/remove-package-tmp-files-through-closewatcher
Draft

kwin wants to merge 9 commits into
masterfrom
bugfix/remove-package-tmp-files-through-closewatcher

Conversation

@kwin

@kwin kwin commented Sep 27, 2026

Copy link
Copy Markdown
Member

No description provided.

@kwin
kwin requested a review from joerghoh September 27, 2026 09:27
@kwin
kwin marked this pull request as draft September 27, 2026 09:33
@kwin

kwin commented Sep 27, 2026

Copy link
Copy Markdown
Member Author

This requires more work as registering the same object as closeable is not possible, as this prevents the phantomreference to be GCed in the first place....

Previously just the JarFile has been closed leading to orphan temp
files. Introduce a new Closer class which is called from CloseWatcher
taking care of both cleanup operations.
@kwin
kwin force-pushed the bugfix/remove-package-tmp-files-through-closewatcher branch from a16332a to bb574f1 Compare September 27, 2026 09:59
@kwin

kwin commented Sep 27, 2026

Copy link
Copy Markdown
Member Author

This requires more work as registering the same object as closeable is not possible, as this prevents the phantomreference to be GCed in the first place....

Fixed by extracting the close operations to a dedicated class.
Now just need to add some more test assertions to ArchiveTest.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved moderate cleanup issues affect partially opened JARs and temporary files during failures or JVM exit.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
What changed in this PR

Moves temporary ZIP cleanup to CloseWatcher-managed archive lifecycles.

Changes:

  • Adds watcher-based cleanup for ZIP and NIO archives.
  • Removes direct temporary-file cleanup from JcrPackageImpl.
  • Updates archive documentation and removes the regression test.
File Summary Findings
vault-core/​src/​test/​java/​org/​apache/​jackrabbit/​vault/​packaging/​impl/​JcrPackageImplTempFileTest.java Removes temporary-file cleanup regression coverage. —
vault-core/​src/​main/​java/​org/​apache/​jackrabbit/​vault/​packaging/​impl/​JcrPackageImpl.java Creates temporary package archives. Two moderate issues: cleanup gaps during copy/construction and possible retention until JVM exit.
vault-core/​src/​main/​java/​org/​apache/​jackrabbit/​vault/​fs/​io/​ZipNioArchive.java Adds watcher-based NIO ZIP cleanup. —
vault-core/​src/​main/​java/​org/​apache/​jackrabbit/​vault/​fs/​io/​ZipArchive.java Adds watcher-based JAR cleanup. Moderate issue: partially opened JARs may remain unclosed after metadata-scan failure.
vault-core/​src/​main/​java/​org/​apache/​jackrabbit/​vault/​fs/​io/​Archive.java Documents temporary archive close requirements. —

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ZipArchive.java Outdated
Comment on lines 324 to 331
File tmpFile = File.createTempFile("vaultpack", ".zip");
// make sure the temp file is removed at the latest when the JVM terminates,
// in case it is never explicitly closed via ZipArchive.close()/ZipVaultPackage.close()
tmpFile.deleteOnExit();
try {
Binary bin = getData().getBinary();
try (FileOutputStream out = FileUtils.openOutputStream(tmpFile);
InputStream in = bin.getStream()) {
IOUtils.copy(in, out);
} finally {
bin.dispose();
}
pack = new ZipVaultPackage(tmpFile, true);
} catch (RepositoryException | IOException | RuntimeException e) {
FileUtils.deleteQuietly(tmpFile);
throw e;
Binary bin = getData().getBinary();
try (FileOutputStream out = FileUtils.openOutputStream(tmpFile);
InputStream in = bin.getStream()) {
IOUtils.copy(in, out);
} finally {
bin.dispose();
}
@kwin kwin changed the title Bugfix/remove package tmp files through closewatcher JCRVLT-851: Remove package tmp files through closewatcher Sep 28, 2026
@kwin
kwin force-pushed the bugfix/remove-package-tmp-files-through-closewatcher branch from 4a20d3c to da9a608 Compare September 28, 2026 08:21
@kwin
kwin requested a lite review from Copilot September 28, 2026 08:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Clear jar after closing to allow reopening

vault-core/​src/​main/​java/​org/​apache/​jackrabbit/​vault/​fs/​io/​ZipArchive.java:236

The new close path closes the JarFile through Closer but never clears the jar field. Consequently, a later open() still returns at the jar != null guard and subsequent reads use the already-closed JarFile; the old implementation reset jar to null. Clear jar when closing so the archive cannot appear open after close and can be reopened consistently.

Comment on lines +324 to +335
Binary bin = getData().getBinary();
File tmpFile = File.createTempFile("vaultpack", ".zip");
// make sure the temp file is removed at the latest when the JVM terminates,
// in case it is never explicitly closed via ZipArchive.close()/ZipVaultPackage.close()
tmpFile.deleteOnExit();
try {
Binary bin = getData().getBinary();
try (FileOutputStream out = FileUtils.openOutputStream(tmpFile);
InputStream in = bin.getStream()) {
IOUtils.copy(in, out);
} finally {
bin.dispose();
}
pack = new ZipVaultPackage(tmpFile, true);
} catch (RepositoryException | IOException | RuntimeException e) {
FileUtils.deleteQuietly(tmpFile);
try (FileOutputStream out = FileUtils.openOutputStream(tmpFile);
InputStream in = bin.getStream()) {
IOUtils.copy(in, out);
} catch (IOException e) {
tmpFile.delete();
throw e;
} finally {
bin.dispose();
}
pack = new ZipVaultPackage(tmpFile, true);
@kwin
kwin force-pushed the bugfix/remove-package-tmp-files-through-closewatcher branch from da9a608 to 87f4eb2 Compare September 28, 2026 08:35
@kwin
kwin requested a lite review from Copilot September 28, 2026 11:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Review effort: Lite
Findings: 3 Medium severity

Open (3)

throw new IOException("Error while reading stream", e);
}
} else {
File tmpFile = File.createTempFile("vaultpack", ".zip");
@kwin
kwin requested a lite review from Copilot September 28, 2026 14:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 324 to 339
@@ -334,8 +333,8 @@ protected VaultPackage getPackage(boolean forceFileArchive) throws RepositoryExc
bin.dispose();
}
pack = new ZipVaultPackage(tmpFile, true);
} catch (RepositoryException | IOException | RuntimeException e) {
FileUtils.deleteQuietly(tmpFile);
} catch (IOException e) {
tmpFile.delete();
throw e;
}
Comment on lines 208 to +219
@Override
public void close() {
if (zipFileSystem != null) {
if (watcher != null) {
CloseWatcher.unregister(watcher);
}
if (watcher != null) {
try {
zipFileSystem.close();
} catch (IOException e) {
log.warn("Error during close.", e);
watcher.getCloseable().close();
} catch (Exception e) {
// should not happen
}
CloseWatcher.unregister(watcher);
zipFileSystem = null;
}
if (deleteAtClose) {
try {
Files.delete(path);
} catch (IOException e) {
log.warn("Could not delete " + path, e);
}
Comment on lines +45 to +50
new ZipNioArchive(tmpFile, true);
System.gc();
Thread.sleep(100);
// Now dump unclosed archives
assertTrue("Couldn't find unclosed archives", AbstractArchive.dumpUnclosedArchives());
assertFalse("Temp file should have been deleted but still exists", Files.exists(tmpFile));
Comment thread vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/ZipArchive.java Outdated
Comment thread vault-core/src/test/java/org/apache/jackrabbit/vault/fs/io/ZipNioArchiveTest.java Outdated
kwin and others added 4 commits September 28, 2026 17:26
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@kwin
kwin force-pushed the bugfix/remove-package-tmp-files-through-closewatcher branch from f7842a7 to e9c4be2 Compare September 28, 2026 15:30
@kwin
kwin requested a lite review from Copilot September 28, 2026 15:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 2 High severity · 10 Medium severity

Open (12)
Resolved since last review (4)

Comment on lines 324 to 343
@@ -334,8 +334,11 @@ protected VaultPackage getPackage(boolean forceFileArchive) throws RepositoryExc
bin.dispose();
}
pack = new ZipVaultPackage(tmpFile, true);
} catch (RepositoryException | IOException | RuntimeException e) {
FileUtils.deleteQuietly(tmpFile);
} catch (IOException e) {
tmpFile.delete();
throw e;
} catch (RepositoryException e) {
tmpFile.delete();
throw e;
}
Comment on lines +230 to +239
if (watcher != null) {
try {
watcher.getCloseable().close();
} catch (Exception e) {
// should not happen
}
if (file != null && isTempFile) {
FileUtils.deleteQuietly(file);
CloseWatcher.unregister(watcher);
watcher = null;
jar = null;
}
Comment on lines +210 to 219
if (watcher != null) {
try {
zipFileSystem.close();
} catch (IOException e) {
log.warn("Error during close.", e);
watcher.getCloseable().close();
} catch (Exception e) {
// should not happen
}
CloseWatcher.unregister(watcher);
watcher = null;
zipFileSystem = null;
}
Comment on lines 324 to +328
File tmpFile = File.createTempFile("vaultpack", ".zip");
// make sure the temp file is removed at the latest when the JVM terminates,
// in case it is never explicitly closed via ZipArchive.close()/ZipVaultPackage.close()
tmpFile.deleteOnExit();
try {
// used as last resort, usually tmpFile is deleted via ZipVaultPackage.close() or the enclosed
// CloseWatcher
tmpFile.deleteOnExit();
Comment on lines +186 to +194
public void testDumpUnclosedArchivesClosesTmpFile() throws IOException, InterruptedException, URISyntaxException {
Path tmpFile = createTempPackage();
new ZipArchive(tmpFile.toFile(), true);
System.gc();
Thread.sleep(100);
// Now dump unclosed archives
assertTrue("Couldn't find unclosed archives", AbstractArchive.dumpUnclosedArchives());
assertFalse("Temp file should have been deleted but still exists", Files.exists(tmpFile));
}
Comment on lines +43 to +51
public void testDumpUnclosedArchivesClosesTmpFile() throws IOException, InterruptedException, URISyntaxException {
Path tmpFile = createTempPackage();
new ZipNioArchive(tmpFile, true);
System.gc();
Thread.sleep(100);
// Now dump unclosed archives
assertTrue("Couldn't find unclosed archives", AbstractArchive.dumpUnclosedArchives());
assertFalse("Temp file should have been deleted but still exists", Files.exists(tmpFile));
}
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
B Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@joerghoh

joerghoh commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

@kwin nice find. Indeed it would render the need for JCRVLT-852 redundant, as the actual leaks could be mitigated to some large extent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants