/*
* 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 com.facebook.presto.raptor.metadata;
import com.facebook.presto.raptor.NodeSupplier;
import com.facebook.presto.spi.Node;
import com.facebook.presto.spi.PrestoException;
import com.google.common.base.Ticker;
import io.airlift.log.Logger;
import io.airlift.units.Duration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.facebook.presto.raptor.RaptorErrorCode.RAPTOR_REASSIGNMENT_DELAY;
import static com.facebook.presto.raptor.RaptorErrorCode.RAPTOR_REASSIGNMENT_THROTTLE;
import static io.airlift.concurrent.Threads.daemonThreadsNamed;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toSet;
public class AssignmentLimiter
{
private static final Logger log = Logger.get(AssignmentLimiter.class);
private final NodeSupplier nodeSupplier;
private final Ticker ticker;
private final Duration reassignmentDelay;
private final Duration reassignmentInterval;
private final ScheduledExecutorService scheduler = newScheduledThreadPool(1, daemonThreadsNamed("assignment-limiter"));
private final AtomicBoolean started = new AtomicBoolean();
@GuardedBy("this")
private final Map<String, Long> delayedNodes = new HashMap<>();
@GuardedBy("this")
private final Set<String> offlineNodes = new HashSet<>();
@GuardedBy("this")
private OptionalLong lastOfflined = OptionalLong.empty();
@Inject
public AssignmentLimiter(NodeSupplier nodeSupplier, Ticker ticker, MetadataConfig config)
{
this(nodeSupplier, ticker, config.getReassignmentDelay(), config.getReassignmentDelay());
}
public AssignmentLimiter(NodeSupplier nodeSupplier, Ticker ticker, Duration reassignmentDelay, Duration reassignmentInterval)
{
this.nodeSupplier = requireNonNull(nodeSupplier, "nodeSupplier is null");
this.ticker = requireNonNull(ticker, "ticker is null");
this.reassignmentDelay = requireNonNull(reassignmentDelay, "reassignmentDelay is null");
this.reassignmentInterval = requireNonNull(reassignmentInterval, "reassignmentInterval is null");
}
@PostConstruct
public void start()
{
if (!started.getAndSet(true)) {
scheduler.scheduleWithFixedDelay(() -> {
try {
clearOnlineNodes();
}
catch (Throwable t) {
log.error(t, "Error clearing online nodes");
}
}, 2, 2, SECONDS);
}
}
@PreDestroy
public void shutdown()
{
scheduler.shutdownNow();
}
public synchronized void checkAssignFrom(String nodeIdentifier)
{
if (offlineNodes.contains(nodeIdentifier)) {
return;
}
long now = ticker.read();
long start = delayedNodes.computeIfAbsent(nodeIdentifier, key -> now);
Duration delay = new Duration(now - start, NANOSECONDS);
if (delay.compareTo(reassignmentDelay) < 0) {
throw new PrestoException(RAPTOR_REASSIGNMENT_DELAY, format(
"Reassignment delay is in effect for node %s (elapsed: %s)",
nodeIdentifier,
delay.convertToMostSuccinctTimeUnit()));
}
if (lastOfflined.isPresent()) {
delay = new Duration(now - lastOfflined.getAsLong(), NANOSECONDS);
if (delay.compareTo(reassignmentInterval) < 0) {
throw new PrestoException(RAPTOR_REASSIGNMENT_THROTTLE, format(
"Reassignment throttle is in effect for node %s (elapsed: %s)",
nodeIdentifier,
delay.convertToMostSuccinctTimeUnit()));
}
}
delayedNodes.remove(nodeIdentifier);
offlineNodes.add(nodeIdentifier);
lastOfflined = OptionalLong.of(now);
}
private void clearOnlineNodes()
{
Set<String> onlineNodes = nodeSupplier.getWorkerNodes().stream()
.map(Node::getNodeIdentifier)
.collect(toSet());
synchronized (this) {
delayedNodes.keySet().removeAll(onlineNodes);
offlineNodes.removeAll(onlineNodes);
}
}
}