/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.commons.coordinate; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.SyncerCallback; import org.olat.core.util.coordinate.SyncerExecutor; import org.olat.core.util.resource.OresHelper; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; import org.olat.resource.OLATResource; import org.olat.resource.OLATResourceManager; import org.olat.test.OlatTestCase; import org.springframework.beans.factory.annotation.Autowired; public class CoordinatorTest extends OlatTestCase { private static final OLog log = Tracing.createLoggerFor(CoordinatorTest.class); @Autowired private RepositoryService repositoryService; /** * Test with 2 threads T1 & T2. * T1 T2 * doInSync T1-1 sleep 5sec * sleep 10sec ... * ... ... * ... doInSync T2-1 * ... sleep 10sec * ... ... * doInSync T1-2 ... * finished ... * doInSync T2-2 * finished */ @Test public void testDoInSyncWithSyncerExecutor() { final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1)); final List<Boolean> statusList = Collections.synchronizedList(new ArrayList<Boolean>(1)); final CountDownLatch finishCount = new CountDownLatch(2); final OLATResourceable ores = OresHelper.createOLATResourceableInstance("testDoInSync", new Long("123")); // thread 1 new Thread(new Runnable() { public void run() { try { // do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor(){ public void execute() { log.info("Thread-1: execute doInSync 1"); } });//end syncerCallback // sleep sleep(1000); // do again do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor(){ public void execute() { log.info("Thread-1: execute doInSync 2"); } });//end syncerCallback log.info("Thread-1: finished"); statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { DBFactory.getInstance().closeSession(); } catch (Exception e) { // ignore } finishCount.countDown(); } }}).start(); // thread 2 new Thread(new Runnable() { public void run() { try { // sleep sleep(500); // do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor(){ public void execute() { log.info("Thread-2: execute doInSync 1"); } });//end syncerCallback // sleep sleep(1000); // do again do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor(){ public void execute() { log.info("Thread-2: execute doInSync 2"); } });//end syncerCallback log.info("Thread-2: finished"); statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { DBFactory.getInstance().closeSession(); } catch (Exception e) { // ignore } finishCount.countDown(); } }}).start(); // sleep until t1 and t2 should have terminated/excepted try { finishCount.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { Assert.fail("Threads did not finish in 10sec"); } // if not -> they are in deadlock and the db did not detect it for (Exception exception : exceptionHolder) { log.error("exception: ", exception); } Assert.assertEquals("It throws an exception in test", 0, exceptionHolder.size()); } /** * Test with 2 threads T1 & T2. * T1 T2 * doInSync T1-1 sleep 5sec * sleep 10sec ... * ... ... * ... doInSync T2-1 * ... sleep 10sec * ... ... * doInSync T1-2 ... * finished ... * doInSync T2-2 * finished */ @Test public void testDoInSyncWithSyncerCallback() { final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>(1)); final List<Boolean> statusList = Collections.synchronizedList(new ArrayList<Boolean>(1)); final OLATResourceable ores = OresHelper.createOLATResourceableInstance("testDoInSync", new Long("123")); final CountDownLatch finishCount = new CountDownLatch(2); // thread 1 new Thread(new Runnable() { public void run() { try { // do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("Thread-1: execute doInSync 1"); return Boolean.TRUE; } });//end syncerCallback // sleep sleep(1000); // do again do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("Thread-1: execute doInSync 2"); return Boolean.TRUE; } });//end syncerCallback log.info("Thread-1: finished"); statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { DBFactory.getInstance().closeSession(); } catch (Exception e) { // ignore } finishCount.countDown(); } }}).start(); // thread 2 new Thread(new Runnable() { public void run() { try { // sleep sleep(500); // do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("Thread-2: execute doInSync 1"); return Boolean.TRUE; } });//end syncerCallback // sleep sleep(1000); // do again do something in sync CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("Thread-2: execute doInSync 2"); return Boolean.TRUE; } });//end syncerCallback log.info("Thread-2: finished"); statusList.add(Boolean.TRUE); } catch (Exception e) { exceptionHolder.add(e); } finally { try { DBFactory.getInstance().closeSession(); } catch (Exception e) { // ignore } finishCount.countDown(); } }}).start(); // sleep until t1 and t2 should have terminated/excepted try { finishCount.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { Assert.fail("Test takes too long (more than 10s)"); } // if not -> they are in deadlock and the db did not detect it for (Exception exception : exceptionHolder) { log.error("exception: ", exception); } Assert.assertEquals("It throws an exception in test", 0, exceptionHolder.size()); } @Test(expected = AssertException.class) public void testNestedAssertExceptionInDoInSync() { final OLATResourceable ores = OresHelper.createOLATResourceableInstance("testNestedAssertExceptionInDoInSync", new Long("123")); CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("testNestedAssertExceptionInDoInSync: execute doInSync 1"); // Do agin in sync => nested => no allowed! CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("testNestedAssertExceptionInDoInSync: execute doInSync 2"); fail("No NestedAssertException thrown"); return Boolean.TRUE; } });//end syncerCallback return Boolean.TRUE; } });//end syncerCallback } @Test public void testSyncerAssertAlreadyDoInSyncFor() { final OLATResourceable ores = OresHelper.createOLATResourceableInstance("testSyncerAssertAlreadyDoInSyncFor", new Long("123")); // 1. check assertAlreadyDoInSyncFor WITHOUT sync-block => AssertException must be thrown try { CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(ores); fail("Did not throw AssertException"); } catch (AssertException ex) { log.info("testSyncerAssertAlreadyDoInSyncFor: This exception is ok, exception=" + ex.getMessage()); } // 2.check assertAlreadyDoInSyncFor WITH sync-block => No AssertException should occour try { log.info("testSyncerAssertAlreadyDoInSyncFor: before doInSync"); CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerCallback<Boolean>(){ public Boolean execute() { log.info("testSyncerAssertAlreadyDoInSyncFor: execute before assertAlreadyDoInSyncFor"); CoordinatorManager.getInstance().getCoordinator().getSyncer().assertAlreadyDoInSyncFor(ores); log.info("testSyncerAssertAlreadyDoInSyncFor: execute done"); return Boolean.TRUE; } });//end syncerCallback }catch(AssertException aex) { fail("testSyncerAssertAlreadyDoInSyncFor: got a AssertException=" + aex); } } @Test public void testDoInSyncPerformance() { final OLATResourceable ores = OresHelper.createOLATResourceableInstance(UUID.randomUUID().toString(), new Long("123989456")); OLATResource r = CoreSpringFactory.getImpl(OLATResourceManager.class).findOrPersistResourceable(ores); int maxLoop = 500; final RepositoryEntry re = repositoryService.create("test", "perfTest", "testPerf", "perfTest description", r); // create security group repositoryService.update(re); DBFactory.getInstance().commitAndCloseSession(); // 1. Do job without doInSync log.info("testDoInSyncPerformance: start test with doInSync"); long startTimeWithoutSync = System.currentTimeMillis(); for (int i = 0; i<maxLoop ; i++) { doTestPerformanceJob(re); DBFactory.getInstance().closeSession(); } long endTimeWithoutSync = System.currentTimeMillis(); // 2. Do job with doInSync log.info("testDoInSyncPerformance: start test with doInSync"); long startTimeDoInSync = System.currentTimeMillis(); for (int i = 0; i<maxLoop ; i++) { CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor(){ public void execute() { doTestPerformanceJob(re); } });//end syncerCallback DBFactory.getInstance().closeSession(); } long endTimeDoInSync = System.currentTimeMillis(); // Compare time long timeWithoutSync = endTimeWithoutSync - startTimeWithoutSync; float perJobWithoutSync = (float)timeWithoutSync / maxLoop; log.info("testDoInSyncPerformance timeWithoutSync=" + timeWithoutSync + " ms for loop with " + maxLoop + " iterations"); log.info("testDoInSyncPerformance perJobWithoutSync=" + perJobWithoutSync + " ms"); long timeWithDoInSync = endTimeDoInSync - startTimeDoInSync; float perJobWithDoInSync = (float)timeWithDoInSync / maxLoop; log.info("testDoInSyncPerformance timeWithDoInSync=" + timeWithDoInSync + " ms for loop with " + maxLoop + " iterations"); log.info("testDoInSyncPerformance perJobWithDoInSync=" + perJobWithDoInSync + " ms"); long timeDiffLoop = timeWithDoInSync - timeWithoutSync; float timeDiffPerCall = perJobWithDoInSync - perJobWithoutSync; log.info("testDoInSyncPerformance diffLoop=" + timeDiffLoop + " ms for loop with " + maxLoop + " iterations"); log.info("testDoInSyncPerformance diffPerCall=" + timeDiffPerCall + " ms"); } private Boolean doTestPerformanceJob(RepositoryEntry re) { repositoryService.incrementLaunchCounter(re); return Boolean.TRUE; } }