/*
* 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.
*
* <b>Note:</b>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<Image> 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<Image> 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;
}
}