apache/pulsar · error · IOException
Unable to delete ${file}
Error message
Unable to delete ${file} What it means
FileUtils.deleteFile(File, recurse) deletes a file or directory tree; after recursively deleting children it calls the retrying internal deleteFile(file, 5), and if that still fails it throws IOException 'Unable to delete <abs path>'. Deletion typically fails because something still holds the file open (unlink returns EBUSY/EPERM) or the parent directory lacks write permission.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/nar/FileUtils.java:191
*
* @param files to delete
* @param recurse will recurse
* @throws IOException if issues deleting files
*/
public static void deleteFiles(final Collection<File> files, final boolean recurse) throws IOException {
for (final File file : files) {
FileUtils.deleteFile(file, recurse);
}
}
public static void deleteFile(final File file, final boolean recurse) throws IOException {
final File[] list = file.listFiles();
if (file.isDirectory() && recurse && list != null) {
FileUtils.deleteFiles(Arrays.asList(list), recurse);
}
//now delete the file itself regardless of whether it is plain file or a directory
if (!FileUtils.deleteFile(file, 5)) {
throw new IOException("Unable to delete " + file.getAbsolutePath());
}
}
public static void sleepQuietly(final long millis) {
try {
Thread.sleep(millis);
} catch (final InterruptedException ex) {
/* do nothing */
}
}
public static boolean mayBeANarArchive(File jarFile) {
try (ZipFile zipFile = new ZipFile(jarFile);) {
ZipEntry entry = zipFile.getEntry("META-INF/bundled-dependencies");
if (entry == null || !entry.isDirectory()) {
log.info().attr("jarFile", jarFile)
.log("Jar file does not contain META-INF/bundled-dependencies,"
+ " it is not a NAR file");View on GitHub (pinned to 820761864e)
Solutions
- Find and stop the process holding the file open (lsof | grep <path> on Linux; handle.exe on Windows).
- Ensure the parent directory is writable (chmod/chown).
- Pass recurse=true for non-empty directories.
- Retry after the holder releases the lock — the internal code already retries 5 times.
Example fix
// before
FileUtils.deleteFile(new File("/tmp/nar/my.nar-unpacked")); // non-empty dir
// after
FileUtils.deleteFile(new File("/tmp/nar/my.nar-unpacked"), true); // recursive Defensive patterns
Strategy: retry
Validate before calling
if (file.exists() && !file.getParentFile().canWrite()) {
throw new IllegalStateException("Parent not writable, delete will fail: " + file.getParent());
} Try / catch
try {
FileUtils.deleteFile(file, true);
} catch (IOException e) {
if (e.getMessage().startsWith("Unable to delete")) {
// commonly a file lock: schedule retry after holders release
log.warn("Delete failed for {}; likely locked by another process", file, e);
return;
}
throw e;
} Prevention
- Close streams/locks on NAR jars and unpacked files before deleting them
- On Windows, exclude service directories from antivirus/indexer locking
- Only call deleteFile on directories with recurse=true
- Run deletion under the same user that created the files
When it happens
Trigger: deleteFile called on a path that is locked/open by another process, is a non-empty directory without recurse=true, or whose parent directory is not writable by the process user.
Common situations: Windows file locks (antivirus/indexers holding NAR jars), a running JVM keeping an unpacked jar mapped, deleting NAR unpack dirs while the broker still has them loaded, or root-owned leftovers.
Related errors
- Failed to read decryption key from ${keyUri}
- ${dir} is not a directory
- ${dir} could not be created
- ${dir} directory does not have read/write privilege
- ${dir} directory does not have read privilege
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/117e80b6b866904e.
Report an issue: GitHub.