org.elasticsearch.cluster.ClusterStateObserver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch - Open Source, Distributed, RESTful Search Engine
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.cluster;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cluster.service.ClusterApplierService;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* A utility class which simplifies interacting with the cluster state in cases where
* one tries to take action based on the current state but may want to wait for a new state
* and retry upon failure.
*/
public class ClusterStateObserver {
protected final Logger logger;
private final Predicate MATCH_ALL_CHANGES_PREDICATE = state -> true;
private final ClusterApplierService clusterApplierService;
private final ThreadContext contextHolder;
volatile TimeValue timeOutValue;
final AtomicReference lastObservedState;
final TimeoutClusterStateListener clusterStateListener = new ObserverClusterStateListener();
// observingContext is not null when waiting on cluster state changes
final AtomicReference observingContext = new AtomicReference<>(null);
volatile Long startTimeNS;
volatile boolean timedOut;
public ClusterStateObserver(ClusterService clusterService, Logger logger, ThreadContext contextHolder) {
this(clusterService, new TimeValue(60000), logger, contextHolder);
}
/**
* @param timeout a global timeout for this observer. After it has expired the observer
* will fail any existing or new #waitForNextChange calls. Set to null
* to wait indefinitely
*/
public ClusterStateObserver(ClusterService clusterService, @Nullable TimeValue timeout, Logger logger, ThreadContext contextHolder) {
this(clusterService.state(), clusterService, timeout, logger, contextHolder);
}
/**
* @param timeout a global timeout for this observer. After it has expired the observer
* will fail any existing or new #waitForNextChange calls. Set to null
* to wait indefinitely
*/
public ClusterStateObserver(ClusterState initialState, ClusterService clusterService, @Nullable TimeValue timeout, Logger logger,
ThreadContext contextHolder) {
this(initialState, clusterService.getClusterApplierService(), timeout, logger, contextHolder);
}
public ClusterStateObserver(ClusterState initialState, ClusterApplierService clusterApplierService, @Nullable TimeValue timeout,
Logger logger, ThreadContext contextHolder) {
this.clusterApplierService = clusterApplierService;
this.lastObservedState = new AtomicReference<>(new StoredState(initialState));
this.timeOutValue = timeout;
if (timeOutValue != null) {
this.startTimeNS = System.nanoTime();
}
this.logger = logger;
this.contextHolder = contextHolder;
}
/** sets the last observed state to the currently applied cluster state and returns it */
public ClusterState setAndGetObservedState() {
if (observingContext.get() != null) {
throw new ElasticsearchException("cannot set current cluster state while waiting for a cluster state change");
}
ClusterState clusterState = clusterApplierService.state();
lastObservedState.set(new StoredState(clusterState));
return clusterState;
}
/** indicates whether this observer has timed out */
public boolean isTimedOut() {
return timedOut;
}
public void waitForNextChange(Listener listener) {
waitForNextChange(listener, MATCH_ALL_CHANGES_PREDICATE);
}
public void waitForNextChange(Listener listener, @Nullable TimeValue timeOutValue) {
waitForNextChange(listener, MATCH_ALL_CHANGES_PREDICATE, timeOutValue);
}
public void waitForNextChange(Listener listener, Predicate statePredicate) {
waitForNextChange(listener, statePredicate, null);
}
/**
* Wait for the next cluster state which satisfies statePredicate
*
* @param listener callback listener
* @param statePredicate predicate to check whether cluster state changes are relevant and the callback should be called
* @param timeOutValue a timeout for waiting. If null the global observer timeout will be used.
*/
public void waitForNextChange(Listener listener, Predicate statePredicate, @Nullable TimeValue timeOutValue) {
listener = new ContextPreservingListener(listener, contextHolder.newRestorableContext(false));
if (observingContext.get() != null) {
throw new ElasticsearchException("already waiting for a cluster state change");
}
Long timeoutTimeLeftMS;
if (timeOutValue == null) {
timeOutValue = this.timeOutValue;
if (timeOutValue != null) {
long timeSinceStartMS = TimeValue.nsecToMSec(System.nanoTime() - startTimeNS);
timeoutTimeLeftMS = timeOutValue.millis() - timeSinceStartMS;
if (timeoutTimeLeftMS <= 0L) {
// things have timeout while we were busy -> notify
logger.trace("observer timed out. notifying listener. timeout setting [{}], time since start [{}]", timeOutValue, new TimeValue(timeSinceStartMS));
// update to latest, in case people want to retry
timedOut = true;
lastObservedState.set(new StoredState(clusterApplierService.state()));
listener.onTimeout(timeOutValue);
return;
}
} else {
timeoutTimeLeftMS = null;
}
} else {
this.startTimeNS = System.nanoTime();
this.timeOutValue = timeOutValue;
timeoutTimeLeftMS = timeOutValue.millis();
timedOut = false;
}
// sample a new state. This state maybe *older* than the supplied state if we are called from an applier,
// which wants to wait for something else to happen
ClusterState newState = clusterApplierService.state();
if (lastObservedState.get().isOlderOrDifferentMaster(newState) && statePredicate.test(newState)) {
// good enough, let's go.
logger.trace("observer: sampled state accepted by predicate ({})", newState);
lastObservedState.set(new StoredState(newState));
listener.onNewClusterState(newState);
} else {
logger.trace("observer: sampled state rejected by predicate ({}). adding listener to ClusterService", newState);
final ObservingContext context = new ObservingContext(listener, statePredicate);
if (!observingContext.compareAndSet(null, context)) {
throw new ElasticsearchException("already waiting for a cluster state change");
}
clusterApplierService.addTimeoutListener(timeoutTimeLeftMS == null ? null : new TimeValue(timeoutTimeLeftMS), clusterStateListener);
}
}
class ObserverClusterStateListener implements TimeoutClusterStateListener {
@Override
public void clusterChanged(ClusterChangedEvent event) {
ObservingContext context = observingContext.get();
if (context == null) {
// No need to remove listener as it is the responsibility of the thread that set observingContext to null
return;
}
final ClusterState state = event.state();
if (context.statePredicate.test(state)) {
if (observingContext.compareAndSet(context, null)) {
clusterApplierService.removeTimeoutListener(this);
logger.trace("observer: accepting cluster state change ({})", state);
lastObservedState.set(new StoredState(state));
context.listener.onNewClusterState(state);
} else {
logger.trace("observer: predicate approved change but observing context has changed - ignoring (new cluster state version [{}])", state.version());
}
} else {
logger.trace("observer: predicate rejected change (new cluster state version [{}])", state.version());
}
}
@Override
public void postAdded() {
ObservingContext context = observingContext.get();
if (context == null) {
// No need to remove listener as it is the responsibility of the thread that set observingContext to null
return;
}
ClusterState newState = clusterApplierService.state();
if (lastObservedState.get().isOlderOrDifferentMaster(newState) && context.statePredicate.test(newState)) {
// double check we're still listening
if (observingContext.compareAndSet(context, null)) {
logger.trace("observer: post adding listener: accepting current cluster state ({})", newState);
clusterApplierService.removeTimeoutListener(this);
lastObservedState.set(new StoredState(newState));
context.listener.onNewClusterState(newState);
} else {
logger.trace("observer: postAdded - predicate approved state but observing context has changed - ignoring ({})", newState);
}
} else {
logger.trace("observer: postAdded - predicate rejected state ({})", newState);
}
}
@Override
public void onClose() {
ObservingContext context = observingContext.getAndSet(null);
if (context != null) {
logger.trace("observer: cluster service closed. notifying listener.");
clusterApplierService.removeTimeoutListener(this);
context.listener.onClusterServiceClose();
}
}
@Override
public void onTimeout(TimeValue timeout) {
ObservingContext context = observingContext.getAndSet(null);
if (context != null) {
clusterApplierService.removeTimeoutListener(this);
long timeSinceStartMS = TimeValue.nsecToMSec(System.nanoTime() - startTimeNS);
logger.trace("observer: timeout notification from cluster service. timeout setting [{}], time since start [{}]", timeOutValue, new TimeValue(timeSinceStartMS));
// update to latest, in case people want to retry
lastObservedState.set(new StoredState(clusterApplierService.state()));
timedOut = true;
context.listener.onTimeout(timeOutValue);
}
}
}
/**
* The observer considers two cluster states to be the same if they have the same version and master node id (i.e. null or set)
*/
private static class StoredState {
private final String masterNodeId;
private final long version;
StoredState(ClusterState clusterState) {
this.masterNodeId = clusterState.nodes().getMasterNodeId();
this.version = clusterState.version();
}
/**
* returns true if stored state is older then given state or they are from a different master, meaning they can't be compared
* */
public boolean isOlderOrDifferentMaster(ClusterState clusterState) {
return version < clusterState.version() || Objects.equals(masterNodeId, clusterState.nodes().getMasterNodeId()) == false;
}
}
public interface Listener {
/** called when a new state is observed */
void onNewClusterState(ClusterState state);
/** called when the cluster service is closed */
void onClusterServiceClose();
void onTimeout(TimeValue timeout);
}
static class ObservingContext {
public final Listener listener;
public final Predicate statePredicate;
ObservingContext(Listener listener, Predicate statePredicate) {
this.listener = listener;
this.statePredicate = statePredicate;
}
}
private static final class ContextPreservingListener implements Listener {
private final Listener delegate;
private final Supplier contextSupplier;
private ContextPreservingListener(Listener delegate, Supplier contextSupplier) {
this.contextSupplier = contextSupplier;
this.delegate = delegate;
}
@Override
public void onNewClusterState(ClusterState state) {
try (ThreadContext.StoredContext context = contextSupplier.get()) {
delegate.onNewClusterState(state);
}
}
@Override
public void onClusterServiceClose() {
try (ThreadContext.StoredContext context = contextSupplier.get()) {
delegate.onClusterServiceClose();
}
}
@Override
public void onTimeout(TimeValue timeout) {
try (ThreadContext.StoredContext context = contextSupplier.get()) {
delegate.onTimeout(timeout);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy