Fix ClosedFileSystemException on hot redeploy (#11549)

#11548 implemented workaround for JDK-8291712

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Ludovic Orban 2024-03-25 11:13:10 +01:00 committed by GitHub
parent 258a5e04de
commit 13acb1779e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 127 additions and 6 deletions

View File

@ -120,8 +120,15 @@ public class FileSystemPool implements Dumpable
catch (FileSystemAlreadyExistsException fsaee)
{
fileSystem = Paths.get(jarURIRoot).getFileSystem();
if (LOG.isDebugEnabled())
LOG.debug("Using existing FS {}", jarURIRoot);
if (!fileSystem.isOpen())
{
LOG.warn("FileSystem {} of URI {} already exists but is not open (bug JDK-8291712)", fileSystem, uri);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Using existing FS {}", jarURIRoot);
}
}
catch (ProviderNotFoundException pnfe)
{
@ -164,10 +171,32 @@ public class FileSystemPool implements Dumpable
{
if (LOG.isDebugEnabled())
LOG.debug("Ref counter reached 0, closing pooled FS {}", bucket);
IO.close(bucket.fileSystem);
pool.remove(fsUri);
if (listener != null)
listener.onClose(fsUri);
try
{
// If the filesystem's backing file was deleted, re-create it temporarily before closing
// the filesystem to try to work around JDK-8291712.
Path rootOfCreatedPath = null;
if (!Files.exists(bucket.path))
rootOfCreatedPath = createEmptyFileWithParents(bucket.path);
try
{
bucket.fileSystem.close();
}
finally
{
IO.delete(rootOfCreatedPath);
}
// Remove the FS from the pool only if the above code did not throw as if it is
// createEmptyFileWithParents() that threw, there is a chance we could re-create
// that file later on.
pool.remove(fsUri);
if (listener != null)
listener.onClose(fsUri);
}
catch (IOException e)
{
LOG.warn("Unable to close FileSystem {} of URI {} (bug JDK-8291712)", bucket.fileSystem, fsUri, e);
}
}
else
{
@ -183,6 +212,33 @@ public class FileSystemPool implements Dumpable
}
}
private Path createEmptyFileWithParents(Path path) throws IOException
{
Path createdRootPath = null;
if (!Files.exists(path.getParent()))
createdRootPath = createDirWithAllParents(path.getParent());
Files.createFile(path);
if (createdRootPath == null)
createdRootPath = path;
return createdRootPath;
}
private Path createDirWithAllParents(Path path) throws IOException
{
Path parentPath = path.getParent();
if (!Files.exists(parentPath))
{
Path createdRootPath = createDirWithAllParents(parentPath);
Files.createDirectory(path);
return createdRootPath;
}
else
{
Files.createDirectory(path);
return path;
}
}
@ManagedAttribute("The mounted FileSystems")
public Collection<Mount> mounts()
{

View File

@ -0,0 +1,65 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@ExtendWith(WorkDirExtension.class)
public class FileSystemPoolTest
{
public WorkDir workDir;
@ParameterizedTest
@ValueSource(strings = {"example.jar", "a/b/c/d/example.jar"})
public void testCloseResourceFactoryAfterDeletingFileSystemBackingFile(String jarPathString) throws Exception
{
Path srcPath = MavenTestingUtils.getTestResourcePath("example.jar");
Path rootPath = workDir.getEmptyPathDir();
Path jarPath = rootPath.resolve(jarPathString);
Files.createDirectories(jarPath.getParent());
Files.copy(srcPath, jarPath, StandardCopyOption.REPLACE_EXISTING);
ResourceFactory.Closeable rf1 = ResourceFactory.closeable();
Resource resource1 = rf1.newResource(URIUtil.toJarFileUri(jarPath.toUri()));
assertThat(resource1.exists(), is(true));
// delete the jar file before closing the FS
IO.delete(rootPath);
rf1.close();
assertThat(Files.exists(rootPath), is(false));
// re-create the jar file at the exact same location
Files.createDirectories(jarPath.getParent());
Files.copy(srcPath, jarPath, StandardCopyOption.REPLACE_EXISTING);
ResourceFactory.Closeable rf2 = ResourceFactory.closeable();
// Calling ResourceFactory.newResource() again on the same jar would throw
// java.nio.file.ClosedFileSystemException without the workaround for JDK-8291712.
Resource resource2 = rf2.newResource(URIUtil.toJarFileUri(jarPath.toUri()));
assertThat(resource2.exists(), is(true));
rf2.close();
}
}