vaadin / multiplatform-runtime

4 stars 1 forks source link

FileSystemNotFoundException thrown intermittently when loading static resources using MPR using Spring Boot uber-jar #119

Closed pogocode closed 1 year ago

pogocode commented 1 year ago

FileSystemNotFoundExceptions are thrown intermittently when loading static resources using MPR. This appears to be due to the way that two different classes are interacting with the same FileSystems and closing them whilst in use by the other.

When loading static resources, both of the following use a reference counting technique to decide whether to close the underlying FileSystem:

com.vaadin.flow.server.StaticFileServer#resourceIsDirectory com.vaadin.server.VaadinServlet#resourceIsDirectory

When running simultaneous requests for legacy and flow static resources this can be catastrophic as the FileSystem (in this case the uber jar) gets closed by one of the above whilst the other is using it.

Minimal Reproducible Example

See the attached minimal Spring Boot with MPR that I've made based on the supplied MPR demo:

mpr-demo-mpr-6-file-system-not-found.zip

Build and run using the uber-jar:

mvn clean install -Pproduction
java -jar target/mprdemo-1.0-SNAPSHOT.jar

In a separate terminal run the following, which will make concurrent requests for legacy and flow static resources (on 6 different threads, 3 for legacy and 3 for flow):

cd static-file-concurrency-issue 
./invoke-concurrent-requests.sh

Keep an eye on the terminal output for the Spring boot application, you should see plenty of exceptions similar to:

15:26:43.300 [http-nio-8080-exec-4] WARN  o.a.c.c.C.[.[.[.[.v.mpr.MprServlet] - Servlet.service() for servlet [com.vaadin.mpr.MprServlet] in context with path [] threw exception
java.nio.file.FileSystemNotFoundException: null
    at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:156)
    at java.base/java.nio.file.FileSystems.getFileSystem(FileSystems.java:235)
    at com.vaadin.server.VaadinServlet.closeFileSystem(VaadinServlet.java:1416)
    at com.vaadin.server.VaadinServlet.resourceIsDirectory(VaadinServlet.java:1326)
    at com.vaadin.server.VaadinServlet.isAllowedVAADINResourceUrl(VaadinServlet.java:1254)
    at com.vaadin.server.VaadinServlet.serveStaticResourcesInVAADIN(VaadinServlet.java:865)
    at com.vaadin.mpr.core.AbstractMprServlet.serveStaticResources(AbstractMprServlet.java:241)
    at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:457)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)

and

15:26:43.694 [http-nio-8080-exec-9] WARN  o.a.c.c.C.[.[.[/].[springServlet] - Servlet.service() for servlet [springServlet] threw exception
java.nio.file.FileSystemNotFoundException: null
    at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:156)
    at java.base/java.nio.file.FileSystems.getFileSystem(FileSystems.java:235)
    at com.vaadin.flow.server.StaticFileServer.getFileSystem(StaticFileServer.java:180)
    at com.vaadin.flow.server.StaticFileServer.resourceIsDirectory(StaticFileServer.java:116)
    at com.vaadin.flow.server.StaticFileServer.serveStaticResource(StaticFileServer.java:277)
    at com.vaadin.flow.server.VaadinServlet.serveStaticOrWebJarRequest(VaadinServlet.java:401)
    at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:356)
    at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:106)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)

Worked example / Explanation

The following is just one of many possible error scenarios that can lead to FileSystemNotFoundException being thrown.

Imagine this sequence of events all happening in the resourceIsDirectory(URL resource) methods of said classes:

  1. Request made for Flow resource: /VAADIN/build/FlowClient.42a5821f.js (StaticFileServer creates new FileSystem)
  2. Request made for Legacy resource: /framework/VAADIN/vaadinBootstrap.js(VaadinServlet gets the FileSystem created above as it's a request for an item in the same uber-jar!)
  3. FileSystem closed at end of request for flow resource
  4. FileSystemNotFoundException thrown when closing the FileSystem in legacy servlet as it's already been closed

Expected behaviour

Static resources should be served successfully

Actual behaviour

Static resources not served reliably

Versions:

pogocode commented 1 year ago

I've since tried this with these later versions and the issue still occurs:

        <vaadin.version>23.2.7</vaadin.version>
        <vaadin8.version>8.18.0</vaadin8.version>
        <flow.version>23.2.7</flow.version>
TatuLund commented 1 year ago

I think This issue can happen only with Vaadin 8. The checking if the resource is a Directory is done differently in Vaadin 7, which needs to support Java 6, and Java 6 does not have FileSystem.

The most straightforward way to fix the problem would be to override legacy framework method in MPR, and use the same way we use in Vaadin 7. That will avoid the conflict.

mcollovati commented 1 year ago

The most straightforward way to fix the problem would be to override legacy framework method in MPR, and use the same way we use in Vaadin 7. That will avoid the conflict.

Another solution may be making StaticFileServer.resourceIsDirectory in Flow public and static. This way, the current locking system will prevent closing a file system in use. Of course, MPR Servlet for Vaadin 8 should be changed as well by overriding isAllowedVAADINResourceUrl to use StaticFileServer.resourceIsDirectory

oliveryasuna commented 1 year ago

Also experiencing this issue.

Vaadin 23.3.4
Vaadin 8.19.0
MPR 6.1.1
ashirley commented 1 year ago

Any update on this? As a High/Major with a low impact, well understood solution, it would be good to get this fixed.

tarekoraby commented 1 year ago

@ashirley, do I understand correctly that you have a Prime commercial subscription to Vaadin? If so, you can use the Warranty feature of your Prime subscription to flag this bug for priority fixing, which you can do from http://support.vaadin.com/.

Of course, we will always aim to fix the issue as early as we can, even without flagging it for priority fixing.

mshabarov commented 1 year ago

The issue was triaged and currently added to the backlog priority queue for further investigation.

mcollovati commented 1 year ago

After some investigation, I think that @TatuLund's proposal to override the MPRServlet method using the Vaadin 7 logic is the better solution to fix this issue.