diff --git a/common/src/main/java/org/mvndaemon/mvnd/common/DaemonRegistry.java b/common/src/main/java/org/mvndaemon/mvnd/common/DaemonRegistry.java index eb1d78e2..ab51bf02 100644 --- a/common/src/main/java/org/mvndaemon/mvnd/common/DaemonRegistry.java +++ b/common/src/main/java/org/mvndaemon/mvnd/common/DaemonRegistry.java @@ -16,9 +16,9 @@ package org.mvndaemon.mvnd.common; -import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; +import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -139,6 +139,10 @@ public class DaemonRegistry implements AutoCloseable { public List getStopEvents() { read(); + return doGetDaemonStopEvents(); + } + + protected List doGetDaemonStopEvents() { return new ArrayList<>(stopEvents); } @@ -242,12 +246,28 @@ public class DaemonRegistry implements AutoCloseable { return; } catch (IOException e) { throw new RuntimeException("Could not lock offset 0 of " + registryFile); + } catch (IllegalStateException | ArrayIndexOutOfBoundsException e) { + String absPath = registryFile.toAbsolutePath().normalize().toString(); + LOGGER.warn("Invalid daemon registry info, " + + "trying to recover from this issue. " + + "If you keep getting this warning, " + + "try deleting the `registry.bin` file at [" + absPath + "]", e); + this.reset(); + return; } } throw new RuntimeException("Could not lock " + registryFile + " within " + LOCK_TIMEOUT_MS + " ms"); } } + private void reset() { + infosMap.clear(); + stopEvents.clear(); + BufferCaster.cast(buffer).clear(); + buffer.putInt(0); // reset daemon count + buffer.putInt(0); // reset stop event count + } + private static final int PROCESS_ID = getProcessId0(); private static int getProcessId0() { @@ -271,7 +291,7 @@ public class DaemonRegistry implements AutoCloseable { } } - private String readString() { + protected String readString() { int sz = buffer.getShort(); if (sz == -1) { return null; @@ -284,16 +304,26 @@ public class DaemonRegistry implements AutoCloseable { return new String(buf, StandardCharsets.UTF_8); } - private void writeString(String str) { + protected void writeString(String str) { if (str == null) { buffer.putShort((short) -1); - } else if (str.length() > 1024) { - throw new IllegalStateException("String too long: " + str); - } else { - byte[] buf = str.getBytes(StandardCharsets.UTF_8); - buffer.putShort((short) buf.length); - buffer.put(buf); + return; } + byte[] buf = str.getBytes(StandardCharsets.UTF_8); + if (buf.length > 1024) { + LOGGER.warn("Attempting to write string longer than 1024 bytes: '{}'. Please raise an issue.", str); + str = str.substring(0, 1033); + while (buf.length > 1024) { + str = str.substring(0, str.length() - 12) + "…"; + buf = str.getBytes(StandardCharsets.UTF_8); + } + } + buffer.putShort((short) buf.length); + buffer.put(buf); + } + + protected ByteBuffer buffer() { + return buffer; } public String toString() { diff --git a/common/src/test/java/org/mvndaemon/mvnd/common/DaemonRegistryTest.java b/common/src/test/java/org/mvndaemon/mvnd/common/DaemonRegistryTest.java index c2f4640d..48a79a2f 100644 --- a/common/src/test/java/org/mvndaemon/mvnd/common/DaemonRegistryTest.java +++ b/common/src/test/java/org/mvndaemon/mvnd/common/DaemonRegistryTest.java @@ -17,6 +17,8 @@ package org.mvndaemon.mvnd.common; import java.io.File; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; import java.util.Locale; @@ -50,7 +52,53 @@ public class DaemonRegistryTest { assertNotNull(reg2.getAll()); assertEquals(1, reg2.getAll().size()); } + } + @Test + public void testRecovery() throws IOException { + Path temp = File.createTempFile("reg", ".data").toPath(); + temp.toFile().deleteOnExit(); + try (TestDaemonRegistry reg1 = new TestDaemonRegistry(temp)) { + // first store daemon + byte[] token = new byte[16]; + new Random().nextBytes(token); + reg1.store(new DaemonInfo("12345678", "/java/home/", + "/data/reg/", 0x12345678, 7502, token, + Locale.getDefault().toLanguageTag(), Arrays.asList("-Xmx"), + DaemonState.Idle, System.currentTimeMillis(), System.currentTimeMillis())); + assertEquals(1, reg1.getAll().size()); + // store an invalid event to trigger recovery + StringBuilder sb = new StringBuilder(1024); + for (int i = 0; i < 1024; i++) { + sb.append('…'); + } + reg1.storeStopEvent(new DaemonStopEvent("11111", + System.currentTimeMillis(), + DaemonExpirationStatus.QUIET_EXPIRE, + sb.toString())); + assertEquals(1, reg1.doGetDaemonStopEvents().size()); + // check if registry is reset + assertEquals(0, reg1.getAll().size()); + assertEquals(0, reg1.doGetDaemonStopEvents().size()); + } + } + + static class TestDaemonRegistry extends DaemonRegistry { + public TestDaemonRegistry(Path registryFile) { + super(registryFile); + } + + @Override + protected void writeString(String str) { + ByteBuffer buffer = buffer(); + if (str == null) { + buffer.putShort((short) -1); + } else { + byte[] buf = str.getBytes(StandardCharsets.UTF_8); + buffer.putShort((short) buf.length); + buffer.put(buf); + } + } } }