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

io.zeebe.broker.system.partitions.impl.PartitionTransitionImpl Maven / Gradle / Ivy

/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Zeebe Community License 1.1. You may not use this file
 * except in compliance with the Zeebe Community License 1.1.
 */
package io.zeebe.broker.system.partitions.impl;

import io.zeebe.broker.Loggers;
import io.zeebe.broker.system.partitions.PartitionContext;
import io.zeebe.broker.system.partitions.PartitionStep;
import io.zeebe.broker.system.partitions.PartitionTransition;
import io.zeebe.util.exception.UnrecoverableException;
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.slf4j.Logger;

public class PartitionTransitionImpl implements PartitionTransition {

  private static final Logger LOG = Loggers.SYSTEM_LOGGER;
  private static final List EMPTY_LIST = Collections.emptyList();
  private static final int INACTIVE_TERM = -1;

  private final PartitionContext context;
  private final List leaderSteps;
  private final List followerSteps;
  private final List openedSteps = new ArrayList<>();
  private CompletableActorFuture currentTransition = CompletableActorFuture.completed(null);

  public PartitionTransitionImpl(
      final PartitionContext context,
      final List leaderSteps,
      final List followerSteps) {
    this.context = context;
    this.leaderSteps = leaderSteps;
    this.followerSteps = followerSteps;
  }

  @Override
  public ActorFuture toFollower(final long currentTerm) {
    return enqueueTransition(currentTerm, followerSteps);
  }

  @Override
  public ActorFuture toLeader(final long currentTerm) {
    return enqueueTransition(currentTerm, leaderSteps);
  }

  @Override
  public ActorFuture toInactive() {
    return enqueueTransition(INACTIVE_TERM, EMPTY_LIST);
  }

  /**
   * This method allows to enqueue the next transition, such that the transitions are executed in
   * order. Previous we had the issue that all transitions have subscribe to the current transition,
   * which lead to undefined behavior.
   *
   * @param partitionStepList the steps which should be installed on the transition
   */
  private ActorFuture enqueueTransition(
      final long currentTerm, final List partitionStepList) {
    final var nextTransitionFuture = new CompletableActorFuture();
    final var nextCurrentTransition = currentTransition;
    currentTransition = nextTransitionFuture;
    nextCurrentTransition.onComplete(
        (nothing, err) -> transition(currentTerm, nextTransitionFuture, partitionStepList));
    return nextTransitionFuture;
  }

  private void transition(
      final long currentTerm,
      final CompletableActorFuture future,
      final List steps) {
    closePartition()
        .onComplete(
            (nothing, err) -> {
              if (err instanceof UnrecoverableException) {
                future.completeExceptionally(err);
              } else {
                installPartition(currentTerm, future, new ArrayList<>(steps));
              }
            });
  }

  private void installPartition(
      final long currentTerm,
      final CompletableActorFuture future,
      final List steps) {
    if (steps.isEmpty()) {
      LOG.debug(
          "Partition {} transition complete, installed {} resources!",
          context.getPartitionId(),
          openedSteps.size());
      future.complete(null);
      return;
    }

    final PartitionStep step = steps.remove(0);
    try {
      step.open(currentTerm, context)
          .onComplete(
              (value, err) -> {
                if (err != null) {
                  LOG.error("Expected to open step '{}' but failed with", step.getName(), err);
                  tryCloseStep(step);
                  future.completeExceptionally(err);
                } else {
                  openedSteps.add(step);
                  installPartition(currentTerm, future, steps);
                }
              });
    } catch (final Exception e) {
      LOG.error("Expected to open step '{}' but failed with", step.getName(), e);
      tryCloseStep(step);
      future.completeExceptionally(e);
    }
  }

  private void tryCloseStep(final PartitionStep step) {
    // close if there's anything to close. Don't add to 'opened' list, since the open did not
    // complete, the close might also fail but that shouldn't prevent the next transition
    try {
      step.close(context);
    } catch (final Exception e) {
      LOG.debug("Couldn't close partition step '{}' that failed to open", step.getName(), e);
    }
  }

  private CompletableActorFuture closePartition() {
    final var closingSteps = new ArrayList<>(openedSteps);
    Collections.reverse(closingSteps);
    return closeSteps(closingSteps);
  }

  private CompletableActorFuture closeSteps(final List steps) {
    final var closingPartitionFuture = new CompletableActorFuture();
    closeNextStep(closingPartitionFuture, steps, null);
    return closingPartitionFuture;
  }

  private void closeNextStep(
      final CompletableActorFuture future,
      final List steps,
      final Throwable throwable) {
    if (steps.isEmpty()) {
      LOG.debug(
          "Partition {} closed all previous open resources, before transitioning.",
          context.getPartitionId());
      if (throwable == null) {
        future.complete(null);
      } else {
        future.completeExceptionally(throwable);
      }
      return;
    }

    final PartitionStep step = steps.remove(0);
    LOG.debug("Closing Zeebe-Partition-{}: {}", context.getPartitionId(), step.getName());

    try {
      step.close(context)
          .onComplete(
              (v, closingError) -> {
                if (closingError == null) {
                  LOG.debug(
                      "Closing Zeebe-Partition-{}: {} closed successfully",
                      context.getPartitionId(),
                      step.getName());
                } else {
                  LOG.error(
                      "Closing Zeebe-Partition-{}: {} failed to close. Closing remaining steps",
                      context.getPartitionId(),
                      step.getName(),
                      closingError);
                }

                openedSteps.remove(step);
                closeNextStep(future, steps, throwable != null ? throwable : closingError);
              });
    } catch (final Exception e) {
      LOG.error(
          "Zeebe-Partition-{}: Step {} failed to close with uncaught exception",
          context.getPartitionId(),
          step.getName(),
          e);
      openedSteps.remove(step);
      closeNextStep(future, steps, e);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy