Repository: quarkusio/quarkus
Issue: AeshConsole exception happening randomly when killing continuous testing

## Describe the bug

Exception happening randomly when killing continuous testing (using main):
```
Exception in thread "Aesh InputStream Reader"
java.lang.RuntimeException: Unable to read io/quarkus/deployment/dev/console/AeshConsole$4.class
	at io.quarkus.bootstrap.classloading.JarClassPathElement$1$1$1.apply(JarClassPathElement.java:131)
	at io.quarkus.bootstrap.classloading.JarClassPathElement$1$1$1.apply(JarClassPathElement.java:118)
	at io.quarkus.bootstrap.classloading.JarClassPathElement.withJarFile(JarClassPathElement.java:161)
	at io.quarkus.bootstrap.classloading.JarClassPathElement$1$1.getData(JarClassPathElement.java:118)
	at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:435)
	at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:397)
	at io.quarkus.deployment.dev.console.AeshConsole.lambda$setup$0(AeshConsole.java:160)
	at org.aesh.terminal.EventDecoder.accept(EventDecoder.java:105)
	at org.aesh.terminal.EventDecoder.accept(EventDecoder.java:31)
	at org.aesh.terminal.io.Decoder.write(Decoder.java:133)
	at org.aesh.readline.tty.terminal.TerminalConnection.openBlocking(TerminalConnection.java:216)
	at org.aesh.readline.tty.terminal.TerminalConnection.openBlocking(TerminalConnection.java:203)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.util.zip.ZipException: ZipFile invalid LOC header (bad signature)
	at java.base/java.util.zip.ZipFile$ZipFileInputStream.initDataOffset(ZipFile.java:1008)
	at java.base/java.util.zip.ZipFile$ZipFileInputStream.read(ZipFile.java:1018)
	at java.base/java.util.zip.ZipFile$ZipFileInflaterInputStream.fill(ZipFile.java:468)
	at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:159)
	at java.base/java.io.FilterInputStream.read(FilterInputStream.java:107)
	at io.quarkus.bootstrap.classloading.JarClassPathElement.readStreamContents(JarClassPathElement.java:250)
	at io.quarkus.bootstrap.classloading.JarClassPathElement$1$1$1.apply(JarClassPathElement.java:123)
	... 14 more
```

project: https://github.com/ia3andy/rock-rock-rock


Changed files:
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java

diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
index 70c880b4130..07f7c8cabb8 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
@@ -115,23 +115,44 @@ public URL getUrl() {
 
                         @Override
                         public byte[] getData() {
-                            return withJarFile(new Function<JarFile, byte[]>() {
-                                @Override
-                                public byte[] apply(JarFile jarFile) {
-                                    try {
+                            try {
+                                return withJarFile(new Function<JarFile, byte[]>() {
+                                    @Override
+                                    public byte[] apply(JarFile jarFile) {
                                         try {
-                                            return readStreamContents(jarFile.getInputStream(res));
-                                        } catch (InterruptedIOException e) {
-                                            //if we are interrupted reading data we finish the op, then just re-interrupt the thread state
-                                            byte[] bytes = readStreamContents(jarFile.getInputStream(res));
-                                            Thread.currentThread().interrupt();
-                                            return bytes;
+                                            try {
+                                                return readStreamContents(jarFile.getInputStream(res));
+                                            } catch (InterruptedIOException e) {
+                                                //if we are interrupted reading data we finish the op, then just re-interrupt the thread state
+                                                byte[] bytes = readStreamContents(jarFile.getInputStream(res));
+                                                Thread.currentThread().interrupt();
+                                                return bytes;
+                                            }
+                                        } catch (IOException e) {
+                                            if (!closed) {
+                                                throw new ZipFileMayHaveChangedException(e);
+                                            }
+                                            throw new RuntimeException("Unable to read " + name, e);
                                         }
-                                    } catch (IOException e) {
-                                        throw new RuntimeException("Unable to read " + name, e);
                                     }
+                                });
+                            } catch (ZipFileMayHaveChangedException e) {
+                                //this is a weird corner case, that should not really affect end users, but is super annoying
+                                //if you are actually working on Quarkus. If you rebuild quarkus while you have an application
+                                //running reading from the rebuilt zip file will fail, but some of these classes are needed
+                                //for a clean shutdown, so the Java process hangs and needs to be forcibly killed
+                                //this effectively attempts to reopen the file, allowing shutdown to work.
+                                //we need to do this here as close needs a write lock while withJarFile takes a readLock
+                                try {
+                                    log.error("Failed to read " + name
+                                            + " attempting to re-open the zip file. It is likely a jar file changed on disk, you should shutdown your application",
+                                            e);
+                                    close();
+                                    return getData();
+                                } catch (IOException ignore) {
+                                    throw new RuntimeException("Unable to read " + name, e.getCause());
                                 }
-                            });
+                            }
                         }
 
                         @Override
@@ -260,4 +281,10 @@ public static byte[] readStreamContents(InputStream inputStream) throws IOExcept
     public String toString() {
         return file.getName() + ": " + jarPath;
     }
+
+    static class ZipFileMayHaveChangedException extends RuntimeException {
+        public ZipFileMayHaveChangedException(Throwable cause) {
+            super(cause);
+        }
+    }
 }