From 6de7605e45a28f24693190ae48c31c0292aeae96 Mon Sep 17 00:00:00 2001 From: sky Date: Thu, 17 Jun 2021 16:17:26 +0800 Subject: [PATCH] Attempt to fix bad registry errors, fixes #432 and #433 Fix a possible write a long string in case where the string length is <= 1024 but the encoded size if > 1024. Truncate long strings and warn instead of throwing an exception which could lead to an invalid registry. Since the registry data is invalid, the content is emptied to guarantee the next registry will work correct. This fix is based on #435 provided by @lanmaoxinqing, many thanks ! --- .../mvndaemon/mvnd/common/DaemonRegistry.java | 48 +++++++++++++++---- .../mvnd/common/DaemonRegistryTest.java | 48 +++++++++++++++++++ 2 files changed, 87 insertions(+), 9 deletions(-) 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); + } + } } }