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

io.aeron.Publication Maven / Gradle / Ivy

/*
 * Copyright 2014-2017 Real Logic Ltd.
 *
 * 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 io.aeron;

import io.aeron.logbuffer.*;
import org.agrona.DirectBuffer;
import org.agrona.ManagedResource;
import org.agrona.concurrent.UnsafeBuffer;
import org.agrona.concurrent.status.ReadablePosition;

import static io.aeron.logbuffer.LogBufferDescriptor.*;
import static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH;

/**
 * Aeron Publisher API for sending messages to subscribers of a given channel and streamId pair. {@link Publication}s
 * are created via the {@link Aeron#addPublication(String, int)} method, and messages are sent via one of the
 * {@link #offer(DirectBuffer)} methods, or a {@link #tryClaim(int, BufferClaim)} and {@link BufferClaim#commit()}
 * method combination.
 *
 * The APIs used try claim and offer are non-blocking.
 *
 * Note: Publication instances are threadsafe and can be shared between publishing threads.
 *
 * @see Aeron#addPublication(String, int)
 * @see BufferClaim
 */
public class Publication implements AutoCloseable
{
    /**
     * The publication is not yet connected to a subscriber.
     */
    public static final long NOT_CONNECTED = -1;

    /**
     * The offer failed due to back pressure from the subscribers preventing further transmission.
     */
    public static final long BACK_PRESSURED = -2;

    /**
     * The offer failed due to an administration action and should be retried.
     */
    public static final long ADMIN_ACTION = -3;

    /**
     * The {@link Publication} has been closed and should no longer be used.
     */
    public static final long CLOSED = -4;

    private final long registrationId;
    private int refCount = 0;
    private final int streamId;
    private final int sessionId;
    private final int initialTermId;
    private final int maxMessageLength;
    private final int maxPayloadLength;
    private final int positionBitsToShift;
    private volatile boolean isClosed = false;

    private final TermAppender[] termAppenders = new TermAppender[PARTITION_COUNT];
    private final ReadablePosition positionLimit;
    private final UnsafeBuffer logMetaDataBuffer;
    private final HeaderWriter headerWriter;
    private final LogBuffers logBuffers;
    private final ClientConductor conductor;
    private final String channel;

    Publication(
        final ClientConductor clientConductor,
        final String channel,
        final int streamId,
        final int sessionId,
        final ReadablePosition positionLimit,
        final LogBuffers logBuffers,
        final long registrationId)
    {
        final UnsafeBuffer[] buffers = logBuffers.termBuffers();
        final UnsafeBuffer logMetaDataBuffer = logBuffers.metaDataBuffer();

        for (int i = 0; i < PARTITION_COUNT; i++)
        {
            termAppenders[i] = new TermAppender(buffers[i], logMetaDataBuffer, i);
        }

        final int termLength = logBuffers.termLength();
        this.maxPayloadLength = LogBufferDescriptor.mtuLength(logMetaDataBuffer) - HEADER_LENGTH;
        this.maxMessageLength = FrameDescriptor.computeMaxMessageLength(termLength);
        this.conductor = clientConductor;
        this.channel = channel;
        this.streamId = streamId;
        this.sessionId = sessionId;
        this.initialTermId = LogBufferDescriptor.initialTermId(logMetaDataBuffer);
        this.logMetaDataBuffer = logMetaDataBuffer;
        this.registrationId = registrationId;
        this.positionLimit = positionLimit;
        this.logBuffers = logBuffers;
        this.positionBitsToShift = Integer.numberOfTrailingZeros(termLength);
        this.headerWriter = new HeaderWriter(defaultFrameHeader(logMetaDataBuffer));
    }

    /**
     * Get the length in bytes for each term partition in the log buffer.
     *
     * @return the length in bytes for each term partition in the log buffer.
     */
    public int termBufferLength()
    {
        return logBuffers.termLength();
    }

    /**
     * Media address for delivery to the channel.
     *
     * @return Media address for delivery to the channel.
     */
    public String channel()
    {
        return channel;
    }

    /**
     * Stream identity for scoping within the channel media address.
     *
     * @return Stream identity for scoping within the channel media address.
     */
    public int streamId()
    {
        return streamId;
    }

    /**
     * Session under which messages are published. Identifies this Publication instance.
     *
     * @return the session id for this publication.
     */
    public int sessionId()
    {
        return sessionId;
    }

    /**
     * The initial term id assigned when this {@link Publication} was created. This can be used to determine how many
     * terms have passed since creation.
     *
     * @return the initial term id.
     */
    public int initialTermId()
    {
        return initialTermId;
    }

    /**
     * Maximum message length supported in bytes. Messages may be made of multiple fragments if greater than
     * MTU length.
     *
     * @return maximum message length supported in bytes.
     */
    public int maxMessageLength()
    {
        return maxMessageLength;
    }

