45 import org.testng.annotations.Test;
46
47 import static java.util.concurrent.TimeUnit.MILLISECONDS;
48
49 import java.lang.ref.ReferenceQueue;
50 import java.lang.ref.WeakReference;
51 import java.lang.invoke.MethodHandles;
52 import java.lang.invoke.VarHandle;
53 import java.util.ArrayList;
54 import java.util.concurrent.CountDownLatch;
55 import java.util.concurrent.ForkJoinPool;
56 import java.util.concurrent.ForkJoinTask;
57 import java.util.concurrent.RecursiveAction;
58 import java.util.concurrent.ThreadLocalRandom;
59 import java.util.concurrent.locks.ReentrantLock;
60 import java.util.function.BooleanSupplier;
61
62 @Test
63 public class FJExceptionTableLeak {
64 final ThreadLocalRandom rnd = ThreadLocalRandom.current();
65 final VarHandle NEXT, EX;
66 final Object[] exceptionTable;
67 final ReentrantLock exceptionTableLock;
68
69 FJExceptionTableLeak() throws ReflectiveOperationException {
70 MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(
71 ForkJoinTask.class, MethodHandles.lookup());
72 Class<?> nodeClass = Class.forName(
73 ForkJoinTask.class.getName() + "$ExceptionNode");
74 VarHandle exceptionTableHandle = lookup.findStaticVarHandle(
75 ForkJoinTask.class, "exceptionTable", arrayClass(nodeClass));
76 VarHandle exceptionTableLockHandle = lookup.findStaticVarHandle(
77 ForkJoinTask.class, "exceptionTableLock", ReentrantLock.class);
78 exceptionTable = (Object[]) exceptionTableHandle.get();
79 exceptionTableLock = (ReentrantLock) exceptionTableLockHandle.get();
80
81 NEXT = lookup.findVarHandle(nodeClass, "next", nodeClass);
82 EX = lookup.findVarHandle(nodeClass, "ex", Throwable.class);
83 }
84
85 static <T> Class<T[]> arrayClass(Class<T> klazz) {
86 try {
87 return (Class<T[]>) Class.forName("[L" + klazz.getName() + ";");
88 } catch (ReflectiveOperationException ex) {
89 throw new Error(ex);
98 public void compute() { throw new FailingTaskException(); }
99 }
100
101 /** Counts all FailingTaskExceptions still recorded in exceptionTable. */
102 int retainedExceptions() {
103 exceptionTableLock.lock();
104 try {
105 int count = 0;
106 for (Object node : exceptionTable)
107 for (; node != null; node = next(node))
108 if (ex(node) instanceof FailingTaskException)
109 count++;
110 return count;
111 } finally {
112 exceptionTableLock.unlock();
113 }
114 }
115
116 @Test
117 public void exceptionTableCleanup() throws Exception {
118 ArrayList<FailingTask> failedTasks = failedTasks();
119
120 // Retain a strong ref to one last failing task
121 FailingTask lastTask = failedTasks.get(rnd.nextInt(failedTasks.size()));
122
123 // Clear all other strong refs, making exception table cleanable
124 failedTasks.clear();
125
126 BooleanSupplier exceptionTableIsClean = () -> {
127 try {
128 // Trigger exception table expunging as side effect
129 lastTask.join();
130 throw new AssertionError("should throw");
131 } catch (FailingTaskException expected) {}
132 int count = retainedExceptions();
133 if (count == 0)
134 throw new AssertionError("expected to find last task");
135 return count == 1;
136 };
137 gcAwait(exceptionTableIsClean);
|
45 import org.testng.annotations.Test;
46
47 import static java.util.concurrent.TimeUnit.MILLISECONDS;
48
49 import java.lang.ref.ReferenceQueue;
50 import java.lang.ref.WeakReference;
51 import java.lang.invoke.MethodHandles;
52 import java.lang.invoke.VarHandle;
53 import java.util.ArrayList;
54 import java.util.concurrent.CountDownLatch;
55 import java.util.concurrent.ForkJoinPool;
56 import java.util.concurrent.ForkJoinTask;
57 import java.util.concurrent.RecursiveAction;
58 import java.util.concurrent.ThreadLocalRandom;
59 import java.util.concurrent.locks.ReentrantLock;
60 import java.util.function.BooleanSupplier;
61
62 @Test
63 public class FJExceptionTableLeak {
64 final ThreadLocalRandom rnd = ThreadLocalRandom.current();
65 VarHandle NEXT, EX;
66 Object[] exceptionTable;
67 ReentrantLock exceptionTableLock;
68
69 FJExceptionTableLeak() {
70 // initialize separately to allow to pass with FJ versions without table
71 }
72
73 void init() throws ReflectiveOperationException {
74 MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(
75 ForkJoinTask.class, MethodHandles.lookup());
76 Class<?> nodeClass = Class.forName(
77 ForkJoinTask.class.getName() + "$ExceptionNode");
78 VarHandle exceptionTableHandle = lookup.findStaticVarHandle(
79 ForkJoinTask.class, "exceptionTable", arrayClass(nodeClass));
80 VarHandle exceptionTableLockHandle = lookup.findStaticVarHandle(
81 ForkJoinTask.class, "exceptionTableLock", ReentrantLock.class);
82 exceptionTable = (Object[]) exceptionTableHandle.get();
83 exceptionTableLock = (ReentrantLock) exceptionTableLockHandle.get();
84
85 NEXT = lookup.findVarHandle(nodeClass, "next", nodeClass);
86 EX = lookup.findVarHandle(nodeClass, "ex", Throwable.class);
87 }
88
89 static <T> Class<T[]> arrayClass(Class<T> klazz) {
90 try {
91 return (Class<T[]>) Class.forName("[L" + klazz.getName() + ";");
92 } catch (ReflectiveOperationException ex) {
93 throw new Error(ex);
102 public void compute() { throw new FailingTaskException(); }
103 }
104
105 /** Counts all FailingTaskExceptions still recorded in exceptionTable. */
106 int retainedExceptions() {
107 exceptionTableLock.lock();
108 try {
109 int count = 0;
110 for (Object node : exceptionTable)
111 for (; node != null; node = next(node))
112 if (ex(node) instanceof FailingTaskException)
113 count++;
114 return count;
115 } finally {
116 exceptionTableLock.unlock();
117 }
118 }
119
120 @Test
121 public void exceptionTableCleanup() throws Exception {
122 try {
123 init();
124 } catch (ReflectiveOperationException ex) {
125 return; // using FJ Version without Exception table
126 }
127 ArrayList<FailingTask> failedTasks = failedTasks();
128
129 // Retain a strong ref to one last failing task
130 FailingTask lastTask = failedTasks.get(rnd.nextInt(failedTasks.size()));
131
132 // Clear all other strong refs, making exception table cleanable
133 failedTasks.clear();
134
135 BooleanSupplier exceptionTableIsClean = () -> {
136 try {
137 // Trigger exception table expunging as side effect
138 lastTask.join();
139 throw new AssertionError("should throw");
140 } catch (FailingTaskException expected) {}
141 int count = retainedExceptions();
142 if (count == 0)
143 throw new AssertionError("expected to find last task");
144 return count == 1;
145 };
146 gcAwait(exceptionTableIsClean);
|