WICKET-5527 Inefficient DefaultPageStore.SerializedPagesCache Add tests and fix bugs in the new PerSessionPageStore impl
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/1c31627e Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/1c31627e Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/1c31627e Branch: refs/heads/master Commit: 1c31627e4bfc167bc6e2e29bd642991d6b5a1dcb Parents: 25016d1 Author: Martin Tzvetanov Grigorov <mgrigo...@apache.org> Authored: Tue Mar 11 12:17:55 2014 +0200 Committer: Martin Tzvetanov Grigorov <mgrigo...@apache.org> Committed: Tue Mar 11 12:17:55 2014 +0200 ---------------------------------------------------------------------- .../wicket/pageStore/DefaultPageStore.java | 46 ++++--- .../wicket/pageStore/PerSessionPageStore.java | 52 +++++--- .../wicket/pageStore/AbstractPageStoreTest.java | 131 +++++++++++++++++++ .../wicket/pageStore/DefaultPageStoreTest.java | 31 +++++ .../apache/wicket/pageStore/NoopDataStore.java | 61 +++++++++ .../pageStore/PerSessionPageStoreTest.java | 53 ++++++++ 6 files changed, 336 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/1c31627e/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java index 393fac3..e574f37 100644 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java @@ -303,18 +303,19 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. */ static class SerializedPagesCache implements SecondLevelPageCache<String, Integer, SerializedPage> { - private final int size; + private final int maxSize; private final ConcurrentLinkedDeque<SoftReference<SerializedPage>> cache; /** - * Construct. + * Constructor. * - * @param size + * @param maxSize + * The maximum number of entries to cache */ - public SerializedPagesCache(final int size) + public SerializedPagesCache(final int maxSize) { - this.size = size; + this.maxSize = maxSize; cache = new ConcurrentLinkedDeque<>(); } @@ -327,17 +328,18 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. @Override public SerializedPage removePage(final String sessionId, final Integer pageId) { - if (size > 0) + if (maxSize > 0) { Args.notNull(sessionId, "sessionId"); Args.notNull(pageId, "pageId"); + SerializedPage sample = new SerializedPage(sessionId, pageId, null); + for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();) { SoftReference<SerializedPage> ref = i.next(); SerializedPage entry = ref.get(); - if (entry != null && entry.getPageId() == pageId && - entry.getSessionId().equals(sessionId)) + if (sample.equals(entry)) { i.remove(); return entry; @@ -356,7 +358,7 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. @Override public void removePages(String sessionId) { - if (size > 0) + if (maxSize > 0) { Args.notNull(sessionId, "sessionId"); @@ -385,17 +387,18 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. public SerializedPage getPage(String sessionId, Integer pageId) { SerializedPage result = null; - if (size > 0) + if (maxSize > 0) { Args.notNull(sessionId, "sessionId"); Args.notNull(pageId, "pageId"); + SerializedPage sample = new SerializedPage(sessionId, pageId, null); + for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();) { SoftReference<SerializedPage> ref = i.next(); SerializedPage entry = ref.get(); - if (entry != null && entry.getPageId() == pageId && - entry.getSessionId().equals(sessionId)) + if (sample.equals(entry)) { i.remove(); result = entry; @@ -406,7 +409,7 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. if (result != null) { // move to top - storePage(sessionId, pageId, result); + internalStore(result); } } return result; @@ -421,7 +424,7 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. @Override public void storePage(String sessionId, Integer pageId, SerializedPage page) { - if (size > 0) + if (maxSize > 0) { Args.notNull(sessionId, "sessionId"); Args.notNull(pageId, "pageId"); @@ -438,11 +441,16 @@ public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore. } } - cache.add(new SoftReference<>(page)); - while (cache.size() > size) - { - cache.remove(0); - } + internalStore(page); + } + } + + private void internalStore(SerializedPage page) + { + cache.push(new SoftReference<>(page)); + while (cache.size() > maxSize) + { + cache.pollLast(); } } http://git-wip-us.apache.org/repos/asf/wicket/blob/1c31627e/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java index 8d0c631..eaff550 100644 --- a/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java @@ -18,6 +18,7 @@ package org.apache.wicket.pageStore; import java.lang.ref.SoftReference; import java.util.Comparator; +import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; @@ -25,7 +26,6 @@ import java.util.concurrent.ConcurrentSkipListMap; import org.apache.wicket.page.IManageablePage; import org.apache.wicket.serialize.ISerializer; import org.apache.wicket.util.lang.Args; -import org.apache.wicket.util.time.Time; /** * A page store that uses a SecondLevelPageCache with the last N used page instances @@ -75,6 +75,8 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag } /** + * An implementation of SecondLevelPageCache that stores the last used N live page instances + * per http session. */ protected static class PagesCache implements SecondLevelPageCache<String, Integer, IManageablePage> { @@ -92,7 +94,7 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag /** * The last time this page has been used/accessed. */ - private Time accessTime; + private long accessTime; private PageValue(IManageablePage page) { @@ -102,7 +104,7 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag private PageValue(int pageId) { this.pageId = pageId; - this.accessTime = Time.now(); + this.accessTime = System.nanoTime(); } @Override @@ -114,7 +116,6 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag PageValue pageValue = (PageValue) o; return pageId == pageValue.pageId; - } @Override @@ -129,7 +130,7 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag @Override public int compare(PageValue p1, PageValue p2) { - return p1.accessTime.compareTo(p2.accessTime); + return Long.valueOf(p1.accessTime).compareTo(p2.accessTime); } } @@ -173,11 +174,17 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag ConcurrentMap<PageValue, IManageablePage> pages = pagesPerSession.get(); if (pages != null) { - PageValue pv = new PageValue(pageId); - IManageablePage page = pages.remove(pv); - if (page != null) + PageValue sample = new PageValue(pageId); + Iterator<Map.Entry<PageValue, IManageablePage>> iterator = pages.entrySet().iterator(); + while (iterator.hasNext()) { - result = page; + Map.Entry<PageValue, IManageablePage> entry = iterator.next(); + if (sample.equals(entry.getKey())) + { + result = entry.getValue(); + iterator.remove(); + break; + } } } } @@ -231,14 +238,19 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag ConcurrentSkipListMap<PageValue, IManageablePage> pages = pagesPerSession.get(); if (pages != null) { - PageValue pv = new PageValue(pageId); - Map.Entry<PageValue, IManageablePage> entry = pages.ceilingEntry(pv); - - if (entry != null) + PageValue sample = new PageValue(pageId); + Iterator<Map.Entry<PageValue,IManageablePage>> iterator = pages.entrySet().iterator(); + while (iterator.hasNext()) { - // touch the entry - entry.getKey().accessTime = Time.now(); - result = entry.getValue(); + Map.Entry<PageValue, IManageablePage> entry = iterator.next(); + + if (sample.equals(entry.getKey())) + { + // touch the entry + entry.getKey().accessTime = System.nanoTime(); + result = entry.getValue(); + break; + } } } } @@ -286,13 +298,15 @@ public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePag if (pages != null) { + removePage(sessionId, pageId); + + PageValue pv = new PageValue(page); + pages.put(pv, page); + while (pages.size() > maxEntriesPerSession) { pages.pollFirstEntry(); } - - PageValue pv = new PageValue(page); - pages.put(pv, page); } } } http://git-wip-us.apache.org/repos/asf/wicket/blob/1c31627e/wicket-core/src/test/java/org/apache/wicket/pageStore/AbstractPageStoreTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/AbstractPageStoreTest.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/AbstractPageStoreTest.java new file mode 100644 index 0000000..42cce39 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/AbstractPageStoreTest.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import org.apache.wicket.MockPage; +import org.apache.wicket.serialize.ISerializer; +import org.apache.wicket.serialize.java.JavaSerializer; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public abstract class AbstractPageStoreTest extends Assert +{ + protected final String sessionId = "1234567890"; + protected final int pageId = 123; + protected final ISerializer serializer = new JavaSerializer(DefaultPageStore.class.getName()); + protected final IDataStore dataStore = new NoopDataStore(); + protected int maxEntries = 1; + protected IPageStore pageStore = null; + + @Before + public void before() + { + pageStore = createPageStore(serializer, dataStore, maxEntries); + } + + protected abstract IPageStore createPageStore(ISerializer serializer, IDataStore dataStore, int maxEntries); + + @After + public void after() + { + if (pageStore != null) + { + pageStore.destroy(); + pageStore = null; + } + } + + /** + * Assert that a stored page is available to be read + */ + @Test + public void storePage() + { + pageStore.storePage(sessionId, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + } + + /** + * Assert that storing a page twice won't keep two entries + */ + @Test + public void storePage2() + { + int maxEntries = 10; + + pageStore = createPageStore(serializer, dataStore, maxEntries); + + pageStore.storePage(sessionId, new MockPage(pageId)); + pageStore.storePage(sessionId, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + + pageStore.removePage(sessionId, pageId); + + assertNull(pageStore.getPage(sessionId, pageId)); + } + + @Test + public void removePage() + { + pageStore.storePage(sessionId, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + + pageStore.removePage(sessionId, pageId); + + assertNull(pageStore.getPage(sessionId, pageId)); + } + + /** + * Verify that at most {@code maxEntries} per session can be put in the cache + */ + @Test + public void maxSizeSameSession() + { + pageStore.storePage(sessionId, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + + int pageId2 = 234; + pageStore.storePage(sessionId, new MockPage(pageId2)); + assertNull(pageStore.getPage(sessionId, pageId)); + assertNotNull(pageStore.getPage(sessionId, pageId2)); + } + + /** + * Verify that it is OK to store more pages than {@code maxEntries} + * if they are in different sessions + */ + @Test + public void maxSizeDifferentSessions() + { + String sessionId2 = "0987654321"; + + pageStore.storePage(sessionId, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + + pageStore.storePage(sessionId2, new MockPage(pageId)); + + assertNull(pageStore.getPage(sessionId, pageId)); + assertNotNull(pageStore.getPage(sessionId2, pageId)); + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/1c31627e/wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java new file mode 100644 index 0000000..2ffa376 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import org.apache.wicket.serialize.ISerializer; + +/** + * Tests for DefaultPageStore + */ +public class DefaultPageStoreTest extends AbstractPageStoreTest +{ + @Override + protected IPageStore createPageStore(ISerializer serializer, IDataStore dataStore, int maxEntries) + { + return new DefaultPageStore(serializer, dataStore, maxEntries); + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/1c31627e/wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java new file mode 100644 index 0000000..0cadaee --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +/** + * An implementation of IDataStore that does nothing + */ +public class NoopDataStore implements IDataStore +{ + @Override + public byte[] getData(String sessionId, int id) + { + return null; + } + + @Override + public void removeData(String sessionId, int id) + { + } + + @Override + public void removeData(String sessionId) + { + } + + @Override + public void storeData(String sessionId, int id, byte[] data) + { + } + + @Override + public void destroy() + { + } + + @Override + public boolean isReplicated() + { + return false; + } + + @Override + public boolean canBeAsynchronous() + { + return false; + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/1c31627e/wicket-core/src/test/java/org/apache/wicket/pageStore/PerSessionPageStoreTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/PerSessionPageStoreTest.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/PerSessionPageStoreTest.java new file mode 100644 index 0000000..0cd4a2a --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/PerSessionPageStoreTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.pageStore; + +import org.apache.wicket.MockPage; +import org.apache.wicket.serialize.ISerializer; +import org.junit.Test; + +/** + * Tests for PerSessionPageStore + */ +public class PerSessionPageStoreTest extends AbstractPageStoreTest +{ + @Override + protected IPageStore createPageStore(ISerializer serializer, IDataStore dataStore, int maxEntries) + { + return new PerSessionPageStore(serializer, dataStore, maxEntries); + } + + /** + * Verify that it is OK to store more pages than {@code maxEntries} + * if they are in different sessions + */ + @Test + @Override + public void maxSizeDifferentSessions() + { + String sessionId2 = "0987654321"; + + pageStore.storePage(sessionId, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + + pageStore.storePage(sessionId2, new MockPage(pageId)); + + assertNotNull(pageStore.getPage(sessionId, pageId)); + assertNotNull(pageStore.getPage(sessionId2, pageId)); + } +}