    /**
     * Maximum length of a message payload that fits within a message fragment.
     *
     * This is he MTU length minus the message fragment header length.
     *
     * @return maximum message fragment payload length.
     */
    public int maxPayloadLength()
    {
        return maxPayloadLength;
    }

    /**
     * Return the registration id used to register this Publication with the media driver.
     *
     * @return registration id
     */
    public long registrationId()
    {
        return registrationId;
    }

    /**
     * Has the {@link Publication} seen an active Subscriber recently?
     *
     * @return true if this {@link Publication} has seen an active subscriber otherwise false.
     */
    public boolean isConnected()
    {
        return !isClosed && conductor.isPublicationConnected(timeOfLastStatusMessage(logMetaDataBuffer));
    }

    /**
     * Release resources used by this Publication when there are no more references.
     *
     * Publications are reference counted and are only truly closed when the ref count reaches zero.
     */
    public void close()
    {
        conductor.clientLock().lock();
        try
        {
            if (!isClosed && --refCount == 0)
            {
                isClosed = true;
                conductor.releasePublication(this);
            }
        }
        finally
        {
            conductor.clientLock().unlock();
        }
    }

    /**
     * Has this object been closed and should no longer be used?
     *
     * @return true if it has been closed otherwise false.
     */
    public boolean isClosed()
    {
        return isClosed;
    }

    /**
     * Forcibly close the Publication and release resources
     */
    void forceClose()
    {
        if (!isClosed)
        {
            isClosed = true;
            conductor.asyncReleasePublication(registrationId);
            conductor.lingerResource(managedResource());
        }
    }

    /**
     * Get the current position to which the publication has advanced for this stream.
     *
     * @return the current position to which the publication has advanced for this stream.
     * @throws IllegalStateException if the publication is closed.
     */
    public long position()
    {
        if (isClosed)
        {
            return CLOSED;
        }

        final long rawTail = rawTailVolatile(logMetaDataBuffer);
        final int termOffset = termOffset(rawTail, logBuffers.termLength());

        return computePosition(termId(rawTail), termOffset, positionBitsToShift, initialTermId);
    }

    /**
     * Get the position limit beyond which this {@link Publication} will be back pressured.
     *
     * This should only be used as a guide to determine when back pressure is likely to be applied.
     *
     * @return the position limit beyond which this {@link Publication} will be back pressured.
     */
    public long positionLimit()
    {
        if (isClosed)
        {
            return CLOSED;
        }

        return positionLimit.getVolatile();
    }

    /**
     * Non-blocking publish of a buffer containing a message.
     *
     * @param buffer containing message.
     * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED},
     * {@link #ADMIN_ACTION}, or {@link #CLOSED}.
     */
    public long offer(final DirectBuffer buffer)
    {
        return offer(buffer, 0, buffer.capacity());
    }

    /**
     * Non-blocking publish of a partial buffer containing a message.
     *
     * @param buffer containing message.
     * @param offset offset in the buffer at which the encoded message begins.
     * @param length in bytes of the encoded message.
     * @return The new stream position, otherwise a negative error value {@link #NOT_CONNECTED},
     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, or {@link #CLOSED}.
     */
    public long offer(final DirectBuffer buffer, final int offset, final int length)
    {
        return offer(buffer, offset, length, null);
    }

    /**
     * Non-blocking publish of a partial buffer containing a message.
     *
     * @param buffer                containing message.
     * @param offset                offset in the buffer at which the encoded message begins.
     * @param length                in bytes of the encoded message.
     * @param reservedValueSupplier {@link ReservedValueSupplier} for the frame.
     * @return The new stream position, otherwise a negative error value {@link #NOT_CONNECTED},
     * {@link #BACK_PRESSURED}, {@link #ADMIN_ACTION}, or {@link #CLOSED}.
     */
    public long offer(
        final DirectBuffer buffer,
        final int offset,
        final int length,
        final ReservedValueSupplier reservedValueSupplier)
    {
        long newPosition = CLOSED;
        if (!isClosed)
        {
            final long limit = positionLimit.getVolatile();
            final int partitionIndex = activePartitionIndex(logMetaDataBuffer);
            final TermAppender termAppender = termAppenders[partitionIndex];
            final long rawTail = termAppender.rawTailVolatile();
            final long termOffset = rawTail & 0xFFFF_FFFFL;
            final long position =
                computeTermBeginPosition(termId(rawTail), positionBitsToShift, initialTermId) + termOffset;

            if (position < limit)
            {
                final long result;
                if (length <= maxPayloadLength)
                {
                    result = termAppender.appendUnfragmentedMessage(
                        headerWriter, buffer, offset, length, reservedValueSupplier);
                }
                else
                {
                    checkForMaxMessageLength(length);
                    result = termAppender.appendFragmentedMessage(
                        headerWriter, buffer, offset, length, maxPayloadLength, reservedValueSupplier);
                }

                newPosition = newPosition(partitionIndex, (int)termOffset, position, result);
            }
            else if (conductor.isPublicationConnected(timeOfLastStatusMessage(logMetaDataBuffer)))
            {
                newPosition = BACK_PRESSURED;
            }
            else
            {
                newPosition = NOT_CONNECTED;
            }
        }

        return newPosition;
    }

