/** * 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; } } }