aboutsummaryrefslogtreecommitdiffstats
path: root/bcel-builder/src/test/java/org/aspectj/apache/bcel/classfile/tests/LocalVariableTableConcurrencyTest.java
blob: e0fe9a56729754724a3293fbce77e0e7dad96a38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/* *******************************************************************
 * Copyright (c) 2004 IBM
 * 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
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Dmitry Mikhaylov - initial implementation
 * ******************************************************************/

package org.aspectj.apache.bcel.classfile.tests;

import org.aspectj.apache.bcel.classfile.Code;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.classfile.LocalVariableTable;
import org.aspectj.apache.bcel.classfile.Method;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicReference;


public class LocalVariableTableConcurrencyTest extends BcelTestCase {

	private final int nThreads = Runtime.getRuntime().availableProcessors();

	private final ExecutorService[] workers = new ExecutorService[nThreads];

	private LocalVariableTable reference;

	protected void setUp() throws Exception {
		super.setUp();

		for (int i = 0; i < nThreads; i++) workers[i] = Executors.newSingleThreadExecutor();

		JavaClass clazz = getClassFromJar("SimpleGenericsProgram");
		Method mainMethod = getMethod(clazz,"main");
		Code codeAttr = (Code) findAttribute("Code",mainMethod.getAttributes());
		reference = (LocalVariableTable) findAttribute("LocalVariableTable",codeAttr.getAttributes());
	}

	/**
	 * Try to hit concurrency bug in org.aspectj.apache.bcel.classfile.LocalVariableTable.unpack().
	 * We do so by running unpack() on same instance with multiple threads, and artificially
	 * delaying all threads but first so that they enter unpack() the moment first thread is about to leave it.
	 *
	 * Since this test relies on empirically obtained access pattern and number of iterations,
	 * it never has 100% probability of hitting the bug. If it fails - there is certainly a bug.
	 * If it passes, it could mean anything - fully correct code or slightly changed execution order preventing
	 * threads to collide at problematic location.
	 *
	 * As such, it is not really good for unit testing.
	 */
	public void testLocalVariableTableAttributeConcurrency() throws RuntimeException, InterruptedException {
		final AtomicReference<RuntimeException> error = new AtomicReference<>();
		for (int i = 0; i < 10000; i++) {
			LocalVariableTable sharedInstance = reference.copyFromPackedState();
			CountDownLatch preStart = new CountDownLatch(nThreads);
			Semaphore start = new Semaphore(0);
			CountDownLatch finish = new CountDownLatch(nThreads);

			for (int j = 0; j < nThreads; j++) {
				final boolean needsDelay = j > 0;
				workers[j].execute(() -> {
					preStart.countDown();
					start.acquireUninterruptibly();
					// trying to trigger concurrent unpacking bug - one tread should enter unpack() when other is about to leave it
					if (needsDelay) reference.copyFromPackedState().getTableLength();
					try {
						sharedInstance.getTableLength();
					}
					catch (RuntimeException ex) {
						error.compareAndSet(null, ex);
					}
					finish.countDown();
				});
			}

			preStart.await();
			start.release(nThreads);
			finish.await();

			if (error.get() != null) throw error.get();
		}
	}

	protected void tearDown() throws Exception {
		for (int i = 0; i < nThreads; i++) if (workers[i] != null) workers[i].shutdownNow();
		super.tearDown();
	}
}