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

io.aeron.Subscription Maven / Gradle / Ivy

There is a newer version: 1.46.7
Show newest version
/*
 * 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.collections.ArrayUtil;

import java.util.*;
import java.util.function.Consumer;

class SubscriptionLhsPadding
{
    @SuppressWarnings("unused")
    protected long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15;
}

class SubscriptionFields extends SubscriptionLhsPadding
{
    protected static final Image[] EMPTY_ARRAY = new Image[0];

    protected final long registrationId;
    protected int roundRobinIndex = 0;
    protected final int streamId;
    protected volatile boolean isClosed = false;

    protected volatile Image[] images = EMPTY_ARRAY;
    protected final ClientConductor clientConductor;
    protected final String channel;
    protected final AvailableImageHandler availableImageHandler;
    protected final UnavailableImageHandler unavailableImageHandler;

    protected SubscriptionFields(
        final long registrationId,
        final int streamId,
        final ClientConductor clientConductor,
        final String channel,
        final AvailableImageHandler availableImageHandler,
        final UnavailableImageHandler unavailableImageHandler)
    {
        this.registrationId = registrationId;
        this.streamId = streamId;
        this.clientConductor = clientConductor;
        this.channel = channel;
        this.availableImageHandler = availableImageHandler;
        this.unavailableImageHandler = unavailableImageHandler;
    }
}

/**
 * Aeron Subscriber API for receiving a reconstructed {@link Image} for a stream of messages from publishers on
 * a given channel and streamId pair. {@link Image}s are aggregated under a {@link Subscription}.
 *
 * {@link Subscription}s are created via an {@link Aeron} object, and received messages are delivered
 * to the {@link FragmentHandler}.
 *
 * By default fragmented messages are not reassembled before delivery. If an application must
 * receive whole messages, whether or not they were fragmented, then the Subscriber
 * should be created with a {@link FragmentAssembler} or a custom implementation.
 *
 * It is an application's responsibility to {@link #poll} the {@link Subscription} for new messages.
 *
 * Note:Subscriptions are not threadsafe and should not be shared between subscribers.
 *
 * @see FragmentAssembler
 * @see ControlledFragmentHandler
 * @see Aeron#addSubscription(String, int)
 * @see Aeron#addSubscription(String, int, AvailableImageHandler, UnavailableImageHandler)
 */
public class Subscription extends SubscriptionFields implements AutoCloseable
{
    @SuppressWarnings("unused")
    protected long p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30;

    Subscription(
        final ClientConductor conductor,
        final String channel,
        final int streamId,
        final long registrationId,
        final AvailableImageHandler availableImageHandler,
        final UnavailableImageHandler unavailableImageHandler)
    {
        super(registrationId, streamId, conductor, channel, availableImageHandler, unavailableImageHandler);
    }

    /**
     * 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;
    }

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

    /**
     * Callback used to indicate when an {@link Image} becomes available under this {@link Subscription}.
     *
     * @return callback used to indicate when an {@link Image} becomes available under this {@link Subscription}.
     */
    public AvailableImageHandler availableImageHandler()
    {
        return availableImageHandler;
    }

    /**
     * Callback used to indicate when an {@link Image} goes unavailable under this {@link Subscription}.
     *
     * @return Callback used to indicate when an {@link Image} goes unavailable under this {@link Subscription}.
     */
    public UnavailableImageHandler unavailableImageHandler()
    {
        return unavailableImageHandler;
    }

    /**
     * Poll the {@link Image}s under the subscription for available message fragments.
     *
     * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come
     * as a series of fragments ordered within a session.
     *
     * To assemble messages that span multiple fragments then use {@link FragmentAssembler}.
     *
     * @param fragmentHandler callback for handling each message fragment as it is read.
     * @param fragmentLimit   number of message fragments to limit for the poll operation across multiple {@link Image}s.
     * @return the number of fragments received
     */
    public int poll(final FragmentHandler fragmentHandler, final int fragmentLimit)
    {
        final Image[] images = this.images;
        final int length = images.length;
        int fragmentsRead = 0;

        int startingIndex = roundRobinIndex++;
        if (startingIndex >= length)
        {
            roundRobinIndex = startingIndex = 0;
        }

        for (int i = startingIndex; i < length && fragmentsRead < fragmentLimit; i++)
        {
            fragmentsRead += images[i].poll(fragmentHandler, fragmentLimit - fragmentsRead);
        }

        for (int i = 0; i < startingIndex && fragmentsRead < fragmentLimit; i++)
        {
            fragmentsRead += images[i].poll(fragmentHandler, fragmentLimit - fragmentsRead);
        }

        return fragmentsRead;
    }

