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

io.zeebe.gossip.failuredetection.JoinController Maven / Gradle / Ivy

There is a newer version: 0.16.4
Show newest version
/*
 * Copyright © 2017 camunda services GmbH ([email protected])
 *
 * 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.zeebe.gossip.failuredetection;

import io.zeebe.clustering.gossip.MembershipEventType;
import io.zeebe.gossip.GossipConfiguration;
import io.zeebe.gossip.GossipContext;
import io.zeebe.gossip.GossipMath;
import io.zeebe.gossip.Loggers;
import io.zeebe.gossip.dissemination.DisseminationComponent;
import io.zeebe.gossip.membership.Member;
import io.zeebe.gossip.membership.MembershipList;
import io.zeebe.gossip.membership.MembershipStatus;
import io.zeebe.gossip.protocol.GossipEvent;
import io.zeebe.gossip.protocol.GossipEventFactory;
import io.zeebe.gossip.protocol.GossipEventSender;
import io.zeebe.transport.ClientResponse;
import io.zeebe.transport.RemoteAddress;
import io.zeebe.transport.SocketAddress;
import io.zeebe.util.sched.ActorControl;
import io.zeebe.util.sched.future.ActorFuture;
import io.zeebe.util.sched.future.CompletableActorFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.agrona.DirectBuffer;
import org.slf4j.Logger;

public class JoinController {
  private static final Logger LOG = Loggers.GOSSIP_LOGGER;

  private final ActorControl actor;

  private final GossipConfiguration configuration;

  private final Member self;
  private final MembershipList membershipList;

  private final DisseminationComponent disseminationComponent;

  private final GossipEventSender gossipEventSender;
  private final GossipEventFactory gossipEventFactory;

  private final GossipEvent ackResponse;
  private final GossipEvent syncResponse;

  private List contactPoints;

  private boolean isJoined;
  private CompletableActorFuture joinFuture;
  private CompletableActorFuture leaveFuture;

  public JoinController(GossipContext context, ActorControl actor) {
    this.actor = actor;
    this.configuration = context.getConfiguration();

    this.self = context.getMembershipList().self();
    this.membershipList = context.getMembershipList();
    this.disseminationComponent = context.getDisseminationComponent();
    this.gossipEventSender = context.getGossipEventSender();
    this.gossipEventFactory = context.getGossipEventFactory();

    this.ackResponse = gossipEventFactory.createAckResponse();
    this.syncResponse = gossipEventFactory.createSyncResponse();
  }

  public void join(List contactPoints, CompletableActorFuture future) {
    if (isJoined) {
      future.completeExceptionally(new IllegalStateException("Already joined."));
    } else if (contactPoints == null || contactPoints.isEmpty()) {
      future.completeExceptionally(
          new IllegalArgumentException("Can't join cluster without contact points."));
    } else if (joinFuture != null) {
      future.completeExceptionally(new IllegalStateException(("Currently join in progress.")));
    } else {
      this.joinFuture = future;
      this.contactPoints = contactPoints;

      sendJoinEvent();
    }
  }

  private void sendJoinEvent() {
    final List> requestFutures = new ArrayList<>(contactPoints.size());

    self.getTerm().increment();

    for (SocketAddress contactPoint : contactPoints) {
      LOG.trace("Spread JOIN event to contact point '{}'", contactPoint);

      disseminationComponent
          .addMembershipEvent()
          .memberId(self.getId())
          .type(MembershipEventType.JOIN)
          .gossipTerm(self.getTerm());

      final ActorFuture requestFuture =
          gossipEventSender.sendPing(contactPoint, configuration.getJoinTimeoutDuration());
      requestFutures.add(requestFuture);
    }

    actor.runOnFirstCompletion(
        requestFutures,
        (response, failure) -> {
          if (failure == null) {
            processAckResponse(response);

            actor.submit(
                () -> sendSyncRequest(ackResponse.getSenderId(), response.getRemoteAddress()));
          } else {
            LOG.info(
                "Failed to contact any of '{}'. Try again in {}",
                contactPoints,
                configuration.getJoinInterval());

            actor.runDelayed(configuration.getJoinIntervalDuration(), this::sendJoinEvent);
          }
        },
        this::processAckResponse);
  }

  private void processAckResponse(ClientResponse response) {
    final DirectBuffer responseBuffer = response.getResponseBuffer();
    ackResponse.wrap(responseBuffer, 0, responseBuffer.capacity());
  }

  private void sendSyncRequest(final int nodeId, final RemoteAddress remoteAddress) {
    LOG.trace("Send SYNC request to node '{}' with address '{}'", nodeId, remoteAddress);

    final ActorFuture requestFuture =
        gossipEventSender.sendSyncRequest(
            nodeId, remoteAddress, configuration.getSyncTimeoutDuration());

    actor.runOnCompletion(
        requestFuture,
        (request, failure) -> {
          if (failure == null) {
            LOG.debug("Received SYNC response.");

            // process response
            final DirectBuffer response = request.getResponseBuffer();
            syncResponse.wrap(response, 0, response.capacity());

            isJoined = true;
            joinFuture.complete(null);
            joinFuture = null;

            LOG.debug("Joined cluster successfully");
          } else {
            LOG.debug(
                "Failed to receive SYNC response from node '{}'. Try again in {}",
                nodeId,
                configuration.getJoinInterval());

            actor.runDelayed(configuration.getJoinIntervalDuration(), this::sendJoinEvent);
          }
        });
  }

  public void leave(CompletableActorFuture future) {
    if (!isJoined) {
      future.complete(null);
    } else if (leaveFuture != null) {
      future.completeExceptionally(new IllegalStateException(("Currently leave in progress.")));
    } else {
      this.leaveFuture = future;
      sendLeaveEvent();
    }
  }

  private void sendLeaveEvent() {
    final Member self = membershipList.self();

    self.getTerm().increment();

    disseminationComponent
        .addMembershipEvent()
        .memberId(self.getId())
        .type(MembershipEventType.LEAVE)
        .gossipTerm(self.getTerm());

    // spread LEAVE event to random members
    final List members = new ArrayList<>(membershipList.getMembersView());
    Collections.shuffle(members);

    final int clusterSize = membershipList.size();
    final int multiplier = configuration.getRetransmissionMultiplier();
    final int spreadLimit =
        Math.min(GossipMath.gossipPeriodsToSpread(multiplier, clusterSize), clusterSize);

    final List> requestFutures = new ArrayList<>(spreadLimit);

    int spreadCount = 0;
    for (int m = 0; m < members.size() && spreadCount < spreadLimit; m++) {
      final Member member = members.get(m);

      if (member.getStatus() == MembershipStatus.ALIVE) {
        LOG.trace("Spread LEAVE event to node '{}'", member.getId());

        final ActorFuture requestFuture =
            gossipEventSender.sendPing(member.getId(), configuration.getLeaveTimeoutDuration());
        requestFutures.add(requestFuture);

        spreadCount += 1;
      }
    }

    actor.runOnCompletion(
        requestFutures,
        (failure) -> {
          if (failure == null) {
            LOG.info("Left cluster successfully");
          } else {
            LOG.info(
                "Left cluster but timeout is reached before event is confirmed by all members");
          }

          isJoined = false;
          leaveFuture.complete(null);
          leaveFuture = null;
        });
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy