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

org.apache.flink.runtime.rest.handler.async.CompletedOperationCache 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.rest.handler.async;

import org.apache.flink.annotation.VisibleForTesting;
import org.apache.flink.runtime.concurrent.FutureUtils;
import org.apache.flink.types.Either;
import org.apache.flink.util.AutoCloseableAsync;
import org.apache.flink.util.Preconditions;

import org.apache.flink.shaded.guava18.com.google.common.base.Ticker;
import org.apache.flink.shaded.guava18.com.google.common.cache.Cache;
import org.apache.flink.shaded.guava18.com.google.common.cache.CacheBuilder;
import org.apache.flink.shaded.guava18.com.google.common.cache.RemovalListener;

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

import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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

/**
 * Cache to manage ongoing operations.
 *
 * 

The cache allows to register ongoing operations by calling * {@link #registerOngoingOperation(K, CompletableFuture)}, where the * {@code CompletableFuture} contains the operation result. Completed operations will be * removed from the cache automatically after a fixed timeout. */ @ThreadSafe class CompletedOperationCache implements AutoCloseableAsync { private static final long COMPLETED_OPERATION_RESULT_CACHE_DURATION_SECONDS = 300L; private static final Logger LOGGER = LoggerFactory.getLogger(CompletedOperationCache.class); /** * In-progress asynchronous operations. */ private final Map> registeredOperationTriggers = new ConcurrentHashMap<>(); /** * Caches the result of completed operations. */ private final Cache> completedOperations; CompletedOperationCache() { this(Ticker.systemTicker()); } @VisibleForTesting CompletedOperationCache(final Ticker ticker) { completedOperations = CacheBuilder.newBuilder() .expireAfterWrite(COMPLETED_OPERATION_RESULT_CACHE_DURATION_SECONDS, TimeUnit.SECONDS) .removalListener((RemovalListener>) removalNotification -> { if (removalNotification.wasEvicted()) { Preconditions.checkState(removalNotification.getKey() != null); Preconditions.checkState(removalNotification.getValue() != null); // When shutting down the cache, we wait until all results are accessed. // When a result gets evicted from the cache, it will not be possible to access // it any longer, and we might be in the process of shutting down, so we mark // the result as accessed to avoid waiting indefinitely. removalNotification.getValue().markAccessed(); LOGGER.info("Evicted result with trigger id {} because its TTL of {}s has expired.", removalNotification.getKey().getTriggerId(), COMPLETED_OPERATION_RESULT_CACHE_DURATION_SECONDS); } }) .ticker(ticker) .build(); } /** * Registers an ongoing operation with the cache. * * @param operationResultFuture A future containing the operation result. */ public void registerOngoingOperation( final K operationKey, final CompletableFuture operationResultFuture) { final ResultAccessTracker inProgress = ResultAccessTracker.inProgress(); registeredOperationTriggers.put(operationKey, inProgress); operationResultFuture.whenComplete((result, error) -> { if (error == null) { completedOperations.put(operationKey, inProgress.finishOperation(Either.Right(result))); } else { completedOperations.put(operationKey, inProgress.finishOperation(Either.Left(error))); } registeredOperationTriggers.remove(operationKey); }); } /** * Returns the operation result or a {@code Throwable} if the {@code CompletableFuture} * finished, otherwise {@code null}. * * @throws UnknownOperationKeyException If the operation is not found, and there is no ongoing * operation under the provided key. */ @Nullable public Either get( final K operationKey) throws UnknownOperationKeyException { ResultAccessTracker resultAccessTracker; if ((resultAccessTracker = registeredOperationTriggers.get(operationKey)) == null && (resultAccessTracker = completedOperations.getIfPresent(operationKey)) == null) { throw new UnknownOperationKeyException(operationKey); } return resultAccessTracker.accessOperationResultOrError(); } @Override public CompletableFuture closeAsync() { return FutureUtils.orTimeout( asyncWaitForResultsToBeAccessed(), COMPLETED_OPERATION_RESULT_CACHE_DURATION_SECONDS, TimeUnit.SECONDS); } private CompletableFuture asyncWaitForResultsToBeAccessed() { return FutureUtils.waitForAll( Stream.concat(registeredOperationTriggers.values().stream(), completedOperations.asMap().values().stream()) .map(ResultAccessTracker::getAccessedFuture) .collect(Collectors.toList())); } @VisibleForTesting void cleanUp() { completedOperations.cleanUp(); } /** * Stores the result of an asynchronous operation, and tracks accesses to it. */ private static class ResultAccessTracker { /** Result of an asynchronous operation. Null if operation is in progress. */ @Nullable private final Either operationResultOrError; /** Future that completes if a non-null {@link #operationResultOrError} is accessed. */ private final CompletableFuture accessed; private static ResultAccessTracker inProgress() { return new ResultAccessTracker<>(); } private ResultAccessTracker() { this.operationResultOrError = null; this.accessed = new CompletableFuture<>(); } private ResultAccessTracker(final Either operationResultOrError, final CompletableFuture accessed) { this.operationResultOrError = checkNotNull(operationResultOrError); this.accessed = checkNotNull(accessed); } /** * Creates a new instance of the tracker with the result of the asynchronous operation set. */ public ResultAccessTracker finishOperation(final Either operationResultOrError) { checkState(this.operationResultOrError == null); return new ResultAccessTracker<>(checkNotNull(operationResultOrError), this.accessed); } /** * If present, returns the result of the asynchronous operation, and marks the result as * accessed. If the result is not present, this method returns null. */ @Nullable public Either accessOperationResultOrError() { if (operationResultOrError != null) { markAccessed(); } return operationResultOrError; } public CompletableFuture getAccessedFuture() { return accessed; } private void markAccessed() { accessed.complete(null); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy