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

net.kuujo.copycat.event.internal.DefaultEventLog Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 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 net.kuujo.copycat.event.internal;

import net.kuujo.copycat.EventListener;
import net.kuujo.copycat.event.EventLog;
import net.kuujo.copycat.event.EventLogConfig;
import net.kuujo.copycat.log.LogSegment;
import net.kuujo.copycat.resource.internal.AbstractResource;
import net.kuujo.copycat.resource.internal.ResourceManager;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Default event log partition implementation.
 *
 * @author Jordan Halterman
 */
public class DefaultEventLog extends AbstractResource> implements EventLog {
  private EventListener consumer;
  private ScheduledFuture retentionFuture;
  private Long commitIndex;

  public DefaultEventLog(ResourceManager context) {
    super(context);
    context.consumer(this::consume);
  }

  @Override
  public EventLog consumer(EventListener consumer) {
    this.consumer = consumer;
    return this;
  }

  @Override
  public CompletableFuture get(long index) {
    CompletableFuture future = new CompletableFuture<>();
    context.execute(() -> {
      if (!context.log().containsIndex(index)) {
        executor.execute(() -> future.completeExceptionally(new IndexOutOfBoundsException(String.format("Log index %d out of bounds", index))));
      } else {
        ByteBuffer buffer = context.log().getEntry(index);
        if (buffer != null) {
          T entry = serializer.readObject(buffer);
          executor.execute(() -> future.complete(entry));
        } else {
          executor.execute(() -> future.complete(null));
        }
      }
    });
    return future;
  }

  @Override
  public CompletableFuture commit(T entry) {
    return context.commit(serializer.writeObject(entry)).thenApplyAsync(ByteBuffer::getLong, executor);
  }

  /**
   * Handles a log write.
   */
  private ByteBuffer consume(long term, Long index, ByteBuffer entry) {
    ByteBuffer result = ByteBuffer.allocateDirect(8);
    result.putLong(index);
    if (consumer != null) {
      T value = serializer.readObject(entry);
      executor.execute(() -> consumer.accept(value));
    }
    commitIndex = index;
    result.flip();
    return result;
  }

  /**
   * Compacts the log.
   */
  private synchronized void compact() {
    if (commitIndex != null) {
      // Iterate through segments in the log and remove/close/delete segments that should no longer be retained.
      // A segment is no longer retained if all of the following conditions are met:
      // - The segment is not the last segment in the log
      // - The segment's last index is less than or equal to the commit index
      // - The configured retention policy's retain(LogSegment) method returns false.
      for (Iterator> iterator = context.log().segments().entrySet().iterator(); iterator.hasNext(); ) {
        Map.Entry entry = iterator.next();
        LogSegment segment = entry.getValue();
        if (context.log().lastSegment() != segment
          && segment.lastIndex() != null
          && segment.lastIndex() <= commitIndex
          && !context.config().getResourceConfig().getRetentionPolicy().retain(entry.getValue())) {
          iterator.remove();
          try {
            segment.close();
            segment.delete();
          } catch (IOException e) {
          }
        }
      }
    }
  }

  @Override
  public synchronized CompletableFuture> open() {
    return runStartupTasks()
      .thenComposeAsync(v -> context.open(), executor)
      .thenRun(() -> {
        retentionFuture = context.scheduleWithFixedDelay(this::compact, 0, context.config().getResourceConfig().getRetentionCheckInterval(), TimeUnit.MILLISECONDS);
      })
      .thenApply(v -> this);
  }

  @Override
  public synchronized CompletableFuture close() {
    if (retentionFuture != null) {
      retentionFuture.cancel(false);
    }
    return context.close()
      .thenCompose(v -> runShutdownTasks());
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy