/* * Copyright (C) 2009 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.services.jcr.impl.dataflow.persistent; import org.exoplatform.services.jcr.JcrImplBaseTest; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.NodeImpl; import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData; import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData; import org.exoplatform.services.jcr.impl.dataflow.TransientValueData; import org.exoplatform.services.jcr.util.IdGenerator; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import javax.jcr.PropertyType; /** * Created by The eXo Platform SAS * * Date: 19.06.2008 * * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a> * @version $Id: TestLinkedCacheMultithread.java 34801 2009-07-31 15:44:50Z dkatayev $ */ public class TestLinkedCacheMultithread extends JcrImplBaseTest { protected static Log log = ExoLogger.getLogger("exo.jcr.component.core.TestLinkedCacheMultithread"); private LinkedWorkspaceStorageCacheImpl cache; // private LRUWorkspaceStorageCacheImpl cache; private NodeData rootData; class Reader extends Thread { final NodeData[] nodes; final int nodesMaxIndex; final Random random; int itemsProcessed = 0; volatile boolean execute = true; Reader(NodeData[] nodes, String name) { this.nodes = nodes; this.random = new Random(); this.nodesMaxIndex = nodes.length - 1; super.setName(name); } @Override public void run() { // log.info("START"); try { while (execute) { NodeData rndNode = nodes[random.nextInt(nodesMaxIndex)]; if (random.nextBoolean()) { // by id NodeData n = (NodeData)cache.get(rndNode.getIdentifier()); if (n != null) assertEquals(rndNode.getIdentifier(), n.getIdentifier()); } else { // by parent + name NodeData n = (NodeData)cache.get(rndNode.getParentIdentifier(), rndNode.getQPath().getEntries()[rndNode .getQPath().getEntries().length - 1], ItemType.NODE); if (n != null) assertEquals(rndNode.getIdentifier(), n.getIdentifier()); } itemsProcessed++; } } catch (Exception e) { log.error(getName() + " " + e, e); } // log.info("FINISH"); } public void cancel() { this.execute = false; } } class Writer extends Thread { final NodeData[] parentNodes; final int nodesMaxIndex; final Random random; final long putTimeout; int itemsProcessed = 0; volatile boolean execute = true; Writer(NodeData[] parentNodes, String name, long putTimeout) { this.parentNodes = parentNodes; this.random = new Random(); this.nodesMaxIndex = parentNodes.length - 1; this.putTimeout = putTimeout; super.setName(name); } @Override public void run() { // log.info("START"); try { while (execute) { int next = random.nextInt(nodesMaxIndex); NodeData rndNode = parentNodes[next]; if (random.nextBoolean()) { // put single item if (random.nextBoolean()) { // node cache.put(new TransientNodeData(QPath.makeChildPath(rndNode.getQPath(), InternalQName .parse("[]childNode-" + next)), IdGenerator.generate(), 1, Constants.NT_UNSTRUCTURED, new InternalQName[0], 1, IdGenerator.generate(), rndNode.getACL())); } else { TransientPropertyData pd = new TransientPropertyData(QPath.makeChildPath(rndNode.getQPath(), InternalQName .parse("[]property-" + next)), IdGenerator.generate(), 1, PropertyType.STRING, rndNode .getIdentifier(), false, new TransientValueData("prop data")); cache.put(pd); } itemsProcessed++; } else { // put list of childs if (random.nextBoolean()) { // nodes List<NodeData> cn = createNodesData(rndNode, 100); cache.addChildNodes(rndNode, cn); itemsProcessed += cn.size(); } else { // properties w/o value List<PropertyData> cp = createPropertiesData(rndNode, 100); cache.addChildProperties(rndNode, cp); itemsProcessed += cp.size(); } } Thread.sleep(putTimeout); } } catch (Exception e) { log.error(getName() + " " + e, e); } // log.info("FINISH"); } public void cancel() { this.execute = false; } } class Remover extends Thread { final NodeData[] nodes; final int nodesMaxIndex; final Random random; final long putTimeout; int itemsProcessed = 0; volatile boolean execute = true; Remover(NodeData[] nodes, String name, long putTimeout) { this.nodes = nodes; this.random = new Random(); this.nodesMaxIndex = nodes.length - 1; this.putTimeout = putTimeout; super.setName(name); } @Override public void run() { // log.info("START"); try { while (execute) { NodeData rndNode = nodes[random.nextInt(nodesMaxIndex)]; if (random.nextBoolean()) { // remove child node List<NodeData> cns = cache.getChildNodes(rndNode); if (cns != null) cache.remove(cns.get(0)); } else { // remove child property List<PropertyData> cps = cache.getChildProperties(rndNode); if (cps != null) cache.remove(cps.get(0)); } itemsProcessed++; Thread.sleep(putTimeout); } } catch (Exception e) { log.error(getName() + " " + e, e); } // log.info("FINISH"); } public void cancel() { this.execute = false; } } class Locker extends Thread { final int timeout; Locker(int timeout) { super("Locker-" + timeout); this.timeout = timeout; } @Override public void run() { synchronized (cache) { try { log.info("sleep..."); Thread.sleep(timeout); log.info("done"); } catch (InterruptedException e) { log.error(getName() + " " + e, e); } } } } @Override public void setUp() throws Exception { super.setUp(); // cache = new LinkedWorkspaceStorageCacheImpl((WorkspaceEntry) // session.getContainer().getComponentInstanceOfType(WorkspaceEntry.class)); // cache = new LRUWorkspaceStorageCacheImpl("testLoad_cache", true, 100 * 1024, 120, 5 * 60000, // 30000, false); cache = new LinkedWorkspaceStorageCacheImpl("testLoad_cache", true, 100 * 1024, 120, 5 * 60000, 30000, false, true, 0, true); rootData = (NodeData)((NodeImpl)root).getData(); } private List<NodeData> createNodesData(NodeData parent, int count) throws Exception { List<NodeData> nodes = new ArrayList<NodeData>(); for (int i = 1; i <= count; i++) { nodes.add(new TransientNodeData(QPath.makeChildPath(parent.getQPath(), InternalQName.parse("[]node" + i)), IdGenerator.generate(), 1, Constants.NT_UNSTRUCTURED, new InternalQName[0], 1, IdGenerator.generate(), parent.getACL())); } return nodes; } /** * properties w/o value. * * @param parent * @param count * @return * @throws Exception */ private List<PropertyData> createPropertiesData(NodeData parent, int count) throws Exception { List<PropertyData> props = new ArrayList<PropertyData>(); for (int i = 1; i <= count; i++) { TransientPropertyData pd = new TransientPropertyData(QPath.makeChildPath(parent.getQPath(), InternalQName.parse("[]property-" + i)), IdGenerator.generate(), 1, PropertyType.STRING, parent.getIdentifier(), false, new TransientValueData( "prop data")); props.add(pd); } return props; } private List<NodeData> prepare() throws Exception { // prepare final List<NodeData> nodes1 = createNodesData(rootData, 100); cache.put(rootData); for (NodeData n : nodes1) { cache.put(n); } cache.addChildNodes(rootData, nodes1); // re-put as childs final List<NodeData> nodes2 = createNodesData(nodes1.get(5), 250); cache.put(nodes1.get(5)); for (NodeData n : nodes2) { cache.put(n); } cache.addChildNodes(rootData, nodes2); // re-put as childs final List<NodeData> nodes = new ArrayList<NodeData>(); nodes.addAll(nodes1); nodes.addAll(nodes2); return nodes; } public void testDummy() throws Exception { } public void _testGet() throws Exception { List<NodeData> nodes = prepare(); Set<Reader> readers = new HashSet<Reader>(); long start = System.currentTimeMillis(); try { // create readers for (int t = 1; t <= 200; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Reader r = new Reader(ns, "reader #" + t); readers.add(r); r.start(); } log.info("Started"); Thread.sleep(30 * 1000); log.info("Done"); } finally { // join for (Reader r : readers) { r.cancel(); r.join(); } // debug result long totalRead = 0; for (Reader r : readers) { totalRead += r.itemsProcessed; // log.info(r.getName() + " " + (r.itemsProcessed)); } long time = System.currentTimeMillis() - start; double speed = totalRead * 1d / time; log.info("Total read " + totalRead + ", speed " + speed + "read/sec., time " + (time / 1000d) + "sec"); } } public void _testPut() throws Exception { // put any stuff List<NodeData> nodes = prepare(); Set<Writer> writers = new HashSet<Writer>(); try { // create readers for (int t = 1; t <= 100; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Writer r = new Writer(ns, "writer #" + t, 50); writers.add(r); r.start(); } Thread.sleep(5 * 60 * 1000); } finally { // join for (Writer w : writers) { w.cancel(); w.join(); } // debug result for (Writer w : writers) { log.info(w.getName() + " " + (w.itemsProcessed)); } } } public void _testGetPut() throws Exception { List<NodeData> nodes = prepare(); Set<Reader> readers = new HashSet<Reader>(); Set<Writer> writers = new HashSet<Writer>(); try { // create readers for (int t = 1; t <= 10; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Reader r = new Reader(ns, "reader #" + t); readers.add(r); r.start(); } // create writers for (int t = 1; t <= 10; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Writer w = new Writer(ns, "writer #" + t, 250); writers.add(w); w.start(); } Thread.sleep(5 * 60 * 1000); } finally { // join for (Writer w : writers) { w.cancel(); w.join(); } for (Reader r : readers) { r.cancel(); r.join(); } // debug result for (Reader r : readers) {// cache.getSize() log.info(r.getName() + " " + (r.itemsProcessed)); } for (Writer w : writers) { log.info(w.getName() + " " + (w.itemsProcessed)); } } } public void _testGetPutRemove() throws Exception { List<NodeData> nodes = prepare(); Set<Reader> readers = new HashSet<Reader>(); Set<Writer> writers = new HashSet<Writer>(); Set<Remover> removers = new HashSet<Remover>(); long start = System.currentTimeMillis(); try { // create readers for (int t = 1; t <= 100; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Reader r = new Reader(ns, "reader #" + t); readers.add(r); r.start(); } // create writers for (int t = 1; t <= 5; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Writer w = new Writer(ns, "writer #" + t, 1000); writers.add(w); w.start(); } // create removers for (int t = 1; t <= 5; t++) { NodeData[] ns = new NodeData[nodes.size()]; nodes.toArray(ns); Remover r = new Remover(ns, "remover #" + t, 1000); removers.add(r); r.start(); } log.info("Wait...."); // Thread.sleep(50400 * 1000); // 50400sec = 14h Thread.sleep(20 * 1000); // 20sec. log.info("Stopping"); } finally { // join for (Remover r : removers) { r.cancel(); r.join(); } for (Writer w : writers) { w.cancel(); w.join(); } for (Reader r : readers) { r.cancel(); r.join(); } // debug result long stop = System.currentTimeMillis() - start; long totalRead = 0; for (Reader r : readers) { totalRead += r.itemsProcessed; // log.info(r.getName() + " " + (r.itemsProcessed)); } for (Writer w : writers) { totalRead += w.itemsProcessed; // log.info(w.getName() + " " + (w.itemsProcessed)); } for (Remover r : removers) { totalRead += r.itemsProcessed; // log.info(r.getName() + " " + (r.itemsProcessed)); } double speed = totalRead * 1d / stop; log.info("Total accessed " + totalRead + ", speed " + speed + " oper/sec., time " + (stop / 1000d) + "sec"); } } }