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

com.datastax.oss.driver.api.core.metadata.SafeInitNodeStateListener Maven / Gradle / Ivy

The 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 com.datastax.oss.driver.api.core.metadata;

import com.datastax.oss.driver.api.core.session.Session;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import net.jcip.annotations.GuardedBy;

/**
 * A node state listener wrapper that delays (or ignores) init events until after the session is
 * ready.
 *
 * 

By default, the driver calls node state events, such as {@link #onUp} and {@link #onAdd}, * before the session is ready; see {@link NodeStateListener#onSessionReady(Session)} for a detailed * explanation. This can make things complicated if your listener implementation needs the session * to process those events. * *

This class wraps another implementation to shield it from those details: * *

 * NodeStateListener delegate = ... // your listener implementation
 *
 * SafeInitNodeStateListener wrapper =
 *     new SafeInitNodeStateListener(delegate, true);
 *
 * CqlSession session = CqlSession.builder()
 *     .withNodeStateListener(wrapper)
 *     .build();
 * 
* * With this setup, {@code delegate.onSessionReady} is guaranteed to be invoked first, before any * other method. The second constructor argument indicates what to do with the method calls that * were ignored before that: * *
    *
  • if {@code true}, they are recorded, and replayed to {@code delegate} immediately after * {@link #onSessionReady}. They are guaranteed to happen in the original order, and before * any post-initialization events. *
  • if {@code false}, they are discarded. *
* *

Usage in non-blocking applications: beware that this class is not lock-free. It is implemented * with locks for internal coordination. * * @since 4.6.0 */ public class SafeInitNodeStateListener implements NodeStateListener { private final NodeStateListener delegate; private final boolean replayInitEvents; // Write lock: recording init events or setting sessionReady // Read lock: reading init events or checking sessionReady private final ReadWriteLock lock = new ReentrantReadWriteLock(); @GuardedBy("lock") private boolean sessionReady; @GuardedBy("lock") private final List initEvents = new ArrayList<>(); /** * Creates a new instance. * * @param delegate the wrapped listener, to which method invocations will be forwarded. * @param replayInitEvents whether to record events during initialization and replay them to the * child listener once it's created, or just ignore them. */ public SafeInitNodeStateListener(@NonNull NodeStateListener delegate, boolean replayInitEvents) { this.delegate = Objects.requireNonNull(delegate); this.replayInitEvents = replayInitEvents; } @Override public void onSessionReady(@NonNull Session session) { lock.writeLock().lock(); try { if (!sessionReady) { sessionReady = true; delegate.onSessionReady(session); if (replayInitEvents) { for (InitEvent event : initEvents) { event.invoke(delegate); } } } } finally { lock.writeLock().unlock(); } } @Override public void onAdd(@NonNull Node node) { onEvent(node, InitEvent.Type.ADD); } @Override public void onUp(@NonNull Node node) { onEvent(node, InitEvent.Type.UP); } @Override public void onDown(@NonNull Node node) { onEvent(node, InitEvent.Type.DOWN); } @Override public void onRemove(@NonNull Node node) { onEvent(node, InitEvent.Type.REMOVE); } private void onEvent(Node node, InitEvent.Type eventType) { // Cheap case: the session is ready, just delegate lock.readLock().lock(); try { if (sessionReady) { eventType.listenerMethod.accept(delegate, node); return; } } finally { lock.readLock().unlock(); } // Otherwise, we must acquire the write lock to record the event if (replayInitEvents) { lock.writeLock().lock(); try { // Must re-check because we completely released the lock for a short duration if (sessionReady) { eventType.listenerMethod.accept(delegate, node); } else { initEvents.add(new InitEvent(node, eventType)); } } finally { lock.writeLock().unlock(); } } } @Override public void close() throws Exception { delegate.close(); } private static class InitEvent { enum Type { ADD(NodeStateListener::onAdd), UP(NodeStateListener::onUp), DOWN(NodeStateListener::onDown), REMOVE(NodeStateListener::onRemove), ; @SuppressWarnings("ImmutableEnumChecker") final BiConsumer listenerMethod; Type(BiConsumer listenerMethod) { this.listenerMethod = listenerMethod; } } final Node node; final Type type; InitEvent(@NonNull Node node, @NonNull Type type) { this.node = Objects.requireNonNull(node); this.type = Objects.requireNonNull(type); } void invoke(@NonNull NodeStateListener target) { type.listenerMethod.accept(Objects.requireNonNull(target), node); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy