/* * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved. * * 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 reactor.core.publisher; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import reactor.core.Exceptions; import reactor.core.Fuseable; import reactor.core.publisher.Operators.MonoSubscriber; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import reactor.test.subscriber.AssertSubscriber; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class MonoSubscriberTest { @Test public void queueSubscriptionSyncRejected() { MonoSubscriber<Integer, Integer> ds = new MonoSubscriber<>(new AssertSubscriber<>()); assertEquals(Fuseable.NONE, ds.requestFusion(Fuseable.SYNC)); } @Test public void clear() { MonoSubscriber<Integer, Integer> ds = new MonoSubscriber<>(new AssertSubscriber<>()); ds.value = 1; ds.clear(); assertEquals(MonoSubscriber.FUSED_CONSUMED, ds.state); assertNull(ds.value); } @Test public void completeCancelRace() { for (int i = 0; i < 500; i++) { final MonoSubscriber<Integer, Integer> ds = new MonoSubscriber<>(new AssertSubscriber<>()); Runnable r1 = () -> ds.complete(1); Runnable r2 = ds::cancel; race(r1, r2, Schedulers.single()); } } @Test public void requestClearRace() { for (int i = 0; i < 5000; i++) { AssertSubscriber<Integer> ts = new AssertSubscriber<Integer>(0L); final MonoSubscriber<Integer, Integer> ds = new MonoSubscriber<>(ts); ts.onSubscribe(ds); ds.complete(1); Runnable r1 = () -> ds.request(1); Runnable r2 = () -> ds.value = null; race(r1, r2, Schedulers.single()); if (ts.values().size() >= 1) { ts.assertValues(1); } } } @Test public void requestCancelRace() { for (int i = 0; i < 5000; i++) { AssertSubscriber<Integer> ts = new AssertSubscriber<>(0L); final MonoSubscriber<Integer, Integer> ds = new MonoSubscriber<>(ts); ts.onSubscribe(ds); ds.complete(1); Runnable r1 = () -> ds.request(1); Runnable r2 = ds::cancel; race(r1, r2, Schedulers.single()); if (ts.values().size() >= 1) { ts.assertValues(1); } } } /** * Synchronizes the execution of two runnables (as much as possible) * to test race conditions. * <p>The method blocks until both have run to completion. * @param r1 the first runnable * @param r2 the second runnable * @param s the scheduler to use */ //TODO pull into reactor-tests? public static void race(final Runnable r1, final Runnable r2, Scheduler s) { final AtomicInteger count = new AtomicInteger(2); final CountDownLatch cdl = new CountDownLatch(2); final Throwable[] errors = { null, null }; s.schedule(() -> { if (count.decrementAndGet() != 0) { while (count.get() != 0) { } } try { try { r1.run(); } catch (Throwable ex) { errors[0] = ex; } } finally { cdl.countDown(); } }); if (count.decrementAndGet() != 0) { while (count.get() != 0) { } } try { try { r2.run(); } catch (Throwable ex) { errors[1] = ex; } } finally { cdl.countDown(); } try { if (!cdl.await(5, TimeUnit.SECONDS)) { throw new AssertionError("The wait timed out!"); } } catch (InterruptedException ex) { throw new RuntimeException(ex); } if (errors[0] != null && errors[1] == null) { throw Exceptions.propagate(errors[0]); } if (errors[0] == null && errors[1] != null) { throw Exceptions.propagate(errors[1]); } if (errors[0] != null && errors[1] != null) { errors[0].addSuppressed(errors[1]); throw Exceptions.propagate(errors[0]); } } }