From e00b7e21eece08dbe118739751e92c61c6f74692 Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Tue, 6 Aug 2024 09:20:04 +0100 Subject: [PATCH 1/3] Leaky trigger state when XQuery method not found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XQuery trigger not found is acceptable behaviour (logs a debug message only, could get very chatty logs otherwise). BUT the exit path from the exception fails to tidy up the per-thread trigger state. This has been observed to leak memory; the per-thread doesn’t get cleaned up until the thread is deleted, and the trigger cyclic-check stack just builds up. So we clean up the trigger state when a trigger is not found. Closes https://github.com/eXist-db/exist/issues/5459 --- .../main/java/org/exist/collections/triggers/XQueryTrigger.java | 1 + 1 file changed, 1 insertion(+) diff --git a/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java b/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java index 5bd690b3830..420dd1f4dc4 100644 --- a/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java +++ b/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java @@ -447,6 +447,7 @@ private void execute(final TriggerPhase phase, final TriggerEvent event, final D if (LOG.isDebugEnabled()) { LOG.debug("No such function '" + functionName + "' in XQueryTrigger: " + compiledQuery.getSource()); } + TriggerStatePerThread.clearIfFinished(phase); return; } } From bdb703ff2405fb6f39cbabe7f9b9a97c76e863b1 Mon Sep 17 00:00:00 2001 From: Alan Paxton Date: Tue, 27 Aug 2024 15:23:08 +0100 Subject: [PATCH 2/3] Avoid 2nd potential XQuery trigger leak We may leak trigger states to thread local which can potentially occur when the specified XQueryTrigger library module is not available in the database Closes https://github.com/eXist-db/exist/issues/5459 --- .../main/java/org/exist/collections/triggers/XQueryTrigger.java | 1 + 1 file changed, 1 insertion(+) diff --git a/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java b/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java index 420dd1f4dc4..0a50ddaf9e1 100644 --- a/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java +++ b/exist-core/src/main/java/org/exist/collections/triggers/XQueryTrigger.java @@ -405,6 +405,7 @@ private void execute(final TriggerPhase phase, final TriggerEvent event, final D compiledQuery = getScript(broker, transaction); if (compiledQuery == null) { // NOTE: can occur if there is no such XQueryTrigger library module available in the database + TriggerStatePerThread.clearIfFinished(phase); return; } } catch (final TriggerException e) { From ed728b3fb0f8e3f3e6d2a3252f4d5ef3d1280e2f Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Thu, 10 Oct 2024 16:42:49 +0200 Subject: [PATCH 3/3] [bugfix] Address flaky test due to timing issue --- .../src/test/java/org/exist/storage/StoreResourceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exist-core/src/test/java/org/exist/storage/StoreResourceTest.java b/exist-core/src/test/java/org/exist/storage/StoreResourceTest.java index 67d22cae0ff..986a143d869 100644 --- a/exist-core/src/test/java/org/exist/storage/StoreResourceTest.java +++ b/exist-core/src/test/java/org/exist/storage/StoreResourceTest.java @@ -52,7 +52,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.exist.test.TestConstants.TEST_COLLECTION_URI; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.Assert.*; public class StoreResourceTest { @@ -80,7 +80,7 @@ public void replaceXmlAsOwner() throws AuthenticationException, LockException, P final Subject user2 = existWebServer.getBrokerPool().getSecurityManager().authenticate(USER2_NAME, USER2_PWD); final long originalDoc1LastModified = getLastModified(USER1_DOC1); replaceXmlDoc(user2, USER1_DOC1, "else"); - checkAttributes(USER1_DOC1, USER1_NAME, GROUP1_NAME, USER1_DOC1_MODE, equalTo(getCreated(USER1_DOC1)), not(originalDoc1LastModified)); + checkAttributes(USER1_DOC1, USER1_NAME, GROUP1_NAME, USER1_DOC1_MODE, equalTo(getCreated(USER1_DOC1)), greaterThanOrEqualTo(originalDoc1LastModified)); } /** @@ -91,7 +91,7 @@ public void replaceBinaryAsGroupMember() throws AuthenticationException, LockExc final Subject user2 = existWebServer.getBrokerPool().getSecurityManager().authenticate(USER2_NAME, USER2_PWD); final long originalDoc1LastModified = getLastModified(USER1_BIN_DOC1); replaceBinDoc(user2, USER1_BIN_DOC1, "something else"); - checkAttributes(USER1_BIN_DOC1, USER1_NAME, GROUP1_NAME, USER1_BIN_DOC1_MODE, equalTo(getCreated(USER1_BIN_DOC1)), not(originalDoc1LastModified)); + checkAttributes(USER1_BIN_DOC1, USER1_NAME, GROUP1_NAME, USER1_BIN_DOC1_MODE, equalTo(getCreated(USER1_BIN_DOC1)), greaterThanOrEqualTo(originalDoc1LastModified)); } private void replaceXmlDoc(final Subject execAsUser, final XmldbURI docName, final String content) throws EXistException, PermissionDeniedException, LockException, IOException, SAXException {