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

io.atomix.copycat.client.DefaultCopycatClient Maven / Gradle / Ivy

There is a newer version: 1.2.8
Show newest version
/*
 * Copyright 2015 the original author or authors.
 *
 * 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 io.atomix.copycat.client;

import io.atomix.catalyst.serializer.Serializer;
import io.atomix.catalyst.transport.Address;
import io.atomix.catalyst.transport.Transport;
import io.atomix.catalyst.util.Assert;
import io.atomix.catalyst.util.Listener;
import io.atomix.catalyst.util.concurrent.CatalystThreadFactory;
import io.atomix.catalyst.util.concurrent.Futures;
import io.atomix.catalyst.util.concurrent.SingleThreadContext;
import io.atomix.catalyst.util.concurrent.ThreadContext;
import io.atomix.copycat.client.session.ClientSession;
import io.atomix.copycat.client.session.ClosedSessionException;
import io.atomix.copycat.client.session.Session;
import io.atomix.copycat.client.util.AddressSelector;
import io.atomix.copycat.client.util.ClientSequencer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Default Copycat client implementation.
 *
 * @author  l.accept(state));
    }
  }

  @Override
  public Listener onStateChange(Consumer callback) {
    return new StateChangeListener(callback);
  }

  @Override
  public Transport transport() {
    return transport;
  }

  @Override
  public Serializer serializer() {
    ThreadContext context = ThreadContext.currentContext();
    return context != null ? context.serializer() : this.context.serializer();
  }

  @Override
  public Session session() {
    return session;
  }

  @Override
  public ThreadContext context() {
    return context;
  }

  /**
   * Creates a new child session.
   */
  private ClientSession newSession() {
    ClientSession session = new ClientSession(transport.client(), selector, new SingleThreadContext(threadFactory, context.serializer().clone()), connectionStrategy, retryStrategy);

    // Update the session change listener.
    if (changeListener != null)
      changeListener.close();
    changeListener = session.onStateChange(this::onStateChange);

    // Register all event listeners.
    eventListeners.forEach(l -> l.register(session));
    return session;
  }

  /**
   * Handles a session state change.
   */
  private void onStateChange(Session.State state) {
    switch (state) {
      // When the session is opened, transition the state to CONNECTED.
      case OPEN:
        setState(State.CONNECTED);
        break;
      // When the session becomes unstable, transition the state to SUSPENDED.
      case UNSTABLE:
        setState(State.SUSPENDED);
        break;
      // When the session is expired, transition the state to SUSPENDED if necessary. The recovery strategy
      // must determine whether to attempt to recover the client.
      case EXPIRED:
        setState(State.SUSPENDED);
        recoveryStrategy.recover(this);
      case CLOSED:
        setState(State.CLOSED);
        break;
      default:
        break;
    }
  }

  @Override
  public synchronized CompletableFuture open() {
    if (state != State.CLOSED)
      return CompletableFuture.completedFuture(this);

    if (openFuture == null) {
      openFuture = new CompletableFuture<>();
      session = newSession();
      session.open().whenCompleteAsync((result, error) -> {
        if (error == null) {
          openFuture.complete(this);
        } else {
          openFuture.completeExceptionally(error);
        }
      }, context.executor());
    }
    return openFuture;
  }

  @Override
  public boolean isOpen() {
    return state != State.CLOSED;
  }

  @Override
  public  CompletableFuture submit(Command command) {
    if (session == null)
      return Futures.exceptionalFuture(new ClosedSessionException("session closed"));

    OperationFuture future = new OperationFuture<>(command);
    context.executor().execute(() -> submit(command, session::submit, future));
    return future;
  }

  @Override
  public  CompletableFuture submit(Query query) {
    if (session == null)
      return Futures.exceptionalFuture(new ClosedSessionException("session closed"));

    OperationFuture future = new OperationFuture<>(query);
    context.executor().execute(() -> submit(query, session::submit, future));
    return future;
  }

  /**
   * Submits an operation to the cluster.
   */
  private , U> void submit(T operation, Function> submitter, OperationFuture future) {
    context.checkThread();
    long sequence = sequencer.nextSequence();
    operations.put(sequence, future);
    submitter.apply(operation).whenCompleteAsync((r, e) -> complete(sequence, r, e, future), context.executor());
  }

  /**
   * Resubmits an operation future upon session recovery.
   */
  @SuppressWarnings("unchecked")
  private  void resubmit(long sequence, OperationFuture future) {
    context.checkThread();
    session.submit(future.operation).whenCompleteAsync((r, e) -> complete(sequence, r, e, future), context.executor());
  }

  /**
   * Completes an operation.
   */
  private  void complete(long sequence, T result, Throwable error, OperationFuture future) {
    context.checkThread();
    sequencer.sequence(sequence, () -> {
      if (error == null) {
        operations.remove(sequence);
        future.complete(result);
      } else if (!(error instanceof ClosedSessionException)) {
        operations.remove(sequence);
        future.completeExceptionally(error);
      }
    });
  }

  @Override
  public Listener onEvent(String event, Runnable callback) {
    return onEvent(event, v -> callback.run());
  }

  @Override
  public  Listener onEvent(String event, Consumer callback) {
    EventListener listener = new EventListener<>(event, callback);
    listener.register(session);
    return listener;
  }

  @Override
  public synchronized CompletableFuture recover() {
    if (recoverFuture == null) {
      LOGGER.debug("Recovering session");

      // Open the new child session. If an exception occurs opening the new child session, consider this session expired.
      recoverFuture = new CompletableFuture<>();

      ClientSession session = newSession();
      session.open().whenCompleteAsync((result, error) -> {
        // If the session was opened successfully, resubmit any pending operations.
        if (error == null) {
          this.session = session;
          for (Map.Entry> entry : operations.entrySet()) {
            resubmit(entry.getKey(), entry.getValue());
          }
          recoverFuture.complete(this);
        } else {
          setState(State.CLOSED);
          recoverFuture.completeExceptionally(error);
        }
      }, context.executor());
    }
    return recoverFuture;
  }

  @Override
  public synchronized CompletableFuture close() {
    if (state == State.CLOSED)
      return CompletableFuture.completedFuture(null);

    if (closeFuture == null) {
      // Close the child session and call close listeners once complete.
      closeFuture = new CompletableFuture<>();
      session.close().whenCompleteAsync((result, error) -> {
        setState(State.CLOSED);
        for (Map.Entry> entry : operations.entrySet()) {
          entry.getValue().completeExceptionally(new ClosedSessionException("session closed"));
        }

        CompletableFuture.runAsync(() -> {
          context.close();
          transport.close();
          if (error == null) {
            closeFuture.complete(null);
          } else {
            closeFuture.completeExceptionally(error);
          }
        });
      }, context.executor());
    }
    return closeFuture;
  }

  @Override
  public boolean isClosed() {
    return state == State.CLOSED;
  }

  /**
   * Kills the client.
   *
   * @return A completable future to be completed once the client's session has been killed.
   */
  public synchronized CompletableFuture kill() {
    if (state == State.CLOSED)
      return CompletableFuture.completedFuture(null);

    if (closeFuture == null) {
      closeFuture = session.kill()
        .whenComplete((result, error) -> {
          setState(State.CLOSED);
          CompletableFuture.runAsync(() -> {
            context.close();
            transport.close();
          });
        });
    }
    return closeFuture;
  }

  @Override
  public int hashCode() {
    return 23 + 37 * (session != null ? session.hashCode() : 0);
  }

  @Override
  public boolean equals(Object object) {
    return object instanceof DefaultCopycatClient && ((DefaultCopycatClient) object).session() == session;
  }

  @Override
  public String toString() {
    return String.format("%s[session=%s]", getClass().getSimpleName(), session);
  }

  /**
   * A completable future related to a single operation.
   */
  private static final class OperationFuture extends CompletableFuture {
    private final Operation operation;

    private OperationFuture(Operation operation) {
      this.operation = operation;
    }
  }

  /**
   * State change listener.
   */
  private final class StateChangeListener implements Listener {
    private final Consumer callback;

    protected StateChangeListener(Consumer callback) {
      this.callback = callback;
      changeListeners.add(this);
    }

    @Override
    public void accept(State state) {
      context.executor().execute(() -> callback.accept(state));
    }

    @Override
    public void close() {
      changeListeners.remove(this);
    }
  }

  /**
   * Event listener wrapper.
   */
  private final class EventListener implements Listener {
    private final String event;
    private final Consumer callback;
    private Listener parent;

    private EventListener(String event, Consumer callback) {
      this.event = event;
      this.callback = callback;
      eventListeners.add(this);
    }

    /**
     * Registers the session event listener.
     */
    public void register(Session session) {
      if (ThreadContext.currentContext() == context) {
        parent = session.onEvent(event, callback);
      } else {
        context.execute(() -> {
          parent = session.onEvent(event, callback);
        }).join();
      }
    }

    @Override
    public void accept(T message) {
      context.executor().execute(() -> callback.accept(message));
    }

    @Override
    public void close() {
      parent.close();
      eventListeners.remove(this);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy