All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.runtime.rpc.RpcEndpoint Maven / Gradle / Ivy

There is a newer version: 1.13.6
Show newest version
/*
 * 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.runtime.rpc;

import org.apache.flink.api.common.time.Time;
import org.apache.flink.runtime.concurrent.ComponentMainThreadExecutor;
import org.apache.flink.runtime.concurrent.ScheduledFutureAdapter;
import org.apache.flink.util.AutoCloseableAsync;
import org.apache.flink.util.Preconditions;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;

import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

import static org.apache.flink.util.Preconditions.checkNotNull;

/**
 * Base class for RPC endpoints. Distributed components which offer remote procedure calls have to
 * extend the RPC endpoint base class. An RPC endpoint is backed by an {@link RpcService}.
 *
 * 

Endpoint and Gateway

* To be done... *

Single Threaded Endpoint Execution

* *

All RPC calls on the same endpoint are called by the same thread * (referred to as the endpoint's main thread). * Thus, by executing all state changing operations within the main * thread, we don't have to reason about concurrent accesses, in the same way in the Actor Model * of Erlang or Akka. * *

The RPC endpoint provides {@link #runAsync(Runnable)}, {@link #callAsync(Callable, Time)} * and the {@link #getMainThreadExecutor()} to execute code in the RPC endpoint's main thread. * *

Lifecycle

* *

The RPC endpoint has the following stages: *

    *
  • * The RPC endpoint is created in a non-running state and does not serve any RPC requests. *
  • *
  • * Calling the {@link #start()} method triggers the start of the RPC endpoint and schedules overridable * {@link #onStart()} method call to the main thread. *
  • *
  • * When the start operation ends the RPC endpoint is moved to the running state * and starts to serve and complete RPC requests. *
  • *
  • * Calling the {@link #closeAsync()} method triggers the termination of the RPC endpoint and schedules overridable * {@link #onStop()} method call to the main thread. *
  • *
  • * When {@link #onStop()} method is called, it triggers an asynchronous stop operation. * The RPC endpoint is not in the running state anymore but it continues to serve RPC requests. *
  • *
  • * When the asynchronous stop operation ends, the RPC endpoint terminates completely * and does not serve RPC requests anymore. *
  • *
* The running state can be queried in a RPC method handler or in the main thread by calling {@link #isRunning()} method. */ public abstract class RpcEndpoint implements RpcGateway, AutoCloseableAsync { protected final Logger log = LoggerFactory.getLogger(getClass()); // ------------------------------------------------------------------------ /** RPC service to be used to start the RPC server and to obtain rpc gateways. */ private final RpcService rpcService; /** Unique identifier for this rpc endpoint. */ private final String endpointId; /** Interface to access the underlying rpc server. */ protected final RpcServer rpcServer; /** A reference to the endpoint's main thread, if the current method is called by the main thread. */ final AtomicReference currentMainThread = new AtomicReference<>(null); /** The main thread executor to be used to execute future callbacks in the main thread * of the executing rpc server. */ private final MainThreadExecutor mainThreadExecutor; /** * Indicates whether the RPC endpoint is started and not stopped or being stopped. * *

IMPORTANT: the running state is not thread safe and can be used only in the main thread of the rpc endpoint. */ private boolean isRunning; /** * Initializes the RPC endpoint. * * @param rpcService The RPC server that dispatches calls to this RPC endpoint. * @param endpointId Unique identifier for this endpoint */ protected RpcEndpoint(final RpcService rpcService, final String endpointId) { this.rpcService = checkNotNull(rpcService, "rpcService"); this.endpointId = checkNotNull(endpointId, "endpointId"); this.rpcServer = rpcService.startServer(this); this.mainThreadExecutor = new MainThreadExecutor(rpcServer, this::validateRunsInMainThread); } /** * Initializes the RPC endpoint with a random endpoint id. * * @param rpcService The RPC server that dispatches calls to this RPC endpoint. */ protected RpcEndpoint(final RpcService rpcService) { this(rpcService, UUID.randomUUID().toString()); } /** * Returns the rpc endpoint's identifier. * * @return Rpc endpoint's identifier. */ public String getEndpointId() { return endpointId; } /** * Returns whether the RPC endpoint is started and not stopped or being stopped. * * @return whether the RPC endpoint is started and not stopped or being stopped. */ protected boolean isRunning() { validateRunsInMainThread(); return isRunning; } // ------------------------------------------------------------------------ // Start & shutdown & lifecycle callbacks // ------------------------------------------------------------------------ /** * Triggers start of the rpc endpoint. This tells the underlying rpc server that the rpc endpoint is ready * to process remote procedure calls. */ public final void start() { rpcServer.start(); } /** * Internal method which is called by the RpcService implementation to start the RpcEndpoint. * * @throws Exception indicating that the rpc endpoint could not be started. If an exception occurs, * then the rpc endpoint will automatically terminate. */ public final void internalCallOnStart() throws Exception { validateRunsInMainThread(); isRunning = true; onStart(); } /** * User overridable callback which is called from {@link #internalCallOnStart()}. * *

This method is called when the RpcEndpoint is being started. The method is guaranteed * to be executed in the main thread context and can be used to start the rpc endpoint in the * context of the rpc endpoint's main thread. * *

IMPORTANT: This method should never be called directly by the user. * * @throws Exception indicating that the rpc endpoint could not be started. If an exception occurs, * then the rpc endpoint will automatically terminate. */ protected void onStart() throws Exception {} /** * Triggers stop of the rpc endpoint. This tells the underlying rpc server that the rpc endpoint is * no longer ready to process remote procedure calls. */ protected final void stop() { rpcServer.stop(); } /** * Internal method which is called by the RpcService implementation to stop the RpcEndpoint. * * @return Future which is completed once all post stop actions are completed. If an error * occurs this future is completed exceptionally */ public final CompletableFuture internalCallOnStop() { validateRunsInMainThread(); CompletableFuture stopFuture = onStop(); isRunning = false; return stopFuture; } /** * User overridable callback which is called from {@link #internalCallOnStop()}. * *

This method is called when the RpcEndpoint is being shut down. The method is guaranteed * to be executed in the main thread context and can be used to clean up internal state. * *

IMPORTANT: This method should never be called directly by the user. * * @return Future which is completed once all post stop actions are completed. If an error * occurs this future is completed exceptionally */ protected CompletableFuture onStop() { return CompletableFuture.completedFuture(null); } /** * Triggers the shut down of the rpc endpoint. The shut down is executed asynchronously. * *

In order to wait on the completion of the shut down, obtain the termination future * via {@link #getTerminationFuture()}} and wait on its completion. */ @Override public final CompletableFuture closeAsync() { rpcService.stopServer(rpcServer); return getTerminationFuture(); } // ------------------------------------------------------------------------ // Basic RPC endpoint properties // ------------------------------------------------------------------------ /** * Returns a self gateway of the specified type which can be used to issue asynchronous * calls against the RpcEndpoint. * *

IMPORTANT: The self gateway type must be implemented by the RpcEndpoint. Otherwise * the method will fail. * * @param selfGatewayType class of the self gateway type * @param type of the self gateway to create * @return Self gateway of the specified type which can be used to issue asynchronous rpcs */ public C getSelfGateway(Class selfGatewayType) { if (selfGatewayType.isInstance(rpcServer)) { @SuppressWarnings("unchecked") C selfGateway = ((C) rpcServer); return selfGateway; } else { throw new RuntimeException("RpcEndpoint does not implement the RpcGateway interface of type " + selfGatewayType + '.'); } } /** * Gets the address of the underlying RPC endpoint. The address should be fully qualified so that * a remote system can connect to this RPC endpoint via this address. * * @return Fully qualified address of the underlying RPC endpoint */ @Override public String getAddress() { return rpcServer.getAddress(); } /** * Gets the hostname of the underlying RPC endpoint. * * @return Hostname on which the RPC endpoint is running */ @Override public String getHostname() { return rpcServer.getHostname(); } /** * Gets the main thread execution context. The main thread execution context can be used to * execute tasks in the main thread of the underlying RPC endpoint. * * @return Main thread execution context */ protected MainThreadExecutor getMainThreadExecutor() { return mainThreadExecutor; } /** * Gets the endpoint's RPC service. * * @return The endpoint's RPC service */ public RpcService getRpcService() { return rpcService; } /** * Return a future which is completed with true when the rpc endpoint has been terminated. * In case of a failure, this future is completed with the occurring exception. * * @return Future which is completed when the rpc endpoint has been terminated. */ public CompletableFuture getTerminationFuture() { return rpcServer.getTerminationFuture(); } // ------------------------------------------------------------------------ // Asynchronous executions // ------------------------------------------------------------------------ /** * Execute the runnable in the main thread of the underlying RPC endpoint. * * @param runnable Runnable to be executed in the main thread of the underlying RPC endpoint */ protected void runAsync(Runnable runnable) { rpcServer.runAsync(runnable); } /** * Execute the runnable in the main thread of the underlying RPC endpoint, with * a delay of the given number of milliseconds. * * @param runnable Runnable to be executed * @param delay The delay after which the runnable will be executed */ protected void scheduleRunAsync(Runnable runnable, Time delay) { scheduleRunAsync(runnable, delay.getSize(), delay.getUnit()); } /** * Execute the runnable in the main thread of the underlying RPC endpoint, with * a delay of the given number of milliseconds. * * @param runnable Runnable to be executed * @param delay The delay after which the runnable will be executed */ protected void scheduleRunAsync(Runnable runnable, long delay, TimeUnit unit) { rpcServer.scheduleRunAsync(runnable, unit.toMillis(delay)); } /** * Execute the callable in the main thread of the underlying RPC service, returning a future for * the result of the callable. If the callable is not completed within the given timeout, then * the future will be failed with a {@link TimeoutException}. * * @param callable Callable to be executed in the main thread of the underlying rpc server * @param timeout Timeout for the callable to be completed * @param Return type of the callable * @return Future for the result of the callable. */ protected CompletableFuture callAsync(Callable callable, Time timeout) { return rpcServer.callAsync(callable, timeout); } // ------------------------------------------------------------------------ // Main Thread Validation // ------------------------------------------------------------------------ /** * Validates that the method call happens in the RPC endpoint's main thread. * *

IMPORTANT: This check only happens when assertions are enabled, * such as when running tests. * *

This can be used for additional checks, like *

{@code
	 * protected void concurrencyCriticalMethod() {
	 *     validateRunsInMainThread();
	 *
	 *     // some critical stuff
	 * }
	 * }
*/ public void validateRunsInMainThread() { assert MainThreadValidatorUtil.isRunningInExpectedThread(currentMainThread.get()); } // ------------------------------------------------------------------------ // Utilities // ------------------------------------------------------------------------ /** * Executor which executes runnables in the main thread context. */ protected static class MainThreadExecutor implements ComponentMainThreadExecutor { private final MainThreadExecutable gateway; private final Runnable mainThreadCheck; MainThreadExecutor(MainThreadExecutable gateway, Runnable mainThreadCheck) { this.gateway = Preconditions.checkNotNull(gateway); this.mainThreadCheck = Preconditions.checkNotNull(mainThreadCheck); } public void runAsync(Runnable runnable) { gateway.runAsync(runnable); } public void scheduleRunAsync(Runnable runnable, long delayMillis) { gateway.scheduleRunAsync(runnable, delayMillis); } public void execute(@Nonnull Runnable command) { runAsync(command); } @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { final long delayMillis = TimeUnit.MILLISECONDS.convert(delay, unit); FutureTask ft = new FutureTask<>(command, null); scheduleRunAsync(ft, delayMillis); return new ScheduledFutureAdapter<>(ft, delayMillis, TimeUnit.MILLISECONDS); } @Override public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { throw new UnsupportedOperationException("Not implemented because the method is currently not required."); } @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { throw new UnsupportedOperationException("Not implemented because the method is currently not required."); } @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { throw new UnsupportedOperationException("Not implemented because the method is currently not required."); } @Override public void assertRunningInMainThread() { mainThreadCheck.run(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy