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