/*
* 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.driver;
import io.aeron.driver.buffer.RawLog;
import io.aeron.driver.status.SystemCounters;
import io.aeron.logbuffer.LogBufferDescriptor;
import io.aeron.logbuffer.LogBufferUnblocker;
import org.agrona.collections.ArrayUtil;
import org.agrona.concurrent.UnsafeBuffer;
import org.agrona.concurrent.status.AtomicCounter;
import org.agrona.concurrent.status.Position;
import org.agrona.concurrent.status.ReadablePosition;
import static io.aeron.driver.Configuration.PUBLICATION_LINGER_NS;
import static io.aeron.driver.status.SystemCounterDescriptor.UNBLOCKED_PUBLICATIONS;
import static io.aeron.logbuffer.LogBufferDescriptor.*;
/**
* Encapsulation of a LogBuffer used directly between publishers and subscribers for IPC.
*/
public class IpcPublication implements DriverManagedResource, Subscribable
{
enum Status
{
ACTIVE, INACTIVE, LINGER
}
private static final ReadablePosition[] EMPTY_POSITIONS = new ReadablePosition[0];
private final long registrationId;
private final long tripGain;
private final int sessionId;
private final int streamId;
private final int termWindowLength;
private final int positionBitsToShift;
private final int initialTermId;
private final long unblockTimeoutNs;
private long tripLimit = 0;
private long consumerPosition = 0;
private long lastConsumerPosition = 0;
private long timeOfLastConsumerPositionChange = 0;
private long cleanPosition = 0;
private long timeOfLastStatusChange = 0;
private int refCount = 0;
private boolean reachedEndOfLife = false;
private final boolean isExclusive;
private Status status = Status.ACTIVE;
private final UnsafeBuffer[] termBuffers;
private ReadablePosition[] subscriberPositions = EMPTY_POSITIONS;
private final RawLog rawLog;
private final Position publisherLimit;
private final AtomicCounter unblockedPublications;
public IpcPublication(
final long registrationId,
final int sessionId,
final int streamId,
final Position publisherLimit,
final RawLog rawLog,
final long unblockTimeoutNs,
final SystemCounters systemCounters,
final boolean isExclusive)
{
this.registrationId = registrationId;
this.sessionId = sessionId;
this.streamId = streamId;
this.isExclusive = isExclusive;
this.termBuffers = rawLog.termBuffers();
this.initialTermId = initialTermId(rawLog.metaData());
final int termLength = rawLog.termLength();
this.positionBitsToShift = Integer.numberOfTrailingZeros(termLength);
this.termWindowLength = Configuration.ipcPublicationTermWindowLength(termLength);
this.tripGain = termWindowLength / 8;
this.publisherLimit = publisherLimit;
this.rawLog = rawLog;
this.unblockTimeoutNs = unblockTimeoutNs;
this.unblockedPublications = systemCounters.get(UNBLOCKED_PUBLICATIONS);
consumerPosition = producerPosition();
}
public int sessionId()
{
return sessionId;
}
public int streamId()
{
return streamId;
}
public long registrationId()
{
return registrationId;
}
public boolean isExclusive()
{
return isExclusive;
}
public RawLog rawLog()
{
return rawLog;
}
public int publisherLimitId()
{
return publisherLimit.id();
}
public void close()
{
publisherLimit.close();
for (final ReadablePosition position : subscriberPositions)
{
position.close();
}
rawLog.close();
}
public void addSubscriber(final ReadablePosition subscriberPosition)
{
subscriberPositions = ArrayUtil.add(subscriberPositions, subscriberPosition);
}
public void removeSubscriber(final ReadablePosition subscriberPosition)
{
subscriberPositions = ArrayUtil.remove(subscriberPositions, subscriberPosition);
subscriberPosition.close();
}
int updatePublishersLimit()
{
int workCount = 0;
long minSubscriberPosition = Long.MAX_VALUE;
long maxSubscriberPosition = consumerPosition;
for (final ReadablePosition subscriberPosition : subscriberPositions)
{
final long position = subscriberPosition.getVolatile();
minSubscriberPosition = Math.min(minSubscriberPosition, position);
maxSubscriberPosition = Math.max(maxSubscriberPosition, position);
}
if (subscriberPositions.length == 0)
{
publisherLimit.setOrdered(maxSubscriberPosition);
tripLimit = maxSubscriberPosition;
}
else
{
final long proposedLimit = minSubscriberPosition + termWindowLength;
if (proposedLimit > tripLimit)
{
publisherLimit.setOrdered(proposedLimit);
tripLimit = proposedLimit + tripGain;
cleanBuffer(minSubscriberPosition);
workCount = 1;
}
consumerPosition = maxSubscriberPosition;
}
return workCount;
}
private void cleanBuffer(final long minConsumerPosition)
{
final long cleanPosition = this.cleanPosition;
final UnsafeBuffer dirtyTerm = termBuffers[indexByPosition(cleanPosition, positionBitsToShift)];
final int bytesForCleaning = (int)(minConsumerPosition - cleanPosition);
final int bufferCapacity = dirtyTerm.capacity();
final int termOffset = (int)cleanPosition & (bufferCapacity - 1);
final int length = Math.min(bytesForCleaning, bufferCapacity - termOffset);
if (length > 0)
{
dirtyTerm.setMemory(termOffset, length, (byte)0);
this.cleanPosition = cleanPosition + length;
}
}
public long joiningPosition()
{
return producerPosition();
}
public long producerPosition()
{
final long rawTail = rawTailVolatile(rawLog.metaData());
final int termOffset = termOffset(rawTail, rawLog.termLength());
return computePosition(termId(rawTail), termOffset, positionBitsToShift, initialTermId);
}
public void onTimeEvent(final long timeNs, final long timeMs, final DriverConductor conductor)
{
checkForBlockedPublisher(timeNs);
if (subscriberPositions.length > 0)
{
LogBufferDescriptor.timeOfLastStatusMessage(rawLog.metaData(), timeMs);
}
switch (status)
{
case INACTIVE:
if (isDrained())
{
status = Status.LINGER;
timeOfLastStatusChange = timeNs;
conductor.transitionToLinger(this);
}
break;
case LINGER:
if (timeNs > (timeOfLastStatusChange + PUBLICATION_LINGER_NS))
{
reachedEndOfLife = true;
conductor.cleanupIpcPublication(this);
}
break;
}
}
public boolean hasReachedEndOfLife()
{
return reachedEndOfLife;
}
public void timeOfLastStateChange(final long time)
{
timeOfLastStatusChange = time;
}
public long timeOfLastStateChange()
{
return timeOfLastStatusChange;
}
public void delete()
{
close();
}
public int incRef()
{
return ++refCount;
}
public int decRef()
{
final int count = --refCount;
if (0 == count)
{
status = Status.INACTIVE;
}
return count;
}
long consumerPosition()
{
return consumerPosition;
}
Status status()
{
return status;
}
private boolean isDrained()
{
final long producerPosition = producerPosition();
for (final ReadablePosition subscriberPosition : subscriberPositions)
{
if (subscriberPosition.getVolatile() < producerPosition)
{
return false;
}
}
return true;
}
private void checkForBlockedPublisher(final long timeNs)
{
if (consumerPosition == lastConsumerPosition)
{
if (producerPosition() > consumerPosition &&
timeNs > (timeOfLastConsumerPositionChange + unblockTimeoutNs))
{
if (LogBufferUnblocker.unblock(termBuffers, rawLog.metaData(), consumerPosition))
{
unblockedPublications.orderedIncrement();
}
}
}
else
{
timeOfLastConsumerPositionChange = timeNs;
lastConsumerPosition = consumerPosition;
}
}
}