    /**
     * Try to claim a range in the publication log into which a message can be written with zero copy semantics.
     * Once the message has been written then {@link BufferClaim#commit()} should be called thus making it available.
     *
     * Note: This method can only be used for message lengths less than MTU length minus header.
     *
     * 
{@code
     *     final BufferClaim bufferClaim = new BufferClaim(); // Can be stored and reused to avoid allocation
     *
     *     if (publication.tryClaim(messageLength, bufferClaim) > 0L)
     *     {
     *         try
     *         {
     *              final MutableDirectBuffer buffer = bufferClaim.buffer();
     *              final int offset = bufferClaim.offset();
     *
     *              // Work with buffer directly or wrap with a flyweight
     *         }
     *         finally
     *         {
     *             bufferClaim.commit();
     *         }
     *     }
     * }
* * @param length of the range to claim, in bytes.. * @param bufferClaim to be populated if the claim succeeds. * @return The new stream position, otherwise {@link #NOT_CONNECTED}, {@link #BACK_PRESSURED}, * {@link #ADMIN_ACTION}, or {@link #CLOSED}. * @throws IllegalArgumentException if the length is greater than {@link #maxPayloadLength()} within an MTU. * @see BufferClaim#commit() * @see BufferClaim#abort() */ public long tryClaim(final int length, final BufferClaim bufferClaim) { checkForMaxPayloadLength(length); long newPosition = CLOSED; if (!isClosed) { final long limit = positionLimit.getVolatile(); final int partitionIndex = activePartitionIndex(logMetaDataBuffer); final TermAppender termAppender = termAppenders[partitionIndex]; final long rawTail = termAppender.rawTailVolatile(); final long termOffset = rawTail & 0xFFFF_FFFFL; final long position = computeTermBeginPosition(termId(rawTail), positionBitsToShift, initialTermId) + termOffset; if (position < limit) { final long result = termAppender.claim(headerWriter, length, bufferClaim); newPosition = newPosition(partitionIndex, (int)termOffset, position, result); } else if (conductor.isPublicationConnected(timeOfLastStatusMessage(logMetaDataBuffer))) { newPosition = BACK_PRESSURED; } else { newPosition = NOT_CONNECTED; } } return newPosition; } /** * Add a destination manually to a multi-destination-cast Publication. * * @param endpointChannel for the destination to add */ public void addDestination(final String endpointChannel) { conductor.clientLock().lock(); try { conductor.addDestination(registrationId, endpointChannel); } finally { conductor.clientLock().unlock(); } } /** * Remove a previously added destination manually from a multi-destination-cast Publication. * * @param endpointChannel for the destination to remove */ public void removeDestination(final String endpointChannel) { conductor.clientLock().lock(); try { conductor.removeDestination(registrationId, endpointChannel); } finally { conductor.clientLock().unlock(); } } /** * @see Publication#close() */ void incRef() { ++refCount; } private long newPosition(final int index, final int currentTail, final long position, final long result) { long newPosition = ADMIN_ACTION; final int termOffset = TermAppender.termOffset(result); if (termOffset > 0) { newPosition = (position - currentTail) + termOffset; } else if (termOffset == TermAppender.TRIPPED) { final int nextIndex = nextPartitionIndex(index); termAppenders[nextIndex].tailTermId(TermAppender.termId(result) + 1); LogBufferDescriptor.activePartitionIndexOrdered(logMetaDataBuffer, nextIndex); } return newPosition; } private void checkForMaxPayloadLength(final int length) { if (length > maxPayloadLength) { throw new IllegalArgumentException( "Claim exceeds maxPayloadLength of " + maxPayloadLength + ", length=" + length); } } private void checkForMaxMessageLength(final int length) { if (length > maxMessageLength) { throw new IllegalArgumentException( "Message exceeds maxMessageLength of " + maxMessageLength + ", length=" + length); } } ManagedResource managedResource() { return new PublicationManagedResource(); } private class PublicationManagedResource implements ManagedResource { private long timeOfLastStateChange = 0; public void timeOfLastStateChange(final long time) { this.timeOfLastStateChange = time; } public long timeOfLastStateChange() { return timeOfLastStateChange; } public void delete() { logBuffers.close(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy