/**
* Copyright 2014 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rx.internal.operators;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.observers.TestObserver;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
public class OperatorUnsubscribeOnTest {
@Test
public void testUnsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() throws InterruptedException {
UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler();
try {
final ThreadSubscription subscription = new ThreadSubscription();
final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>();
Observable<Integer> w = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> t1) {
subscribeThread.set(Thread.currentThread());
t1.add(subscription);
t1.onNext(1);
t1.onNext(2);
t1.onCompleted();
}
});
TestObserver<Integer> observer = new TestObserver<Integer>();
w.subscribeOn(UI_EVENT_LOOP).observeOn(Schedulers.computation()).unsubscribeOn(UI_EVENT_LOOP).subscribe(observer);
Thread unsubscribeThread = subscription.getThread();
assertNotNull(unsubscribeThread);
assertNotSame(Thread.currentThread(), unsubscribeThread);
assertNotNull(subscribeThread.get());
assertNotSame(Thread.currentThread(), subscribeThread.get());
// True for Schedulers.newThread()
System.out.println("unsubscribeThread: " + unsubscribeThread);
System.out.println("subscribeThread.get(): " + subscribeThread.get());
assertTrue(unsubscribeThread == UI_EVENT_LOOP.getThread());
observer.assertReceivedOnNext(Arrays.asList(1, 2));
observer.assertTerminalEvent();
} finally {
UI_EVENT_LOOP.shutdown();
}
}
@Test
public void testUnsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads() throws InterruptedException {
UIEventLoopScheduler UI_EVENT_LOOP = new UIEventLoopScheduler();
try {
final ThreadSubscription subscription = new ThreadSubscription();
final AtomicReference<Thread> subscribeThread = new AtomicReference<Thread>();
Observable<Integer> w = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> t1) {
subscribeThread.set(Thread.currentThread());
t1.add(subscription);
t1.onNext(1);
t1.onNext(2);
t1.onCompleted();
}
});
TestObserver<Integer> observer = new TestObserver<Integer>();
w.subscribeOn(Schedulers.newThread()).observeOn(Schedulers.computation()).unsubscribeOn(UI_EVENT_LOOP).subscribe(observer);
Thread unsubscribeThread = subscription.getThread();
assertNotNull(unsubscribeThread);
assertNotSame(Thread.currentThread(), unsubscribeThread);
assertNotNull(subscribeThread.get());
assertNotSame(Thread.currentThread(), subscribeThread.get());
// True for Schedulers.newThread()
System.out.println("unsubscribeThread: " + unsubscribeThread);
System.out.println("subscribeThread.get(): " + subscribeThread.get());
assertTrue(unsubscribeThread == UI_EVENT_LOOP.getThread());
observer.assertReceivedOnNext(Arrays.asList(1, 2));
observer.assertTerminalEvent();
} finally {
UI_EVENT_LOOP.shutdown();
}
}
private static class ThreadSubscription implements Subscription {
private volatile Thread thread;
private final CountDownLatch latch = new CountDownLatch(1);
private final Subscription s = Subscriptions.create(new Action0() {
@Override
public void call() {
System.out.println("unsubscribe invoked: " + Thread.currentThread());
thread = Thread.currentThread();
latch.countDown();
}
});
@Override
public void unsubscribe() {
s.unsubscribe();
}
@Override
public boolean isUnsubscribed() {
return s.isUnsubscribed();
}
public Thread getThread() throws InterruptedException {
latch.await();
return thread;
}
}
public static class UIEventLoopScheduler extends Scheduler {
private final Scheduler.Worker eventLoop;
private final Subscription s;
private volatile Thread t;
public UIEventLoopScheduler() {
eventLoop = Schedulers.newThread().createWorker();
s = eventLoop;
/*
* DON'T DO THIS IN PRODUCTION CODE
*/
final CountDownLatch latch = new CountDownLatch(1);
eventLoop.schedule(new Action0() {
@Override
public void call() {
t = Thread.currentThread();
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException("failed to initialize and get inner thread");
}
}
@Override
public Worker createWorker() {
return eventLoop;
}
public void shutdown() {
s.unsubscribe();
}
public Thread getThread() {
return t;
}
}
}