/* * 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import com.google.common.base.Stopwatch; import org.apache.commons.lang3.RandomUtils; import org.apache.wicket.page.IManageablePage; import org.apache.wicket.serialize.ISerializer; import org.apache.wicket.serialize.java.DeflatedJavaSerializer; import org.apache.wicket.util.SlowTests; import org.apache.wicket.util.file.File; import org.apache.wicket.util.lang.Bytes; import org.junit.Test; import org.junit.experimental.categories.Category; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * AsynchronousPageStoreTest * * @author manuelbarzi */ @Category(SlowTests.class) public class AsynchronousPageStoreTest { /** Log for reporting. */ private static final Logger log = LoggerFactory.getLogger(AsynchronousPageStoreTest.class); @SuppressWarnings("serial") private static class DummyPage implements IManageablePage { private int pageId; private long writeMillis; private long readMillis; private String sessionId; private DummyPage(int pageId, long writeMillis, long readMillis, String sessionId) { this.pageId = pageId; this.writeMillis = writeMillis; this.readMillis = readMillis; this.sessionId = sessionId; } @Override public boolean isPageStateless() { return false; } @Override public int getPageId() { return pageId; } @Override public void detach() { } @Override public boolean setFreezePageId(boolean freeze) { return false; } /** * @param s * @throws IOException */ private void writeObject(java.io.ObjectOutputStream s) throws IOException { log.debug("serializing page {} for {}ms (session {})", getPageId(), writeMillis, sessionId); try { Thread.sleep(writeMillis); } catch (InterruptedException e) { throw new RuntimeException(e); } s.writeInt(pageId); s.writeLong(writeMillis); s.writeLong(readMillis); s.writeObject(sessionId); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { log.debug("deserializing page {} for {}ms (session {})", getPageId(), writeMillis, sessionId); try { Thread.sleep(readMillis); } catch (InterruptedException e) { throw new RuntimeException(e); } pageId = s.readInt(); writeMillis = s.readLong(); readMillis = s.readLong(); sessionId = (String)s.readObject(); } public String toString() { return "DummyPage[pageId = " + pageId + ", writeMillis = " + writeMillis + ", readMillis = " + readMillis + ", sessionId = " + sessionId + ", hashCode = " + hashCode() + "]"; } } /** * Store returns the same page instance from queue when there is a close request for it back * again. * * @throws InterruptedException */ @Test public void storeReturnsSameInstanceOnClosePageRequest() throws InterruptedException { ISerializer serializer = new DeflatedJavaSerializer("applicationKey"); // ISerializer serializer = new DummySerializer(); IDataStore dataStore = new DiskDataStore("applicationName", new File("./target"), Bytes.bytes(10000l)); // IPageStore pageStore = new DummyPageStore(new File("target/store")); IPageStore pageStore = spy(new DefaultPageStore(serializer, dataStore, 0)); IPageStore asyncPageStore = new AsynchronousPageStore(pageStore, 100); int pageId = 0; String sessionId = "sessionId"; DummyPage page = new DummyPage(pageId, 1000, 1000, sessionId); asyncPageStore.storePage(sessionId, page); Thread.sleep(500); IManageablePage pageBack = asyncPageStore.getPage(sessionId, pageId); verify(pageStore, never()).getPage(sessionId, pageId); assertEquals(page, pageBack); } /** * Store returns the restored page instance from wrapped store when there is a distant request * for it back again. * * @throws InterruptedException */ @Test public void storeReturnsRestoredInstanceOnDistantPageRequest() throws InterruptedException { ISerializer serializer = new DeflatedJavaSerializer("applicationKey"); // ISerializer serializer = new DummySerializer(); IDataStore dataStore = new DiskDataStore("applicationName", new File("./target"), Bytes.bytes(10000l)); // IPageStore pageStore = new DummyPageStore(new File("target/store")); IPageStore pageStore = spy(new DefaultPageStore(serializer, dataStore, 0)); IPageStore asyncPageStore = new AsynchronousPageStore(pageStore, 100); int pageId = 0; String sessionId = "sessionId"; DummyPage page = new DummyPage(pageId, 1000, 1000, sessionId); asyncPageStore.storePage(sessionId, page); Thread.sleep(1500); IManageablePage pageBack = asyncPageStore.getPage(sessionId, pageId); verify(pageStore, times(1)).getPage(sessionId, pageId); assertNotEquals(page, pageBack); } /** * Store works fully asynchronous when number of pages handled never exceeds the * asynchronous-storage capacity. * * @throws InterruptedException */ @Test public void storeBehavesAsyncWhenNotExceedingStoreCapacity() throws InterruptedException { int sessions = 2; int pages = 5; long writeMillis = 2000; long readMillis = 1500; int asyncPageStoreCapacity = pages * sessions; List<Metrics> results = runTest(sessions, pages, writeMillis, readMillis, asyncPageStoreCapacity); for (Metrics metrics : results) System.out.println(metrics); for (Metrics metrics : results) { assertEquals(metrics.storedPage, metrics.restoredPage); assertTrue(metrics.storingMillis < writeMillis); assertTrue(metrics.restoringMillis < readMillis); } } /** * Store behaves sync from when number of pages handled exceeds the given asynchronous-storage * capacity. It works asynchronous until the number of pages reaches the limit (capacity). * * @throws InterruptedException */ @Test public void storeBehavesSyncFromWhenExceedingStoreCapacity() throws InterruptedException { int sessions = 2; int pages = 5; long writeMillis = 2000; long readMillis = 1500; int asyncPageStoreCapacity = pages; // only up to the first round of // pages List<Metrics> results = runTest(sessions, pages, writeMillis, readMillis, asyncPageStoreCapacity); for (Metrics metrics : results) System.out.println(metrics); int sync = 0; for (int i = 0; i < results.size(); i++) { Metrics metrics = results.get(i); assertEquals(metrics.storedPage.sessionId, metrics.restoredPage.sessionId); assertEquals(metrics.storedPage.getPageId(), metrics.restoredPage.getPageId()); if (!metrics.storedPage.equals(metrics.restoredPage)) { assertTrue(metrics.storingMillis >= metrics.storedPage.writeMillis); sync++; } } assertTrue(sync > 0); } // test run private class Metrics { private DummyPage storedPage; private long storingMillis; private DummyPage restoredPage; private long restoringMillis; public String toString() { return "Metrics[storedPage = " + storedPage + ", storingMillis = " + storingMillis + ", restoredPage = " + restoredPage + ", restoringMillis = " + restoringMillis + "]"; } } private List<Metrics> runTest(int sessions, int pages, long writeMillis, long readMillis, int asyncPageStoreCapacity) throws InterruptedException { List<Metrics> results = new ArrayList<>(); final CountDownLatch lock = new CountDownLatch(pages * sessions); // ISerializer serializer = new DummySerializer(); ISerializer serializer = new DeflatedJavaSerializer("applicationKey"); IDataStore dataStore = new DiskDataStore("applicationName", new File("./target"), Bytes.bytes(10000l)); // IPageStore pageStore = new DummyPageStore(new File("target/store")) { IPageStore pageStore = new DefaultPageStore(serializer, dataStore, 0) { @Override public void storePage(String sessionId, IManageablePage page) { super.storePage(sessionId, page); lock.countDown(); } }; IPageStore asyncPageStore = new AsynchronousPageStore(pageStore, asyncPageStoreCapacity); Stopwatch stopwatch = Stopwatch.createUnstarted(); for (int pageId = 1; pageId <= pages; pageId++) { for (int i = 1; i <= sessions; i++) { String sessionId = String.valueOf(i); Metrics metrics = new Metrics(); stopwatch.reset(); DummyPage page = new DummyPage(pageId, around(writeMillis), around(readMillis), sessionId); stopwatch.start(); asyncPageStore.storePage(sessionId, page); metrics.storedPage = page; metrics.storingMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS); stopwatch.reset(); stopwatch.start(); metrics.restoredPage = DummyPage.class .cast(asyncPageStore.getPage(sessionId, pageId)); metrics.restoringMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS); results.add(metrics); } } lock.await(pages * sessions * (writeMillis + readMillis), TimeUnit.MILLISECONDS); return results; } private long around(long target) { return RandomUtils.nextLong((long)(target * .9), (long)(target * 1.1)); } }