/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.flink.streaming.runtime.tasks; import org.apache.flink.core.testutils.OneShotLatch; import org.apache.flink.streaming.runtime.operators.TestProcessingTimeServiceTest.ReferenceSettingExceptionHandler; import org.apache.flink.util.TestLogger; import org.junit.Test; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class SystemProcessingTimeServiceTest extends TestLogger { @Test public void testTriggerHoldsLock() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); try { assertEquals(0, timer.getNumTasksScheduled()); // schedule something ScheduledFuture<?> future = timer.registerTimer(System.currentTimeMillis(), new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) { assertTrue(Thread.holdsLock(lock)); } }); // wait until the execution is over future.get(); assertEquals(0, timer.getNumTasksScheduled()); // check that no asynchronous error was reported if (errorRef.get() != null) { throw new Exception(errorRef.get()); } } finally { timer.shutdownService(); } } /** * Tests that the schedule at fixed rate callback is called under the given lock */ @Test public void testScheduleAtFixedRateHoldsLock() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); final OneShotLatch awaitCallback = new OneShotLatch(); try { assertEquals(0, timer.getNumTasksScheduled()); // schedule something ScheduledFuture<?> future = timer.scheduleAtFixedRate( new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) { assertTrue(Thread.holdsLock(lock)); awaitCallback.trigger(); } }, 0L, 100L); // wait until the first execution is active awaitCallback.await(); // cancel periodic callback future.cancel(true); // check that no asynchronous error was reported if (errorRef.get() != null) { throw new Exception(errorRef.get()); } } finally { timer.shutdownService(); } } /** * Tests that SystemProcessingTimeService#scheduleAtFixedRate is actually triggered multiple * times. */ @Test(timeout=10000) public void testScheduleAtFixedRate() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final long period = 10L; final int countDown = 3; final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); final CountDownLatch countDownLatch = new CountDownLatch(countDown); try { timer.scheduleAtFixedRate(new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { countDownLatch.countDown(); } }, 0L, period); countDownLatch.await(); if (errorRef.get() != null) { throw new Exception(errorRef.get()); } } finally { timer.shutdownService(); } } /** * Tests that shutting down the SystemProcessingTimeService will also cancel the scheduled at * fix rate future. */ @Test public void testQuiesceAndAwaitingCancelsScheduledAtFixRateFuture() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final long period = 10L; final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); try { ScheduledFuture<?> scheduledFuture = timer.scheduleAtFixedRate(new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { } }, 0L, period); assertFalse(scheduledFuture.isDone()); // this should cancel our future timer.quiesceAndAwaitPending(); // it may be that the cancelled status is not immediately visible after the // termination (not necessary a volatile update), so we need to "get()" the cancellation // exception to be on the safe side try { scheduledFuture.get(); fail("scheduled future is not cancelled"); } catch (CancellationException ignored) { // expected } scheduledFuture = timer.scheduleAtFixedRate(new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { throw new Exception("Test exception."); } }, 0L, 100L); assertNotNull(scheduledFuture); assertEquals(0, timer.getNumTasksScheduled()); if (errorRef.get() != null) { throw new Exception(errorRef.get()); } } finally { timer.shutdownService(); } } @Test public void testImmediateShutdown() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); try { assertFalse(timer.isTerminated()); final OneShotLatch latch = new OneShotLatch(); // the task should trigger immediately and should block until terminated with interruption timer.registerTimer(System.currentTimeMillis(), new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { latch.trigger(); Thread.sleep(100000000); } }); latch.await(); timer.shutdownService(); // can only enter this scope after the triggerable is interrupted //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (lock) { assertTrue(timer.isTerminated()); } try { timer.registerTimer(System.currentTimeMillis() + 1000, new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) {} }); fail("should result in an exception"); } catch (IllegalStateException e) { // expected } try { timer.scheduleAtFixedRate( new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) {} }, 0L, 100L); fail("should result in an exception"); } catch (IllegalStateException e) { // expected } // obviously, we have an asynchronous interrupted exception assertNotNull(errorRef.get()); assertTrue(errorRef.get().getCause() instanceof InterruptedException); assertEquals(0, timer.getNumTasksScheduled()); } finally { timer.shutdownService(); } } @Test public void testQuiescing() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); try { final OneShotLatch latch = new OneShotLatch(); final ReentrantLock scopeLock = new ReentrantLock(); timer.registerTimer(System.currentTimeMillis() + 20, new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { scopeLock.lock(); try { latch.trigger(); // delay a bit before leaving the method Thread.sleep(5); } finally { scopeLock.unlock(); } } }); // after the task triggered, shut the timer down cleanly, waiting for the task to finish latch.await(); timer.quiesceAndAwaitPending(); // should be able to immediately acquire the lock, since the task must have exited by now assertTrue(scopeLock.tryLock()); // should be able to schedule more tasks (that never get executed) ScheduledFuture<?> future = timer.registerTimer(System.currentTimeMillis() - 5, new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { throw new Exception("test"); } }); assertNotNull(future); // nothing should be scheduled right now assertEquals(0, timer.getNumTasksScheduled()); // check that no asynchronous error was reported - that ensures that the newly scheduled // triggerable did, in fact, not trigger if (errorRef.get() != null) { throw new Exception(errorRef.get()); } } finally { timer.shutdownService(); } } @Test public void testFutureCancellation() throws Exception { final Object lock = new Object(); final AtomicReference<Throwable> errorRef = new AtomicReference<>(); final SystemProcessingTimeService timer = new SystemProcessingTimeService( new ReferenceSettingExceptionHandler(errorRef), lock); try { assertEquals(0, timer.getNumTasksScheduled()); // schedule something ScheduledFuture<?> future = timer.registerTimer(System.currentTimeMillis() + 100000000, new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) {} }); assertEquals(1, timer.getNumTasksScheduled()); future.cancel(false); assertEquals(0, timer.getNumTasksScheduled()); future = timer.scheduleAtFixedRate( new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception {} }, 10000000000L, 50L); assertEquals(1, timer.getNumTasksScheduled()); future.cancel(false); assertEquals(0, timer.getNumTasksScheduled()); // check that no asynchronous error was reported if (errorRef.get() != null) { throw new Exception(errorRef.get()); } } finally { timer.shutdownService(); } } @Test public void testExceptionReporting() throws InterruptedException { final AtomicBoolean exceptionWasThrown = new AtomicBoolean(false); final OneShotLatch latch = new OneShotLatch(); final Object lock = new Object(); ProcessingTimeService timeServiceProvider = new SystemProcessingTimeService( new AsyncExceptionHandler() { @Override public void handleAsyncException(String message, Throwable exception) { exceptionWasThrown.set(true); latch.trigger(); } }, lock); timeServiceProvider.registerTimer(System.currentTimeMillis(), new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { throw new Exception("Exception in Timer"); } }); latch.await(); assertTrue(exceptionWasThrown.get()); } @Test public void testExceptionReportingScheduleAtFixedRate() throws InterruptedException { final AtomicBoolean exceptionWasThrown = new AtomicBoolean(false); final OneShotLatch latch = new OneShotLatch(); final Object lock = new Object(); ProcessingTimeService timeServiceProvider = new SystemProcessingTimeService( new AsyncExceptionHandler() { @Override public void handleAsyncException(String message, Throwable exception) { exceptionWasThrown.set(true); latch.trigger(); } }, lock); timeServiceProvider.scheduleAtFixedRate( new ProcessingTimeCallback() { @Override public void onProcessingTime(long timestamp) throws Exception { throw new Exception("Exception in Timer"); } }, 0L, 100L ); latch.await(); assertTrue(exceptionWasThrown.get()); } }