/* * Copyright (c) 2010-2013, 2015, 2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Martin Fluegge - initial API and implementation */ package org.eclipse.emf.cdo.tests.bugzilla; import org.eclipse.emf.cdo.internal.server.Repository; import org.eclipse.emf.cdo.internal.server.TransactionCommitContext; import org.eclipse.emf.cdo.net4j.CDONet4jSession; import org.eclipse.emf.cdo.server.IRepository; import org.eclipse.emf.cdo.spi.server.InternalCommitContext; import org.eclipse.emf.cdo.spi.server.InternalRepository; import org.eclipse.emf.cdo.spi.server.InternalTransaction; import org.eclipse.emf.cdo.tests.AbstractCDOTest; import org.eclipse.emf.cdo.tests.config.impl.RepositoryConfig; import org.eclipse.emf.cdo.tests.model3.NodeA; import org.eclipse.emf.cdo.tests.model3.NodeB; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.util.CommitException; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.net4j.util.WrappedException; import org.eclipse.emf.ecore.resource.Resource; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Bug 316444. * * @author Martin Fluegge * @since 4.0 */ public class Bugzilla_316444_Test extends AbstractCDOTest { private static final String REPOSITORY_NAME = "repo1"; private static final String RESOURCE_PATH = "/my/resource"; public int idSessionA; public int idSessionB; private int idInitSession; private boolean finishedSessionA = false; private List<Exception> exceptions; @Override public synchronized Map<String, Object> getTestProperties() { Map<String, Object> map = super.getTestProperties(); map.put(IRepository.Props.ENSURE_REFERENTIAL_INTEGRITY, "true"); return map; } @Override protected void doSetUp() throws Exception { exceptions = new ArrayList<Exception>(); createRepository(); super.doSetUp(); } private void createRepository() { Repository repository = new Repository.Default() { private Object monitor = new Object(); private CountDownLatch latch = new CountDownLatch(2); @Override public InternalCommitContext createCommitContext(InternalTransaction transaction) { return new TransactionCommitContext(transaction) { @Override protected void lockObjects() throws InterruptedException { int sessionID = getTransaction().getSession().getSessionID(); if (sessionID == idSessionB) { synchronized (monitor) { // Only wait if Session A has not passed the lockObjects if (!finishedSessionA) { msg("Session B is waiting for Session A"); monitor.wait(DEFAULT_TIMEOUT); msg("Session B stopped waiting"); } else { msg("Session B - no need to wait. A has already passed lockObjects()"); } } } msg("Passing lockObjects() " + getTransaction().getSession()); try { super.lockObjects(); } catch (RuntimeException ex) { latch.countDown(); throw ex; } catch (Exception ex) { latch.countDown(); throw WrappedException.wrap(ex); } msg("Passed lockObjects() " + getTransaction().getSession()); if (sessionID == idSessionA) { synchronized (monitor) { finishedSessionA = true; monitor.notifyAll(); msg("Session A notified others not to wait anymore."); } } // Do nothing for inital session. Otherwise the test will block too early if (sessionID != idInitSession) { latch.countDown(); try { assertEquals(true, latch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)); } catch (InterruptedException ex) { throw WrappedException.wrap(ex); } } } }; } }; Map<String, String> props = getRepositoryProperties(); ((InternalRepository)repository).setProperties(props); repository.setName(REPOSITORY_NAME); Map<String, Object> map = getTestProperties(); map.put(RepositoryConfig.PROP_TEST_REPOSITORY, repository); } /** * Not needed anymore because of {@link Bugzilla_409284_Test}. */ public void _testMovingSubtree() throws Exception { exceptions.clear(); { CDONet4jSession session = (CDONet4jSession)openSession(REPOSITORY_NAME); idInitSession = session.getSessionID(); CDOTransaction transaction = session.openTransaction(); Resource resource = transaction.createResource(getResourcePath(RESOURCE_PATH)); // -------------- create graph begin ------------------------ NodeB root = createSimpleNode("root"); NodeB A = createSimpleNode("A"); NodeB B = createSimpleNode("B"); NodeB C = createSimpleNode("C"); NodeB D = createSimpleNode("D"); NodeB E = createSimpleNode("E"); root.getChildren().add(A); root.getChildren().add(D); A.getChildren().add(B); B.getChildren().add(C); D.getChildren().add(E); resource.getContents().add(root); transaction.commit(); // -------- check for consistency ----------- checkInitialGraph(root, A, B, C, D, E); transaction.close(); session.close(); } // if (!(isConfig(MEM) || isConfig(MEM_AUDITS) || isConfig(MEM_BRANCHES) || isConfig(MEM_OFFLINE))) // { // // do not restart the repository on MEM store // restartRepository(); // } { // Just an additional check to make sure that the graph is stored correctly even after repository restart CDONet4jSession session = (CDONet4jSession)openSession(REPOSITORY_NAME); idInitSession = session.getSessionID(); CDOTransaction transaction = session.openTransaction(); Resource resource = transaction.getResource(getResourcePath(RESOURCE_PATH), true); NodeB root = (NodeB)resource.getContents().get(0); assertEquals("root", root.getName()); NodeB A = getElementFromGraphNodeB(root, "A"); NodeB B = getElementFromGraphNodeB(root, "B"); NodeB C = getElementFromGraphNodeB(root, "C"); NodeB D = getElementFromGraphNodeB(root, "D"); NodeB E = getElementFromGraphNodeB(root, "E"); assertNotNull(A); assertNotNull(B); assertNotNull(C); assertNotNull(D); assertNotNull(E); checkInitialGraph(root, A, B, C, D, E); } { CDONet4jSession session = (CDONet4jSession)openSession(REPOSITORY_NAME); idInitSession = session.getSessionID(); // ----- start threads ----- ThreadA threadA = new ThreadA(RESOURCE_PATH); ThreadB threadB = new ThreadB(RESOURCE_PATH); threadA.start(); sleepIfNeeded(); threadB.start(); threadA.done.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); threadB.done.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); // threadA.join(DEFAULT_TIMEOUT); // threadB.join(DEFAULT_TIMEOUT); if (exceptions.size() > 0) { Exception exception = exceptions.get(0); if (exception instanceof ThreadBShouldHaveThrownAnExceptionException) { fail(exception.getMessage()); } else { throw exception; } } session.close(); msg("finished"); } } public void testLockParentWithEAttributeChange() throws Exception { String resourcePath = RESOURCE_PATH + "1"; { CDONet4jSession session = (CDONet4jSession)openSession(REPOSITORY_NAME); idInitSession = session.getSessionID(); CDOTransaction transaction = session.openTransaction(); Resource resource = transaction.createResource(getResourcePath(resourcePath)); // -------------- create graph begin ------------------------ NodeB root = createSimpleNode("root"); NodeB A = createSimpleNode("A"); NodeB B = createSimpleNode("B"); NodeB C = createSimpleNode("C"); NodeB D = createSimpleNode("D"); NodeB E = createSimpleNode("E"); root.getChildren().add(A); root.getChildren().add(D); A.getChildren().add(B); B.getChildren().add(C); D.getChildren().add(E); resource.getContents().add(root); transaction.commit(); // -------- check for consistency ----------- checkInitialGraph(root, A, B, C, D, E); transaction.close(); session.close(); } // restartRepository(); { CDONet4jSession session = (CDONet4jSession)openSession(REPOSITORY_NAME); idInitSession = session.getSessionID(); // ----- start threads ----- ThreadX threadX = new ThreadX(resourcePath); ThreadA threadA = new ThreadA(resourcePath); threadA.start(); sleepIfNeeded(); threadX.start(); threadA.done.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); threadX.done.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); // threadX.join(DEFAULT_TIMEOUT); // threadA.join(DEFAULT_TIMEOUT); if (exceptions.size() > 0) { throw exceptions.get(0); } } } private void sleepIfNeeded() { if (isConfig(LEGACY)) { // sleep in legacy while Bug 318816 is not solved sleep(1000); } } private void checkInitialGraph(NodeB root, NodeB A, NodeB B, NodeB C, NodeB D, NodeB E) { assertEquals("A", A.getName()); assertEquals("B", B.getName()); assertEquals("C", C.getName()); assertEquals("D", D.getName()); assertEquals("E", E.getName()); assertEquals(root, A.getParent()); assertEquals(root, A.eContainer()); assertEquals(root, D.getParent()); assertEquals(root, D.eContainer()); assertEquals(A, B.getParent()); assertEquals(A, B.eContainer()); assertEquals(B, C.getParent()); assertEquals(B, C.eContainer()); assertEquals(D, E.getParent()); assertEquals(D, E.eContainer()); } private abstract class AbstactTestThread extends Thread { public CountDownLatch done = new CountDownLatch(1); protected final String resourcePath; public AbstactTestThread(String resourcePath) { this.resourcePath = resourcePath; } @Override public final void run() { try { doRun(); } finally { done.countDown(); } } protected abstract void doRun(); } /** * @author Martin Fluegge */ private class ThreadA extends AbstactTestThread { private CDONet4jSession session; public ThreadA(String resourcePath) { super(resourcePath); setName("ThreadA"); msg("Starting Thread A"); session = (CDONet4jSession)openSession(REPOSITORY_NAME); idSessionA = session.getSessionID(); } @Override protected void doRun() { try { msg("Started Thread A " + session); CDOTransaction transaction = session.openTransaction(); Resource resource = transaction.getResource(getResourcePath(resourcePath), true); NodeB root = (NodeB)resource.getContents().get(0); assertEquals("root", root.getName()); NodeB B = getElementFromGraphNodeB(root, "B"); NodeB E = getElementFromGraphNodeB(root, "E"); assertEquals("B", B.getName()); assertEquals("E", E.getName()); E.getChildren().add(B); try { transaction.commit(); } catch (CommitException ex) { exceptions.add(ex); } session.close(); msg("Finished Thread A " + session); } catch (Exception e) { exceptions.add(e); } } } /** * @author Martin Fluegge */ private class ThreadB extends AbstactTestThread { private CDONet4jSession session; public ThreadB(String resourcePath) { super(resourcePath); setName("ThreadB"); msg("Starting Thread B"); session = (CDONet4jSession)openSession(REPOSITORY_NAME); idSessionB = session.getSessionID(); } @Override protected void doRun() { try { msg("Started Thread B " + session); CDOTransaction transaction = session.openTransaction(); Resource resource = transaction.getResource(getResourcePath(resourcePath), true); NodeB root = (NodeB)resource.getContents().get(0); assertEquals("root", root.getName()); NodeB C = getElementFromGraphNodeB(root, "C"); NodeB D = getElementFromGraphNodeB(root, "D"); assertEquals("C", C.getName()); assertEquals("D", D.getName()); C.getChildren().add(D); try { transaction.commit(); } catch (CommitException ex) { try { String message = ex.getMessage(); if (message == null || !message.contains("ContainmentCycleDetectedException")) { throw ex; } msg("Finished (Passed) Thread B " + session); return; } catch (Exception exx) { throw new RuntimeException(exx); } } exceptions.add(new ThreadBShouldHaveThrownAnExceptionException("Thread B should have thrown an exception")); session.close(); } catch (Exception e) { exceptions.add(e); msg("Finished Thread B " + session); } } } /** * @author Martin Fluegge */ private class ThreadX extends AbstactTestThread { private CDONet4jSession session; public ThreadX(String resourcePath) { super(resourcePath); setName("ThreadX"); msg("Starting Thread X"); session = (CDONet4jSession)openSession(REPOSITORY_NAME); idSessionB = session.getSessionID(); } @Override protected void doRun() { try { msg("Started Thread X" + session); CDOTransaction transaction = session.openTransaction(); Resource resource = transaction.getResource(getResourcePath(resourcePath), true); NodeB root = (NodeB)resource.getContents().get(0); assertEquals("root", root.getName()); NodeB D = getElementFromGraphNodeB(root, "D"); assertEquals("D", D.getName()); D.setName("DD"); try { transaction.commit(); } catch (CommitException ex) { exceptions.add(ex); } session.close(); msg("Finished Thread X"); } catch (Exception e) { exceptions.add(e); } } } private NodeB createSimpleNode(String name) { NodeB y = getModel3Factory().createNodeB(); y.setName(name); return y; } private NodeB getElementFromGraphNodeB(final NodeB node, final String name) throws Exception { if (node.getName().equals(name)) { return node; } CDOView view = CDOUtil.getView(node); return view.syncExec(new Callable<NodeB>() { public NodeB call() throws Exception { for (NodeB child : node.getChildren()) { NodeB elementFromGraph = getElementFromGraphNodeB(child, name); if (elementFromGraph != null) { return elementFromGraph; } } return null; } }); } @SuppressWarnings("unused") private NodeA getElementFromGraphNodeA(final NodeA node, final String name) throws Exception { if (node.getName().equals(name)) { return node; } CDOView view = CDOUtil.getView(node); return view.syncExec(new Callable<NodeA>() { public NodeA call() throws Exception { for (NodeA child : node.getChildren()) { NodeA elementFromGraph = getElementFromGraphNodeA(child, name); if (elementFromGraph != null) { return elementFromGraph; } } return null; } }); } /* * @SuppressWarnings("unused") private void restartRepository() { LifecycleUtil.deactivate(getRepository()); * createRepository(); getRepository(); } */ /** * @author Martin Fluegge */ private static class ThreadBShouldHaveThrownAnExceptionException extends Exception { private static final long serialVersionUID = 1L; public ThreadBShouldHaveThrownAnExceptionException(String s) { super(s); } } }