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

io.zeebe.logstreams.impl.log.LogStreamImpl Maven / Gradle / Ivy

There is a newer version: 1.0.0-alpha7
Show newest version
/*
 * 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.0. You may not use this file
 * except in compliance with the Zeebe Community License 1.0.
 */
package io.zeebe.logstreams.impl.log;

import io.zeebe.dispatcher.Dispatcher;
import io.zeebe.dispatcher.Dispatchers;
import io.zeebe.dispatcher.impl.PositionUtil;
import io.zeebe.logstreams.impl.Loggers;
import io.zeebe.logstreams.log.LogStream;
import io.zeebe.logstreams.log.LogStreamBatchWriter;
import io.zeebe.logstreams.log.LogStreamReader;
import io.zeebe.logstreams.log.LogStreamRecordWriter;
import io.zeebe.logstreams.log.LogStreamWriter;
import io.zeebe.logstreams.spi.LogStorage;
import io.zeebe.util.health.FailureListener;
import io.zeebe.util.health.HealthStatus;
import io.zeebe.util.sched.Actor;
import io.zeebe.util.sched.ActorCondition;
import io.zeebe.util.sched.ActorScheduler;
import io.zeebe.util.sched.channel.ActorConditions;
import io.zeebe.util.sched.future.ActorFuture;
import io.zeebe.util.sched.future.CompletableActorFuture;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import org.slf4j.Logger;

public final class LogStreamImpl extends Actor implements LogStream, FailureListener {
  private static final long INVALID_ADDRESS = -1L;

  private static final Logger LOG = Loggers.LOGSTREAMS_LOGGER;
  private static final String APPENDER_SUBSCRIPTION_NAME = "appender";

  private final ActorConditions onCommitPositionUpdatedConditions;
  private final String logName;
  private final int partitionId;
  private final int maxFrameLength;
  private final ActorScheduler actorScheduler;
  private final List readers;
  private final LogStreamReaderImpl reader;
  private final LogStorage logStorage;
  private final CompletableActorFuture closeFuture;
  private final int nodeId;
  private ActorFuture appenderFuture;
  private Dispatcher writeBuffer;
  private LogStorageAppender appender;
  private long commitPosition;
  private Throwable closeError; // set if any error occurred during closeAsync
  private final String actorName;
  private FailureListener failureListener;

  LogStreamImpl(
      final ActorScheduler actorScheduler,
      final ActorConditions onCommitPositionUpdatedConditions,
      final String logName,
      final int partitionId,
      final int nodeId,
      final int maxFrameLength,
      final LogStorage logStorage) {
    this.actorScheduler = actorScheduler;
    this.onCommitPositionUpdatedConditions = onCommitPositionUpdatedConditions;
    this.logName = logName;

    this.partitionId = partitionId;
    this.nodeId = nodeId;
    this.actorName = buildActorName(nodeId, "LogStream-" + partitionId);

    this.maxFrameLength = maxFrameLength;
    this.logStorage = logStorage;
    this.closeFuture = new CompletableActorFuture<>();

    try {
      logStorage.open();
    } catch (final IOException e) {
      throw new UncheckedIOException(e);
    }

    this.commitPosition = INVALID_ADDRESS;
    this.readers = new ArrayList<>();
    this.reader = new LogStreamReaderImpl(logStorage);
    this.readers.add(reader);

    internalSetCommitPosition(reader.seekToEnd());
  }

  @Override
  public int getPartitionId() {
    return partitionId;
  }

  @Override
  public String getLogName() {
    return logName;
  }

  @Override
  public ActorFuture getCommitPositionAsync() {
    return actor.call(() -> commitPosition);
  }

  @Override
  public void setCommitPosition(final long commitPosition) {
    actor.call(() -> internalSetCommitPosition(commitPosition));
  }

  @Override
  public ActorFuture newLogStreamReader() {
    return actor.call(
        () -> {
          final LogStreamReaderImpl newReader = new LogStreamReaderImpl(logStorage);
          readers.add(newReader);
          return newReader;
        });
  }

  @Override
  public ActorFuture newLogStreamRecordWriter() {
    // this should be replaced after refactoring the actor control
    if (actor.isClosed()) {
      return CompletableActorFuture.completedExceptionally(new RuntimeException("Actor is closed"));
    }

    final var writerFuture = new CompletableActorFuture();
    actor.run(() -> createWriter(writerFuture, LogStreamWriterImpl::new));
    return writerFuture;
  }

  @Override
  public ActorFuture newLogStreamBatchWriter() {
    // this should be replaced after refactoring the actor control
    if (actor.isClosed()) {
      return CompletableActorFuture.completedExceptionally(new RuntimeException("Actor is closed"));
    }

    final var writerFuture = new CompletableActorFuture();
    actor.run(() -> createWriter(writerFuture, LogStreamBatchWriterImpl::new));
    return writerFuture;
  }

  @Override
  public void registerOnCommitPositionUpdatedCondition(final ActorCondition condition) {
    actor.call(() -> onCommitPositionUpdatedConditions.registerConsumer(condition));
  }

  @Override
  public void removeOnCommitPositionUpdatedCondition(final ActorCondition condition) {
    actor.call(() -> onCommitPositionUpdatedConditions.removeConsumer(condition));
  }

  @Override
  public String getName() {
    return actorName;
  }

  @Override
  protected void onActorClosing() {
    LOG.info("On closing logstream {} close {} readers", logName, readers.size());
    readers.forEach(LogStreamReader::close);
    LOG.info("Close log storage with name {}", logName);
    logStorage.close();
  }

  @Override
  protected void onActorClosed() {
    if (closeError != null) {
      closeFuture.completeExceptionally(closeError);
    } else {
      closeFuture.complete(null);
    }
  }

  @Override
  public void close() {
    closeAsync().join();
  }

  @Override
  public ActorFuture closeAsync() {
    if (actor.isClosed()) {
      return closeFuture;
    }

    actor.run(
        () ->
            closeAppender()
                .onComplete(
                    (nothing, appenderError) -> {
                      closeError = appenderError;
                      actor.close();
                    }));
    return closeFuture;
  }

  private void internalSetCommitPosition(final long commitPosition) {
    if (commitPosition > this.commitPosition) {
      this.commitPosition = commitPosition;
      onCommitPositionUpdatedConditions.signalConsumers();
    }
  }

  private  void createWriter(
      final CompletableActorFuture writerFuture, final WriterCreator creator) {
    if (appender != null) {
      writerFuture.complete(creator.create(partitionId, writeBuffer));
    } else if (appenderFuture != null) {
      appenderFuture.onComplete(onOpenAppender(writerFuture, creator));
    } else {
      openAppender().onComplete(onOpenAppender(writerFuture, creator));
    }
  }

  private  BiConsumer onOpenAppender(
      final CompletableActorFuture writerFuture, final WriterCreator creator) {
    return (openedAppender, errorOnOpeningAppender) -> {
      if (errorOnOpeningAppender == null) {
        writerFuture.complete(creator.create(partitionId, writeBuffer));
      } else {
        writerFuture.completeExceptionally(errorOnOpeningAppender);
      }
    };
  }

  private ActorFuture closeAppender() {
    final var closeAppenderFuture = new CompletableActorFuture();
    if (appender == null) {
      closeAppenderFuture.complete(null);
      return closeAppenderFuture;
    }

    appenderFuture = null;
    LOG.info("Close appender for log stream {}", logName);
    final var toCloseAppender = appender;
    final var toCloseWriteBuffer = writeBuffer;
    appender = null;
    writeBuffer = null;
    toCloseAppender
        .closeAsync()
        .onComplete(
            (v, t) -> {
              if (t == null) {
                toCloseWriteBuffer.closeAsync().onComplete(closeAppenderFuture);
              } else {
                closeAppenderFuture.completeExceptionally(t);
              }
            });
    return closeAppenderFuture;
  }

  private ActorFuture openAppender() {
    if (appenderFuture != null) {
      return appenderFuture;
    }

    final var appenderOpenFuture = new CompletableActorFuture();

    appenderFuture = appenderOpenFuture;
    final int initialDispatcherPartitionId = determineInitialPartitionId();
    writeBuffer =
        Dispatchers.create(buildActorName(nodeId, "dispatcher-" + partitionId))
            .maxFragmentLength(maxFrameLength)
            .initialPartitionId(initialDispatcherPartitionId + 1)
            .name(logName + "-write-buffer")
            .actorScheduler(actorScheduler)
            .build();

    writeBuffer
        .openSubscriptionAsync(APPENDER_SUBSCRIPTION_NAME)
        .onComplete(
            (subscription, throwable) -> {
              if (throwable == null) {
                appender =
                    new LogStorageAppender(
                        buildActorName(nodeId, "LogAppender-" + partitionId),
                        partitionId,
                        logStorage,
                        subscription,
                        maxFrameLength);

                actorScheduler
                    .submitActor(appender)
                    .onComplete(
                        (v, t) -> {
                          if (t != null) {
                            onOpenAppenderFailed(t);
                          } else {
                            appenderFuture.complete(appender);
                            appender.addFailureListener(this);
                          }
                        });
              } else {
                onOpenAppenderFailed(throwable);
              }
            });

    return appenderOpenFuture;
  }

  private void onOpenAppenderFailed(final Throwable error) {
    LOG.error("Unexpected error when opening appender", error);
    appenderFuture.completeExceptionally(error);
    onFailure();
  }

  private int determineInitialPartitionId() {
    try (final LogStreamReaderImpl logReader = new LogStreamReaderImpl(logStorage)) {

      // Get position of last entry
      final long lastPosition = logReader.seekToEnd();

      // dispatcher needs to generate positions greater than the last position
      int dispatcherPartitionId = 0;

      if (lastPosition > 0) {
        dispatcherPartitionId = PositionUtil.partitionId(lastPosition);
      }

      return dispatcherPartitionId;
    }
  }

  @Override
  public HealthStatus getHealthStatus() {
    return actor.isClosed() ? HealthStatus.UNHEALTHY : HealthStatus.HEALTHY;
  }

  @Override
  public void addFailureListener(final FailureListener failureListener) {
    actor.run(() -> this.failureListener = failureListener);
  }

  @Override
  public void onFailure() {
    actor.run(
        () -> {
          if (failureListener != null) {
            failureListener.onFailure();
          }
          closeAsync();
        });
  }

  @Override
  public void onRecovered() {
    actor.run(
        () -> {
          if (failureListener != null) {
            failureListener.onRecovered();
          }
        });
  }

  @FunctionalInterface
  private interface WriterCreator {
    T create(int partitionId, Dispatcher dispatcher);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy