You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by se...@apache.org on 2020/11/22 22:56:17 UTC

[flink] 01/03: [refactor] Add ManuallyTriggeredScheduledExecutorService from 'flink-runtime' to 'flink-test-utils-junit'.

This is an automated email from the ASF dual-hosted git repository.

sewen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git

commit b2c4482279003667d6d01b6d9d2b53083f68ce2e
Author: Stephan Ewen <se...@apache.org>
AuthorDate: Sun Nov 22 19:53:53 2020 +0100

    [refactor] Add ManuallyTriggeredScheduledExecutorService from 'flink-runtime' to 'flink-test-utils-junit'.
---
 .../ManuallyTriggeredScheduledExecutorService.java | 388 +++++++++++++++++++++
 1 file changed, 388 insertions(+)

diff --git a/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/core/testutils/ManuallyTriggeredScheduledExecutorService.java b/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/core/testutils/ManuallyTriggeredScheduledExecutorService.java
new file mode 100644
index 0000000..0c2c5e2
--- /dev/null
+++ b/flink-test-utils-parent/flink-test-utils-junit/src/main/java/org/apache/flink/core/testutils/ManuallyTriggeredScheduledExecutorService.java
@@ -0,0 +1,388 @@
+/*
+ * 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.core.testutils;
+
+import javax.annotation.Nonnull;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+/**
+ * Simple {@link ScheduledExecutorService} implementation for testing purposes.
+ * It spawns no threads, but lets you trigger the execution of tasks manually.
+ *
+ * <p>This class is helpful when implementing tests tasks synchronous and control when they run,
+ * which would otherwise asynchronous and require complex triggers and latches to test.
+ */
+public class ManuallyTriggeredScheduledExecutorService implements ScheduledExecutorService {
+
+	private final ArrayDeque<Runnable> queuedRunnables = new ArrayDeque<>();
+
+	private final ConcurrentLinkedQueue<ScheduledTask<?>> nonPeriodicScheduledTasks = new ConcurrentLinkedQueue<>();
+
+	private final ConcurrentLinkedQueue<ScheduledTask<?>> periodicScheduledTasks = new ConcurrentLinkedQueue<>();
+
+	private boolean shutdown;
+
+	// ------------------------------------------------------------------------
+	//  (scheduled) execution
+	// ------------------------------------------------------------------------
+
+	@Override
+	public void execute(@Nonnull Runnable command) {
+		synchronized (queuedRunnables) {
+			queuedRunnables.addLast(command);
+		}
+	}
+
+	@Override
+	public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+		return insertNonPeriodicTask(command, delay, unit);
+	}
+
+	@Override
+	public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+		return insertNonPeriodicTask(callable, delay, unit);
+	}
+
+	@Override
+	public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+		return insertPeriodicRunnable(command, initialDelay, period, unit);
+	}
+
+	@Override
+	public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+		return insertPeriodicRunnable(command, initialDelay, delay, unit);
+	}
+
+	// ------------------------------------------------------------------------
+	//  service shutdown
+	// ------------------------------------------------------------------------
+
+	@Override
+	public void shutdown() {
+		shutdown = true;
+	}
+
+	@Override
+	public List<Runnable> shutdownNow() {
+		shutdown();
+		return Collections.emptyList();
+	}
+
+	@Override
+	public boolean isShutdown() {
+		return false;
+	}
+
+	@Override
+	public boolean isTerminated() {
+		return shutdown;
+	}
+
+	@Override
+	public boolean awaitTermination(long timeout, TimeUnit unit) {
+		return true;
+	}
+
+	// ------------------------------------------------------------------------
+	//  non-implemented future task methods
+	// ------------------------------------------------------------------------
+
+	@Override
+	public <T> Future<T> submit(Callable<T> task) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public <T> Future<T> submit(Runnable task, T result) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public Future<?> submit(Runnable task) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+		throw new UnsupportedOperationException();
+	}
+
+	// ------------------------------------------------------------------------
+	// Execution triggering and access to the queued tasks
+	// ------------------------------------------------------------------------
+
+	/**
+	 * Triggers all {@code queuedRunnables}.
+	 */
+	public void triggerAll() {
+		while (numQueuedRunnables() > 0) {
+			trigger();
+		}
+	}
+
+	/**
+	 * Triggers the next queued runnable and executes it synchronously.
+	 * This method throws an exception if no Runnable is currently queued.
+	 */
+	public void trigger() {
+		final Runnable next;
+
+		synchronized (queuedRunnables) {
+			next = queuedRunnables.removeFirst();
+		}
+
+		next.run();
+	}
+
+	/**
+	 * Gets the number of Runnables currently queued.
+	 */
+	public int numQueuedRunnables() {
+		synchronized (queuedRunnables) {
+			return queuedRunnables.size();
+		}
+	}
+
+	public Collection<ScheduledFuture<?>> getScheduledTasks() {
+		final ArrayList<ScheduledFuture<?>> scheduledTasks =
+			new ArrayList<>(nonPeriodicScheduledTasks.size() + periodicScheduledTasks.size());
+		scheduledTasks.addAll(getNonPeriodicScheduledTask());
+		scheduledTasks.addAll(getPeriodicScheduledTask());
+		return scheduledTasks;
+	}
+
+	public Collection<ScheduledFuture<?>> getPeriodicScheduledTask() {
+		return periodicScheduledTasks
+			.stream()
+			.filter(scheduledTask -> !scheduledTask.isCancelled())
+			.collect(Collectors.toList());
+	}
+
+	public Collection<ScheduledFuture<?>> getNonPeriodicScheduledTask() {
+		return nonPeriodicScheduledTasks
+			.stream()
+			.filter(scheduledTask -> !scheduledTask.isCancelled())
+			.collect(Collectors.toList());
+	}
+
+	/**
+	 * Triggers all registered tasks.
+	 */
+	public void triggerScheduledTasks() {
+		triggerPeriodicScheduledTasks();
+		triggerNonPeriodicScheduledTasks();
+	}
+
+	/**
+	 * Triggers a single non-periodically scheduled task.
+	 *
+	 * @throws NoSuchElementException If there is no such task.
+	 */
+	public void triggerNonPeriodicScheduledTask() {
+		final ScheduledTask<?> poll = nonPeriodicScheduledTasks.remove();
+		if (poll != null) {
+			poll.execute();
+		}
+	}
+
+	/**
+	 * Triggers all non-periodically scheduled tasks. In contrast to {@link #triggerNonPeriodicScheduledTasks()},
+	 * if such a task schedules another non-periodically schedule task, then this new task will also be triggered.
+	 */
+	public void triggerNonPeriodicScheduledTasksWithRecursion() {
+		while (!nonPeriodicScheduledTasks.isEmpty()) {
+			final ScheduledTask<?> scheduledTask = nonPeriodicScheduledTasks.poll();
+
+			if (!scheduledTask.isCancelled()) {
+				scheduledTask.execute();
+			}
+		}
+	}
+
+	public void triggerNonPeriodicScheduledTasks() {
+		final Iterator<ScheduledTask<?>> iterator = nonPeriodicScheduledTasks.iterator();
+
+		while (iterator.hasNext()) {
+			final ScheduledTask<?> scheduledTask = iterator.next();
+
+			if (!scheduledTask.isCancelled()) {
+				scheduledTask.execute();
+			}
+			iterator.remove();
+		}
+	}
+
+	public void triggerPeriodicScheduledTasks() {
+		for (ScheduledTask<?> scheduledTask : periodicScheduledTasks) {
+			if (!scheduledTask.isCancelled()) {
+				scheduledTask.execute();
+			}
+		}
+	}
+
+	private ScheduledFuture<?> insertPeriodicRunnable(
+		Runnable command,
+		long delay,
+		long period,
+		TimeUnit unit) {
+
+		final ScheduledTask<?> scheduledTask = new ScheduledTask<>(
+			() -> {
+				command.run();
+				return null;
+			},
+			unit.convert(delay, TimeUnit.MILLISECONDS),
+			unit.convert(period, TimeUnit.MILLISECONDS));
+
+		periodicScheduledTasks.offer(scheduledTask);
+
+		return scheduledTask;
+	}
+
+	private ScheduledFuture<?> insertNonPeriodicTask(Runnable command, long delay, TimeUnit unit) {
+		return insertNonPeriodicTask(() -> {
+			command.run();
+			return null;
+		}, delay, unit);
+	}
+
+	private <V> ScheduledFuture<V> insertNonPeriodicTask(
+		Callable<V> callable, long delay, TimeUnit unit) {
+		final ScheduledTask<V> scheduledTask =
+			new ScheduledTask<>(callable, unit.convert(delay, TimeUnit.MILLISECONDS));
+
+		nonPeriodicScheduledTasks.offer(scheduledTask);
+
+		return scheduledTask;
+	}
+
+	private static final class ScheduledTask<T> implements ScheduledFuture<T> {
+
+		private final Callable<T> callable;
+
+		private final long delay;
+
+		private final long period;
+
+		private final CompletableFuture<T> result;
+
+		private ScheduledTask(Callable<T> callable, long delay) {
+			this(callable, delay, 0);
+		}
+
+		private ScheduledTask(Callable<T> callable, long delay, long period) {
+			this.callable = Objects.requireNonNull(callable);
+			this.result = new CompletableFuture<>();
+			this.delay = delay;
+			this.period = period;
+		}
+
+		private boolean isPeriodic() {
+			return period > 0;
+		}
+
+		public void execute() {
+			if (!result.isDone()) {
+				if (!isPeriodic()) {
+					try {
+						result.complete(callable.call());
+					} catch (Exception e) {
+						result.completeExceptionally(e);
+					}
+				} else {
+					try {
+						callable.call();
+					} catch (Exception e) {
+						result.completeExceptionally(e);
+					}
+				}
+			}
+		}
+
+		@Override
+		public long getDelay(TimeUnit unit) {
+			return unit.convert(delay, TimeUnit.MILLISECONDS);
+		}
+
+		@Override
+		public int compareTo(Delayed o) {
+			return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
+		}
+
+		@Override
+		public boolean cancel(boolean mayInterruptIfRunning) {
+			return result.cancel(mayInterruptIfRunning);
+		}
+
+		@Override
+		public boolean isCancelled() {
+			return result.isCancelled();
+		}
+
+		@Override
+		public boolean isDone() {
+			return result.isDone();
+		}
+
+		@Override
+		public T get() throws InterruptedException, ExecutionException {
+			return result.get();
+		}
+
+		@Override
+		public T get(long timeout, @Nonnull TimeUnit unit)
+			throws InterruptedException, ExecutionException, TimeoutException {
+			return result.get(timeout, unit);
+		}
+	}
+}