/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.test.jdo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import javax.jdo.Extent; import javax.jdo.JDOHelper; import javax.jdo.JDOOptimisticVerificationException; import javax.jdo.PersistenceManager; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.zoodb.jdo.ZooJdoHelper; import org.zoodb.test.api.TestSuper; import org.zoodb.test.testutil.TestTools; import org.zoodb.tools.ZooConfig; public class Test_024_MultiSessionConcurrency { private final int N = 10000; private final int COMMIT_INTERVAL = 250; private final int T = 8; private final static ArrayList<Throwable> errors = new ArrayList<>(); @BeforeClass public static void beforeClass() { ZooConfig.setFileManager(ZooConfig.FILE_MGR_IN_MEMORY); } @AfterClass public static void afterClass() { ZooConfig.setDefaults(); } @Before public void setUp() { TestTools.removeDb(); TestTools.createDb(); TestTools.defineSchema(TestSuper.class); TestTools.defineIndex(TestSuper.class, "_id", false); } @After public void tearDown() { try { TestTools.removeDb(); } catch (IllegalStateException e) { errors.add(e); } if (!errors.isEmpty()) { RuntimeException e = new RuntimeException("errors: " + errors.size(), errors.get(0)); for (Throwable t: errors) { e.addSuppressed(t); } errors.clear(); throw e; } } private abstract static class Worker extends Thread { final PersistenceManager pm; final int N; final int COMMIT_INTERVAL; int n = 0; final int ID; private Worker(int id, int n, int commitInterval) { this.ID = id; this.pm = ZooJdoHelper.openDB(TestTools.getDbName()); this.N = n; this.COMMIT_INTERVAL = commitInterval; } @Override public void run() { try { pm.currentTransaction().begin(); runWorker(); pm.currentTransaction().rollback(); } catch (Throwable t) { Test_024_MultiSessionConcurrency.errors.add(t); t.printStackTrace(); } finally { closePM(pm); } } abstract void runWorker(); protected interface RepeatableMethod { void run(); } protected void repeatUntilSuccess(RepeatableMethod m) { while (true) { try { m.run(); return; } catch (JDOOptimisticVerificationException e) { pm.currentTransaction().begin(); n -= COMMIT_INTERVAL; assertTrue(e.getNestedExceptions().length >= 1); assertTrue(e.getNestedExceptions().length <= N); } } } } private static class Reader extends Worker { private Reader(int id, int n) { super(id, n, -1); } @SuppressWarnings("unchecked") @Override public void runWorker() { //TODO use repeatUntilSuccess() ? Extent<TestSuper> ext = pm.getExtent(TestSuper.class); for (TestSuper t: ext) { assertTrue(t.getData()[0] >= 0 && t.getData()[0] < N); TestSuper t2 = (TestSuper) pm.getObjectById( JDOHelper.getObjectId(t) ); assertEquals(t.getId(), t2.getId()); if (t.getId() == ID && t.getTime() < N/2) { n++; } } Collection<TestSuper> col = (Collection<TestSuper>) pm.newQuery( TestSuper.class, "_id == " + ID + " && _time >= " + (N/2)).execute(); for (TestSuper t: col) { assertTrue(t.getId() == ID); assertTrue(t.getData()[0] >= 0 && t.getData()[0] < N); TestSuper t2 = (TestSuper) pm.getObjectById( JDOHelper.getObjectId(t) ); assertEquals(t.getId(), t2.getId()); n++; } } } private static class Writer extends Worker { private final ArrayList<Object> oids = new ArrayList<>(); private Writer(int id, int n, int commitInterval) { super(id, n, commitInterval); } @Override public void runWorker() { //TODO use repeatUntilSuccess() ? for (int i = 0; i < N; i++) { TestSuper o = new TestSuper(i, ID, new long[]{i}); pm.makePersistent(o); oids.add(pm.getObjectId(o)); n++; if (n % COMMIT_INTERVAL == 0) { pm.currentTransaction().commit(); pm.currentTransaction().begin(); } } pm.currentTransaction().commit(); pm.currentTransaction().begin(); } } private static class Deleter extends Worker { private Deleter(int id, int n, int commitInterval) { super(id, n, commitInterval); } @SuppressWarnings("unchecked") @Override public void runWorker() { repeatUntilSuccess(new RepeatableMethod() { @Override public void run() { Extent<TestSuper> ext = pm.getExtent(TestSuper.class); Iterator<TestSuper> iter = ext.iterator(); while (iter.hasNext() && n < N/2) { pm.deletePersistent(iter.next()); n++; if (n % COMMIT_INTERVAL == 0) { pm.currentTransaction().commit(); pm.currentTransaction().begin(); ext = pm.getExtent(TestSuper.class); iter = ext.iterator(); } } ext.closeAll(); } }); repeatUntilSuccess(new RepeatableMethod() { @Override public void run() { Collection<TestSuper> col = (Collection<TestSuper>) pm.newQuery(TestSuper.class).execute(); Iterator<TestSuper> iter = col.iterator(); while (iter.hasNext() && n < N) { pm.deletePersistent(iter.next()); n++; if (n % COMMIT_INTERVAL == 0) { pm.currentTransaction().commit(); pm.currentTransaction().begin(); col = (Collection<TestSuper>) pm.newQuery(TestSuper.class).execute(); iter = col.iterator(); } } } }); pm.currentTransaction().commit(); pm.currentTransaction().begin(); } } private static class Updater extends Worker { private static final int DELTA = 100; private final ArrayList<Object> oids = new ArrayList<Object>(); private Updater(int id, int n, int commitInterval, ArrayList<Object> oids) { super(id, n, commitInterval); this.oids.addAll(oids); } @Override public void runWorker() { while (n < oids.size()) { repeatUntilSuccess(new RepeatableMethod() { @Override public void run() { TestSuper t = (TestSuper) pm.getObjectById(oids.get(n)); n++; t.setId(t.getId()+DELTA); if (n % COMMIT_INTERVAL == 0) { pm.currentTransaction().commit(); pm.currentTransaction().begin(); } } }); } pm.currentTransaction().commit(); pm.currentTransaction().begin(); } } private static void closePM(PersistenceManager pm) { if (!pm.isClosed()) { if (pm.currentTransaction().isActive()) { pm.currentTransaction().rollback(); } pm.close(); } } /** * Test concurrent read. * @throws InterruptedException */ @Test public void testParallelRead() throws InterruptedException { //write Writer w = new Writer(0, N, COMMIT_INTERVAL); w.start(); w.join(); //read ArrayList<Reader> readers = new ArrayList<>(); for (int i = 0; i < T; i++) { readers.add(new Reader(0, N)); } for (Reader reader: readers) { reader.start(); } for (Reader reader: readers) { reader.join(); assertEquals("id=" + reader.ID, N, reader.n); } } /** * Test concurrent write. * @throws InterruptedException */ @Test public void testParallelWrite() throws InterruptedException { //write ArrayList<Writer> writers = new ArrayList<>(); for (int i = 0; i < T; i++) { writers.add(new Writer(0, N, COMMIT_INTERVAL)); } for (Writer w: writers) { w.start(); } for (Writer w: writers) { w.join(); assertEquals(N, w.n); } //read Reader r = new Reader(0, N); r.start(); r.join(); assertEquals(N * T, r.n); } /** * Test concurrent write. * @throws InterruptedException */ @Test public void testParallelReadWrite() throws InterruptedException { //read and write ArrayList<Thread> workers = new ArrayList<>(); for (int i = 0; i < T; i++) { workers.add(new Reader(i, N)); workers.add(new Writer(i, N, COMMIT_INTERVAL)); } for (Thread w: workers) { w.start(); } for (Thread w: workers) { w.join(); if (w instanceof Writer) { assertEquals(N, ((Writer)w).n); } } //read only ArrayList<Reader> readers = new ArrayList<>(); for (int i = 0; i < T; i++) { readers.add(new Reader(i, N)); } for (Reader reader: readers) { reader.start(); } for (Reader reader: readers) { reader.join(); assertEquals(N, reader.n); } } @Test public void testParallelUpdater() throws InterruptedException { //read and write ArrayList<Writer> writers = new ArrayList<>(); for (int i = 0; i < T; i++) { writers.add(new Writer(i, N, COMMIT_INTERVAL)); } for (Writer w: writers) { w.start(); } for (Writer w: writers) { w.join(); assertEquals(N, w.n); } //update objects in parallel (no object touched twice) ArrayList<Updater> updaters = new ArrayList<>(); for (int i = 0; i < T; i++) { updaters.add(new Updater(i, N, COMMIT_INTERVAL, writers.get(i).oids)); } for (Updater w: updaters) { w.start(); } for (Updater w: updaters) { w.join(); assertEquals(N, w.n); } //read only ArrayList<Reader> readers = new ArrayList<>(); for (int i = 0; i < T; i++) { readers.add(new Reader(i+Updater.DELTA, N)); } for (Reader reader: readers) { reader.start(); } for (Reader reader: readers) { reader.join(); assertEquals("ID=" + reader.ID, N, reader.n); } } @Test public void testConcurrentUpdater() throws InterruptedException { //read and write Writer w = new Writer(0, N, COMMIT_INTERVAL); w.start(); w.join(); //update objects concurrently (objects updated concurrently) ArrayList<Updater> updaters = new ArrayList<>(); for (int i = 0; i < T; i++) { updaters.add(new Updater(0, N, COMMIT_INTERVAL, w.oids)); } for (Updater u: updaters) { u.start(); } for (Updater u: updaters) { u.join(); assertEquals(N, w.n); } //read only Reader r = new Reader(T*Updater.DELTA, N); r.start(); r.join(); assertEquals(N, r.n); } @Test // public void repeat() throws InterruptedException { // for (int i =-0; i < 100; i++) { // System.out.println("i=" + i); // testParallelDeleter(); // } // } public void testParallelDeleter() throws InterruptedException { //write ArrayList<Writer> writers = new ArrayList<>(); for (int i = 0; i < T; i++) { writers.add(new Writer(i, N, COMMIT_INTERVAL)); } for (Writer w: writers) { w.start(); } for (Writer w: writers) { w.join(); assertEquals(N, w.n); } //delete ArrayList<Deleter> workers = new ArrayList<>(); for (int i = 0; i < T; i++) { workers.add(new Deleter(i, N, COMMIT_INTERVAL)); } for (Deleter w: workers) { w.start(); } for (Deleter w: workers) { w.join(); assertEquals(N, w.n); } } }