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

io.camunda.zeebe.logstreams.impl.log.Sequencer 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.camunda.zeebe.logstreams.impl.log;

import static io.camunda.zeebe.logstreams.impl.serializer.DataFrameDescriptor.FRAME_ALIGNMENT;

import io.camunda.zeebe.logstreams.impl.serializer.DataFrameDescriptor;
import io.camunda.zeebe.logstreams.log.LogAppendEntry;
import io.camunda.zeebe.logstreams.log.LogStreamWriter;
import io.camunda.zeebe.scheduler.ActorCondition;
import io.camunda.zeebe.scheduler.clock.ActorClock;
import io.camunda.zeebe.util.Either;
import java.io.Closeable;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The sequencer is a multiple-producer, single-consumer queue of {@link LogAppendEntry}. It buffers
 * a fixed amount of entries and rejects writes when the queue is full. The consumer may read at its
 * own pace by repeatedly calling {@link Sequencer#tryRead()} or register for notifications when new
 * entries are written by calling {@link Sequencer#registerConsumer(ActorCondition)}.
 *
 * 

The sequencer assigns all entries a position and makes that position available to its * consumer. The sequencer does not copy or serialize entries, it only keeps a reference to them * until they are handed off to the consumer. */ final class Sequencer implements LogStreamWriter, Closeable { private static final Logger LOG = LoggerFactory.getLogger(Sequencer.class); private final int maxFragmentSize; private volatile long position; private volatile boolean isClosed = false; private volatile ActorCondition consumer; private final Queue queue = new ArrayBlockingQueue<>(128); private final ReentrantLock lock = new ReentrantLock(); private final SequencerMetrics metrics; Sequencer(final long initialPosition, final int maxFragmentSize, final SequencerMetrics metrics) { LOG.trace("Starting new sequencer at position {}", initialPosition); position = initialPosition; this.maxFragmentSize = maxFragmentSize; this.metrics = Objects.requireNonNull(metrics, "must specify metrics"); } /** {@inheritDoc} */ @Override public boolean canWriteEvents(final int eventCount, final int batchSize) { final int framedMessageLength = batchSize + eventCount * (DataFrameDescriptor.HEADER_LENGTH + FRAME_ALIGNMENT) + FRAME_ALIGNMENT; return framedMessageLength <= maxFragmentSize; } /** {@inheritDoc} */ @Override public Either tryWrite( final List appendEntries, final long sourcePosition) { if (isClosed) { LOG.warn("Rejecting write of {}, sequencer is closed", appendEntries); return Either.left(WriteFailure.CLOSED); } for (final var entry : appendEntries) { if (!isEntryValid(entry)) { LOG.warn("Reject write of invalid entry {}", entry); return Either.left(WriteFailure.INVALID_ARGUMENT); } } final var batchSize = appendEntries.size(); if (batchSize == 0) { return Either.left(WriteFailure.INVALID_ARGUMENT); } final long currentPosition; final boolean isEnqueued; lock.lock(); try { currentPosition = position; final var sequencedBatch = new SequencedBatch( ActorClock.currentTimeMillis(), currentPosition, sourcePosition, appendEntries); isEnqueued = queue.offer(sequencedBatch); if (isEnqueued) { metrics.observeBatchLengthBytes(sequencedBatch.length()); position = currentPosition + batchSize; } } finally { lock.unlock(); } if (consumer != null) { consumer.signal(); } metrics.setQueueSize(queue.size()); if (isEnqueued) { metrics.observeBatchSize(batchSize); return Either.right(currentPosition + batchSize - 1); } else { LOG.trace("Rejecting write of {}, sequencer queue is full", appendEntries); return Either.left(WriteFailure.FULL); } } /** * Retrieves, but does not remove, the first item in the sequenced batch queue. * * @return A {@link SequencedBatch} or null if none is available */ SequencedBatch tryRead() { return queue.poll(); } /** * Closes the sequencer. After closing, writes are rejected but reads are still allowed to drain * the queue. Closing the sequencer is not atomic so some writes may occur shortly after closing. */ @Override public void close() { LOG.info("Closing sequencer for writing"); isClosed = true; } void registerConsumer(final ActorCondition consumer) { this.consumer = consumer; } private boolean isEntryValid(final LogAppendEntry entry) { return entry.recordValue() != null && entry.recordValue().getLength() > 0 && entry.recordMetadata() != null && entry.recordMetadata().getLength() > 0; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy