Skip to content

Commit c302c03

Browse files
committed
Merge pull request #3141 from akarnokd/SchedulerLeakCheck
Improved Scheduler.Worker memory leak detection
2 parents ad1fbc2 + 54bb588 commit c302c03

File tree

3 files changed

+110
-59
lines changed

3 files changed

+110
-59
lines changed

src/test/java/rx/schedulers/CachedThreadSchedulerTest.java

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,13 @@
1616

1717
package rx.schedulers;
1818

19-
import java.lang.management.*;
20-
import java.util.concurrent.*;
21-
22-
import junit.framework.Assert;
19+
import static org.junit.Assert.assertTrue;
2320

2421
import org.junit.Test;
2522

26-
import rx.Observable;
27-
import rx.Scheduler;
23+
import rx.*;
24+
import rx.Scheduler.Worker;
2825
import rx.functions.*;
29-
import rx.internal.schedulers.NewThreadWorker;
30-
import static org.junit.Assert.assertTrue;
3126

3227
public class CachedThreadSchedulerTest extends AbstractSchedulerConcurrencyTests {
3328

@@ -74,49 +69,17 @@ public final void testHandledErrorIsNotDeliveredToThreadHandler() throws Interru
7469

7570
@Test(timeout = 30000)
7671
public void testCancelledTaskRetention() throws InterruptedException {
77-
System.out.println("Wait before GC");
78-
Thread.sleep(1000);
79-
80-
System.out.println("GC");
81-
System.gc();
82-
83-
Thread.sleep(1000);
84-
85-
86-
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
87-
MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage();
88-
long initial = memHeap.getUsed();
89-
90-
System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0);
91-
92-
Scheduler.Worker w = Schedulers.io().createWorker();
93-
for (int i = 0; i < 750000; i++) {
94-
if (i % 50000 == 0) {
95-
System.out.println(" -> still scheduling: " + i);
96-
}
97-
w.schedule(Actions.empty(), 1, TimeUnit.DAYS);
72+
Worker w = Schedulers.io().createWorker();
73+
try {
74+
ExecutorSchedulerTest.testCancelledRetention(w, false);
75+
} finally {
76+
w.unsubscribe();
9877
}
99-
100-
memHeap = memoryMXBean.getHeapMemoryUsage();
101-
long after = memHeap.getUsed();
102-
System.out.printf("Peak: %.3f MB%n", after / 1024.0 / 1024.0);
103-
104-
w.unsubscribe();
105-
106-
System.out.println("Wait before second GC");
107-
Thread.sleep(NewThreadWorker.PURGE_FREQUENCY + 2000);
108-
109-
System.out.println("Second GC");
110-
System.gc();
111-
112-
Thread.sleep(1000);
113-
114-
memHeap = memoryMXBean.getHeapMemoryUsage();
115-
long finish = memHeap.getUsed();
116-
System.out.printf("After: %.3f MB%n", finish / 1024.0 / 1024.0);
117-
118-
if (finish > initial * 5) {
119-
Assert.fail(String.format("Tasks retained: %.3f -> %.3f -> %.3f", initial / 1024 / 1024.0, after / 1024 / 1024.0, finish / 1024 / 1024d));
78+
w = Schedulers.io().createWorker();
79+
try {
80+
ExecutorSchedulerTest.testCancelledRetention(w, true);
81+
} finally {
82+
w.unsubscribe();
12083
}
12184
}
12285

src/test/java/rx/schedulers/ComputationSchedulerTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import rx.Observable;
2828
import rx.Scheduler;
29+
import rx.Scheduler.Worker;
2930
import rx.functions.Action0;
3031
import rx.functions.Action1;
3132
import rx.functions.Func1;
@@ -151,4 +152,20 @@ public final void testUnhandledErrorIsDeliveredToThreadHandler() throws Interrup
151152
public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException {
152153
SchedulerTests.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler());
153154
}
155+
156+
@Test(timeout = 30000)
157+
public void testCancelledTaskRetention() throws InterruptedException {
158+
Worker w = Schedulers.computation().createWorker();
159+
try {
160+
ExecutorSchedulerTest.testCancelledRetention(w, false);
161+
} finally {
162+
w.unsubscribe();
163+
}
164+
w = Schedulers.computation().createWorker();
165+
try {
166+
ExecutorSchedulerTest.testCancelledRetention(w, true);
167+
} finally {
168+
w.unsubscribe();
169+
}
170+
}
154171
}

src/test/java/rx/schedulers/ExecutorSchedulerTest.java

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public final void testUnhandledErrorIsDeliveredToThreadHandler() throws Interrup
4848
public final void testHandledErrorIsNotDeliveredToThreadHandler() throws InterruptedException {
4949
SchedulerTests.testHandledErrorIsNotDeliveredToThreadHandler(getScheduler());
5050
}
51-
@Test(timeout = 30000)
52-
public void testCancelledTaskRetention() throws InterruptedException {
51+
52+
public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) throws InterruptedException {
5353
System.out.println("Wait before GC");
5454
Thread.sleep(1000);
5555

@@ -64,13 +64,32 @@ public void testCancelledTaskRetention() throws InterruptedException {
6464
long initial = memHeap.getUsed();
6565

6666
System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0);
67-
68-
Scheduler.Worker w = Schedulers.io().createWorker();
69-
for (int i = 0; i < 500000; i++) {
70-
if (i % 50000 == 0) {
71-
System.out.println(" -> still scheduling: " + i);
67+
68+
int n = 500 * 1000;
69+
if (periodic) {
70+
final CountDownLatch cdl = new CountDownLatch(n);
71+
final Action0 action = new Action0() {
72+
@Override
73+
public void call() {
74+
cdl.countDown();
75+
}
76+
};
77+
for (int i = 0; i < n; i++) {
78+
if (i % 50000 == 0) {
79+
System.out.println(" -> still scheduling: " + i);
80+
}
81+
w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS);
82+
}
83+
84+
System.out.println("Waiting for the first round to finish...");
85+
cdl.await();
86+
} else {
87+
for (int i = 0; i < n; i++) {
88+
if (i % 50000 == 0) {
89+
System.out.println(" -> still scheduling: " + i);
90+
}
91+
w.schedule(Actions.empty(), 1, TimeUnit.DAYS);
7292
}
73-
w.schedule(Actions.empty(), 1, TimeUnit.DAYS);
7493
}
7594

7695
memHeap = memoryMXBean.getHeapMemoryUsage();
@@ -95,7 +114,30 @@ public void testCancelledTaskRetention() throws InterruptedException {
95114
fail(String.format("Tasks retained: %.3f -> %.3f -> %.3f", initial / 1024 / 1024.0, after / 1024 / 1024.0, finish / 1024 / 1024d));
96115
}
97116
}
98-
117+
118+
@Test(timeout = 30000)
119+
public void testCancelledTaskRetention() throws InterruptedException {
120+
ExecutorService exec = Executors.newSingleThreadExecutor();
121+
Scheduler s = Schedulers.from(exec);
122+
try {
123+
Scheduler.Worker w = s.createWorker();
124+
try {
125+
testCancelledRetention(w, false);
126+
} finally {
127+
w.unsubscribe();
128+
}
129+
130+
w = s.createWorker();
131+
try {
132+
testCancelledRetention(w, true);
133+
} finally {
134+
w.unsubscribe();
135+
}
136+
} finally {
137+
exec.shutdownNow();
138+
}
139+
}
140+
99141
/** A simple executor which queues tasks and executes them one-by-one if executeOne() is called. */
100142
static final class TestExecutor implements Executor {
101143
final ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<Runnable>();
@@ -204,4 +246,33 @@ public void execute(Runnable command) {
204246

205247
assertFalse(w.tasks.hasSubscriptions());
206248
}
249+
250+
@Test
251+
public void testNoPeriodicTimedTaskPartRetention() throws InterruptedException {
252+
Executor e = new Executor() {
253+
@Override
254+
public void execute(Runnable command) {
255+
command.run();
256+
}
257+
};
258+
ExecutorSchedulerWorker w = (ExecutorSchedulerWorker)Schedulers.from(e).createWorker();
259+
260+
final CountDownLatch cdl = new CountDownLatch(1);
261+
final Action0 action = new Action0() {
262+
@Override
263+
public void call() {
264+
cdl.countDown();
265+
}
266+
};
267+
268+
Subscription s = w.schedulePeriodically(action, 0, 1, TimeUnit.DAYS);
269+
270+
assertTrue(w.tasks.hasSubscriptions());
271+
272+
cdl.await();
273+
274+
s.unsubscribe();
275+
276+
assertFalse(w.tasks.hasSubscriptions());
277+
}
207278
}

0 commit comments

Comments
 (0)