You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2019/01/11 11:58:01 UTC

[wicket] branch master updated: WICKET-6629 OOM (and disk) in AsynchronousPageStore

This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/master by this push:
     new 5cf4f6c  WICKET-6629 OOM (and disk) in AsynchronousPageStore
5cf4f6c is described below

commit 5cf4f6c8936aaf01bb5a3cae1c392bf2545cf9a0
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
AuthorDate: Fri Jan 11 13:57:14 2019 +0200

    WICKET-6629 OOM (and disk) in AsynchronousPageStore
    
    Clear the AsynchronousPageStore's entries for expired sessions
---
 .../wicket/pageStore/AsynchronousPageStore.java    |   1 +
 .../pageStore/AsyncPageStoreManagerTest.java       | 151 +++++++++++++++++++++
 2 files changed, 152 insertions(+)

diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
index c24b2a0..13bed6e 100644
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
@@ -306,6 +306,7 @@ public class AsynchronousPageStore implements IPageStore
 	@Override
 	public void unbind(String sessionId)
 	{
+		entries.removeIf(entry -> entry.sessionId.equals(sessionId));
 		delegate.unbind(sessionId);
 	}
 
diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/AsyncPageStoreManagerTest.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/AsyncPageStoreManagerTest.java
new file mode 100644
index 0000000..f6a6542
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/AsyncPageStoreManagerTest.java
@@ -0,0 +1,151 @@
+package org.apache.wicket.pageStore;
+
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.page.IPageManager;
+import org.apache.wicket.page.IPageManagerContext;
+import org.apache.wicket.page.PageStoreManager;
+import org.apache.wicket.pageStore.memory.DummyPageManagerContext;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.serialize.java.DeflatedJavaSerializer;
+import org.apache.wicket.util.WicketTestTag;
+import org.apache.wicket.util.file.File;
+import org.apache.wicket.util.lang.Bytes;
+import org.junit.Assert;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.mockito.internal.util.reflection.FieldReader;
+import org.mockito.internal.util.reflection.FieldSetter;
+
+/**
+ * https://issues.apache.org/jira/browse/WICKET-6629
+ */
+@Tag(WicketTestTag.SLOW)
+class AsyncPageStoreManagerTest
+{
+	private static final String APP_NAME = "test_app";
+
+	/*
+	 * Symptoms:
+	 * 1) Out of memory on DiskDataStore.sessionEntryMap, map contains thousands of
+	 * DiskDataStore$SessionEntry's; The vm only contains a few hundred PageStoreManager$SessionEntry's
+	 * (as expected for the currently active sessions)
+	 * 2) Slowly growing disk usage, the Wicket filestore contains thousands of directories, up to the point
+	 * where ls becomes unusable.
+	 * 
+	 * Problem (as far as I can tell):
+	 * The PageSavingRunnable of the AsynchronousPageStore saves entries to the DiskDataStore after the
+	 * corresponding session has been invalidated and its DiskDataStore has been cleaned. Because the session is
+	 * destroyed according to the container and Wicket session handling there is nothing triggering a cleanup for
+	 * this session anymore.
+	 */
+	@Test
+	void invalidateSessionBeforeSave() throws InterruptedException
+	{
+		ISerializer serializer = new DeflatedJavaSerializer("applicationKey");
+		IDataStore dataStore = new DiskDataStore("applicationName", new File("target"),
+			Bytes.bytes(10000l));
+		IPageStore pageStore = new DefaultPageStore(serializer, dataStore, 0);
+		IPageStore asyncPageStore = new AsynchronousPageStore(pageStore, 100);
+		IPageManagerContext pageManagerContext = new DummyPageManagerContext();
+		IPageManager newPageManager = new PageStoreManager(APP_NAME, asyncPageStore,
+			pageManagerContext);
+
+		// Commit a page to the pagemanager
+		TestPage toSerializePage = new TestPage(0);
+		newPageManager.touchPage(toSerializePage);
+		newPageManager.commitRequest();
+
+		// Allow some time for the PageSavingRunnable to save the page to disk
+		Thread.sleep(1000);
+
+		// Page should be stored on disk
+		Assert.assertNotNull(dataStore.getData("dummy_id", 0));
+
+		// "Stop" the PageSavingRunnable, so we can simulate a pending page
+		Thread t = null;
+		try
+		{
+			t = (Thread)new FieldReader(asyncPageStore,
+				AsynchronousPageStore.class.getDeclaredField("pageSavingThread")).read();
+			Runnable r = (Runnable)new FieldReader(t, Thread.class.getDeclaredField("target"))
+				.read();
+			FieldSetter.setField(asyncPageStore,
+				AsynchronousPageStore.class.getDeclaredField("pageSavingThread"), null);
+
+			// Allow some time for the original PageSavingRunnable to exit
+			Thread.sleep(1000);
+
+			t = new Thread(r, "Wicket-AsyncPageStore-PageSavingThread");
+			t.setDaemon(true);
+			FieldSetter.setField(asyncPageStore,
+				AsynchronousPageStore.class.getDeclaredField("pageSavingThread"), t);
+		}
+		catch (NoSuchFieldException | SecurityException e)
+		{
+			throw new RuntimeException(e);
+		}
+
+		// Commit a second page to this session
+		newPageManager.touchPage(new TestPage(1));
+		newPageManager.commitRequest();
+
+		Thread.sleep(1000);
+
+		// PageSavingRunnable is not running so page should not be in the datastore
+		Assert.assertNull(dataStore.getData("dummy_id", 1));
+
+		// Session is invalidated and a clear on the PageStoreManager is called
+		newPageManager.clear();
+
+		// Datastore is indeed empty after the clear
+		Assert.assertNull(dataStore.getData("dummy_id", 0));
+		Assert.assertNull(dataStore.getData("dummy_id", 1));
+
+		// "Restart" the PageSavingRunnable
+		t.start();
+
+		// Allow some time for the PageSavingRunnable to save any pages left in the queue to disk
+		Thread.sleep(1000);
+
+		// Session has been invalidated. The datastore should not contain any pages for this
+		// session, because they will never be cleaned! Not in the DiskDataStore.sessionEntryMap
+		// (OOM) and not on disk (slowly filling disk as this even survives application restarts.
+		Assert.assertNull(dataStore.getData("dummy_id", 0));
+		Assert.assertNull(dataStore.getData("dummy_id", 1));
+	}
+
+	private static class TestPage implements IManageablePage
+	{
+		private static final long serialVersionUID = 1L;
+
+		private final int instanceID;
+
+		private TestPage(int id)
+		{
+			instanceID = id;
+		}
+
+		@Override
+		public boolean isPageStateless()
+		{
+			return false;
+		}
+
+		@Override
+		public int getPageId()
+		{
+			return instanceID;
+		}
+
+		@Override
+		public void detach()
+		{
+		}
+
+		@Override
+		public boolean setFreezePageId(boolean freeze)
+		{
+			return false;
+		}
+	}
+}