    /**
     * Poll in a controlled manner the {@link Image}s under the subscription for available message fragments.
     * Control is applied to fragments in the stream. If more fragments can be read on another stream
     * they will even if BREAK or ABORT is returned from the fragment handler.
     *
     * Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come
     * as a series of fragments ordered within a session.
     *
     * To assemble messages that span multiple fragments then use {@link ControlledFragmentAssembler}.
     *
     * @param fragmentHandler callback for handling each message fragment as it is read.
     * @param fragmentLimit   number of message fragments to limit for the poll operation across multiple {@link Image}s.
     * @return the number of fragments received
     * @see ControlledFragmentHandler
     */
    public int controlledPoll(final ControlledFragmentHandler fragmentHandler, final int fragmentLimit)
    {
        final Image[] images = this.images;
        final int length = images.length;
        int fragmentsRead = 0;

        int startingIndex = roundRobinIndex++;
        if (startingIndex >= length)
        {
            roundRobinIndex = startingIndex = 0;
        }

        for (int i = startingIndex; i < length && fragmentsRead < fragmentLimit; i++)
        {
            fragmentsRead += images[i].controlledPoll(fragmentHandler, fragmentLimit - fragmentsRead);
        }

        for (int i = 0; i < startingIndex && fragmentsRead < fragmentLimit; i++)
        {
            fragmentsRead += images[i].controlledPoll(fragmentHandler, fragmentLimit - fragmentsRead);
        }

        return fragmentsRead;
    }

    /**
     * Poll the {@link Image}s under the subscription for available message fragments in blocks.
     *
     * This method is useful for operations like bulk archiving and messaging indexing.
     *
     * @param blockHandler     to receive a block of fragments from each {@link Image}.
     * @param blockLengthLimit for each {@link Image} polled.
     * @return the number of bytes consumed.
     */
    public long blockPoll(final BlockHandler blockHandler, final int blockLengthLimit)
    {
        long bytesConsumed = 0;
        for (final Image image : images)
        {
            bytesConsumed += image.blockPoll(blockHandler, blockLengthLimit);
        }

        return bytesConsumed;
    }

    /**
     * Poll the {@link Image}s under the subscription for available message fragments in blocks.
     *
     * This method is useful for operations like bulk archiving a stream to file.
     *
     * @param rawBlockHandler to receive a block of fragments from each {@link Image}.
     * @param blockLengthLimit for each {@link Image} polled.
     * @return the number of bytes consumed.
     */
    public long rawPoll(final RawBlockHandler rawBlockHandler, final int blockLengthLimit)
    {
        long bytesConsumed = 0;
        for (final Image image : images)
        {
            bytesConsumed += image.rawPoll(rawBlockHandler, blockLengthLimit);
        }

        return bytesConsumed;
    }

    /**
     * Has the subscription currently no images connected to it?
     *
     * @return he subscription currently no images connected to it?
     */
    public boolean hasNoImages()
    {
        return images.length == 0;
    }

    /**
     * Count of images connected to this subscription.
     *
     * @return count of images connected to this subscription.
     */
    public int imageCount()
    {
        return images.length;
    }

    /**
     * Return the {@link Image} associated with the given sessionId.
     *
     * @param sessionId associated with the Image.
     * @return Image associated with the given sessionId or null if no Image exist.
     */
    public Image imageBySessionId(final int sessionId)
    {
        Image result = null;

        for (final Image image : images)
        {
            if (sessionId == image.sessionId())
            {
                result = image;
                break;
            }
        }

        return result;
    }

    /**
     * Get a {@link List} of active {@link Image}s that match this subscription.
     *
     * @return an unmodifiable {@link List} of active {@link Image}s that match this subscription.
     */
    public List images()
    {
        return Collections.unmodifiableList(Arrays.asList(images));
    }

    /**
     * Iterate over the {@link Image}s for this subscription.
     *
     * @param imageConsumer to handle each {@link Image}.
     */
    public void forEachImage(final Consumer imageConsumer)
    {
        for (final Image image : images)
        {
            imageConsumer.accept(image);
        }
    }

    /**
     * Get the image at the given index from the images array.
     *
     * @param index in the array
     * @return image at given index
     */
    public Image getImage(final int index)
    {
        return images[index];
    }

    /**
     * Close the Subscription so that associated {@link Image}s can be released.
     *
     * This method is idempotent.
     */
    public void close()
    {
        clientConductor.clientLock().lock();
        try
        {
            if (!isClosed)
            {
                isClosed = true;

                closeImages();

                clientConductor.releaseSubscription(this);
            }
        }
        finally
        {
            clientConductor.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;
    }

    void forceClose()
    {
        isClosed = true;

        closeImages();

        clientConductor.asyncReleaseSubscription(this);
    }

    void addImage(final Image image)
    {
        if (isClosed)
        {
            clientConductor.lingerResource(image.managedResource());
        }
        else
        {
            images = ArrayUtil.add(images, image);
        }
    }

    Image removeImage(final long correlationId)
    {
        final Image[] oldArray = images;
        Image removedImage = null;

        for (final Image image : oldArray)
        {
            if (image.correlationId() == correlationId)
            {
                removedImage = image;
                break;
            }
        }

        if (null != removedImage)
        {
            images = ArrayUtil.remove(oldArray, removedImage);
            clientConductor.lingerResource(removedImage.managedResource());
        }

        return removedImage;
    }

    boolean hasImage(final long correlationId)
    {
        boolean hasImage = false;

        for (final Image image : images)
        {
            if (correlationId == image.correlationId())
            {
                hasImage = true;
                break;
            }
        }

        return hasImage;
    }

    private void closeImages()
    {
        for (final Image image : images)
        {
            clientConductor.lingerResource(image.managedResource());

            try
            {
                if (null != unavailableImageHandler)
                {
                    unavailableImageHandler.onUnavailableImage(image);
                }
            }
            catch (final Throwable ex)
            {
                clientConductor.handleError(ex);
            }
        }

        this.images = EMPTY_ARRAY;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy