| /* |
| * Copyright (c) 2014, 2018 CEA and others. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * Christian W. Damus (CEA) - initial API and implementation |
| * Ed Willink - 529547 |
| * Kenn Hussey - 535301 |
| * |
| */ |
| package org.eclipse.uml2.uml.bug.tests; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| import junit.framework.Test; |
| import junit.framework.TestCase; |
| import junit.framework.TestSuite; |
| |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.uml2.common.util.CacheAdapter; |
| import org.eclipse.uml2.uml.resource.UMLResource; |
| import org.eclipse.uml2.uml.tests.util.StandaloneSupport; |
| |
| /** |
| * Tests concurrent access to the {@link CacheAdapter} from multiple threads. |
| */ |
| public class Bug332057Test |
| extends TestCase { |
| |
| private static final boolean DEBUG = false; |
| |
| private static final int TIMEOUT = 5; |
| |
| private static final int THREADS = 19; |
| |
| private static final int ITERATIONS = 5; |
| |
| private static final int SAMPLES = 10; |
| |
| private ThreadGroup group; |
| |
| private CountDownLatch latch; |
| |
| private final Collection<Throwable> exceptions = Collections |
| .synchronizedCollection(new ArrayList<Throwable>()); |
| |
| public Bug332057Test() { |
| super(); |
| } |
| |
| public Bug332057Test(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| return new TestSuite(Bug332057Test.class, "Bug 332057 tests"); //$NON-NLS-1$ |
| } |
| |
| // |
| // Tests |
| // |
| |
| public void testConcurrentCacheAdapterAccess() { |
| doTestConcurrentCacheAdapterAccess(THREADS, ITERATIONS); |
| } |
| |
| // rename to run statistics on multiple concurrent thread performance |
| public void _testMultiThreadPerformance() { |
| doStatistics(THREADS, ITERATIONS); |
| } |
| |
| // rename to run statistics on single thread performance |
| public void _testSingleThreadPerformance() { |
| // this single thread does as much work as the parallel case |
| doStatistics(1, THREADS * ITERATIONS); |
| } |
| |
| void doTestConcurrentCacheAdapterAccess(final int numThreads, |
| final int iterations) { |
| |
| latch = new CountDownLatch(numThreads); |
| |
| for (int i = 1; i <= numThreads; i++) { |
| createThread(i, iterations); |
| } |
| |
| try { |
| assertTrue("Deadlock occurred", |
| latch.await(60 * TIMEOUT, TimeUnit.SECONDS)); |
| assertTrue("Some thread failed with an exception.", |
| exceptions.isEmpty()); |
| } catch (InterruptedException e) { |
| fail("Test interrupted"); |
| } |
| } |
| |
| void doStatistics(final int numThreads, final int iterations) { |
| final StatsCounter stats = new StatsCounter(); |
| |
| for (int i = 0; i <= SAMPLES; i++) { // 1 extra to warm up |
| stats.start(); |
| |
| try { |
| doTestConcurrentCacheAdapterAccess(numThreads, iterations); |
| } finally { |
| long elapsed = stats.sample(); |
| System.out.printf("Finished in %d.%03d seconds.%n", |
| elapsed / 1000L, elapsed % 1000L); |
| System.out.flush(); |
| } |
| } |
| |
| System.out.printf("Mean: %.2f seconds (std dev: %.2f seconds).%n", |
| stats.mean(), stats.stddev()); |
| System.out.flush(); |
| } |
| |
| // |
| // Test framework |
| // |
| |
| @Override |
| protected void setUp() |
| throws Exception { |
| |
| group = new ThreadGroup("CacheAdapterTest"); |
| group.setDaemon(true); |
| } |
| |
| private Thread createThread(int index, final int iterations) { |
| Thread result = new Thread(group, new Runnable() { |
| |
| public void run() { |
| try { |
| threadLoop(iterations); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| exceptions.add(e); |
| } finally { |
| latch.countDown(); |
| } |
| } |
| }, String.format("%s-%d", group.getName(), index)); |
| |
| result.setDaemon(true); |
| result.start(); |
| return result; |
| } |
| |
| void threadLoop(final int iterations) { |
| for (int i = 1; i <= iterations; i++) { |
| if (DEBUG) { |
| System.out.printf( |
| "Thread \"%s\" loading UML metamodel, pass %d.%n", Thread |
| .currentThread().getName(), i); |
| } |
| |
| ResourceSet rset = createResourceSet(); |
| Resource uml = rset.getResource( |
| URI.createURI(UMLResource.UML_METAMODEL_URI), true); |
| EcoreUtil.resolveAll(uml); |
| destroyResourceSet(rset); |
| |
| if (DEBUG) { |
| System.out.printf( |
| "Thread \"%s\" unloaded UML metamodel, pass %d.%n", Thread |
| .currentThread().getName(), i); |
| } |
| } |
| } |
| |
| private ResourceSet createResourceSet() { |
| ResourceSet result = new ResourceSetImpl(); |
| |
| result.eAdapters().add(new CacheAdapter()); |
| |
| if (StandaloneSupport.isStandalone()) { |
| // init touches some global registries, which may not be accessed |
| // concurrently by multiple threads, so be careful to avoid |
| // concurrent modifications and indices out of bounds |
| synchronized (this) { |
| StandaloneSupport.init(result); |
| } |
| } |
| |
| return result; |
| } |
| |
| private void destroyResourceSet(ResourceSet rset) { |
| for (Resource next : rset.getResources()) { |
| next.unload(); |
| next.eAdapters().clear(); |
| } |
| |
| rset.getResources().clear(); |
| rset.eAdapters().clear(); |
| } |
| |
| private static final class StatsCounter { |
| |
| private final double[] samples = new double[SAMPLES]; |
| |
| private int count = 0; |
| |
| private boolean warmedUp; |
| |
| private long start; |
| |
| void start() { |
| start = System.currentTimeMillis(); |
| } |
| |
| long sample() { |
| long result = System.currentTimeMillis() - start; |
| |
| // discard the first sample as it represents the cold state |
| if (warmedUp) { |
| samples[count++] = ((double) result) / 1000.0; |
| } else { |
| warmedUp = true; |
| } |
| |
| return result; |
| } |
| |
| double mean() { |
| double result = 0; |
| |
| for (int i = 0; i < count; i++) { |
| result = result + samples[i]; |
| } |
| |
| result = result / ((double) count); |
| |
| return result; |
| } |
| |
| double stddev() { |
| final double mean = mean(); |
| double sumOfDevSqs = 0.0; |
| |
| for (int i = 0; i < count; i++) { |
| double dev = samples[i] - mean; |
| sumOfDevSqs = sumOfDevSqs + (dev * dev); |
| } |
| |
| return Math.sqrt(sumOfDevSqs / ((double) (count - 1))); |
| } |
| } |
| } |