github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/publication.go (about)

     1  /*
     2  Copyright 2016-2018 Stanislav Liberman
     3  Copyright 2022 Steven Stern
     4  
     5  Licensed under the Apache License, Version 2.0 (the "License");
     6  you may not use this file except in compliance with the License.
     7  You may obtain a copy of the License at
     8  
     9  http://www.apache.org/licenses/LICENSE-2.0
    10  
    11  Unless required by applicable law or agreed to in writing, software
    12  distributed under the License is distributed on an "AS IS" BASIS,
    13  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  See the License for the specific language governing permissions and
    15  limitations under the License.
    16  */
    17  
    18  package aeron
    19  
    20  import (
    21  	"fmt"
    22  
    23  	"github.com/lirm/aeron-go/aeron/atomic"
    24  	"github.com/lirm/aeron-go/aeron/logbuffer"
    25  	"github.com/lirm/aeron-go/aeron/logbuffer/term"
    26  	"github.com/lirm/aeron-go/aeron/logging"
    27  	"github.com/lirm/aeron-go/aeron/util"
    28  )
    29  
    30  const (
    31  	// NotConnected indicates that this Publication is not connected to the driver
    32  	NotConnected int64 = -1
    33  	// BackPressured indicates that sending ring buffer is full
    34  	BackPressured int64 = -2
    35  	// AdminAction indicates that terms needs to be rotated. User should retry the Offer
    36  	AdminAction int64 = -3
    37  	// PublicationClosed indicates that this Publication is closed and no further Offers shall succeed
    38  	PublicationClosed int64 = -4
    39  	// MaxPositionExceeded indicates that ...
    40  	MaxPositionExceeded int64 = -5
    41  )
    42  
    43  // Publication is a sender structure
    44  type Publication struct {
    45  	conductor                *ClientConductor
    46  	channel                  string
    47  	regID                    int64
    48  	originalRegID            int64
    49  	maxPossiblePosition      int64
    50  	streamID                 int32
    51  	sessionID                int32
    52  	initialTermID            int32
    53  	maxPayloadLength         int32
    54  	maxMessageLength         int32
    55  	positionBitsToShift      int32
    56  	pubLimit                 Position
    57  	channelStatusIndicatorID int32
    58  
    59  	isClosed atomic.Bool
    60  	metaData *logbuffer.LogBufferMetaData
    61  
    62  	appenders [logbuffer.PartitionCount]*term.Appender
    63  }
    64  
    65  // NewPublication is a factory method create new publications
    66  func NewPublication(logBuffers *logbuffer.LogBuffers) *Publication {
    67  	termBufferCapacity := logBuffers.Buffer(0).Capacity()
    68  
    69  	pub := new(Publication)
    70  	pub.metaData = logBuffers.Meta()
    71  	pub.initialTermID = pub.metaData.InitTermID.Get()
    72  	pub.maxPayloadLength = pub.metaData.MTULen.Get() - logbuffer.DataFrameHeader.Length
    73  	pub.maxMessageLength = logbuffer.ComputeMaxMessageLength(termBufferCapacity)
    74  	pub.positionBitsToShift = int32(util.NumberOfTrailingZeroes(uint32(termBufferCapacity)))
    75  	pub.maxPossiblePosition = int64(termBufferCapacity) * (1 << 31)
    76  
    77  	pub.isClosed.Set(false)
    78  
    79  	for i := 0; i < logbuffer.PartitionCount; i++ {
    80  		appender := term.MakeAppender(logBuffers, i)
    81  		logger.Debugf("TermAppender[%d]: %v", i, appender)
    82  		pub.appenders[i] = appender
    83  	}
    84  
    85  	return pub
    86  }
    87  
    88  // ChannelStatusID returns the counter used to represent the channel status
    89  // for this publication.
    90  func (pub *Publication) ChannelStatusID() int32 {
    91  	return pub.channelStatusIndicatorID
    92  }
    93  
    94  // RegistrationID returns the registration id.
    95  func (pub *Publication) RegistrationID() int64 {
    96  	return pub.regID
    97  }
    98  
    99  // OriginalRegistrationID returns the original registration id.
   100  func (pub *Publication) OriginalRegistrationID() int64 {
   101  	return pub.originalRegID
   102  }
   103  
   104  // Channel returns the media address for delivery to the channel.
   105  func (pub *Publication) Channel() string {
   106  	return pub.channel
   107  }
   108  
   109  // StreamID returns Stream identity for scoping within the channel media address.
   110  func (pub *Publication) StreamID() int32 {
   111  	return pub.streamID
   112  }
   113  
   114  // SessionID returns the session id for this publication.
   115  func (pub *Publication) SessionID() int32 {
   116  	return pub.sessionID
   117  }
   118  
   119  // InitialTermID returns the initial term id assigned when this publication was
   120  // created. This can be used to determine how many terms have passed since
   121  // creation.
   122  func (pub *Publication) InitialTermID() int32 {
   123  	return pub.initialTermID
   124  }
   125  
   126  // IsConnected returns whether this publication is connected to the driver (not whether it has any Subscriptions)
   127  func (pub *Publication) IsConnected() bool {
   128  	return !pub.IsClosed() && pub.metaData.IsConnected.Get() == 1
   129  }
   130  
   131  // IsClosed returns whether this Publication has been closed
   132  func (pub *Publication) IsClosed() bool {
   133  	return pub.isClosed.Get()
   134  }
   135  
   136  // IsOriginal return true if this instance is the first added otherwise false.
   137  func (pub *Publication) IsOriginal() bool {
   138  	return pub.originalRegID == pub.regID
   139  }
   140  
   141  // Close will close this publication with the driver. This is a blocking call.
   142  func (pub *Publication) Close() error {
   143  	// FIXME Why can pub be nil?!
   144  	if pub != nil && pub.isClosed.CompareAndSet(false, true) {
   145  		return pub.conductor.releasePublication(pub.regID)
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  // Position returns the current position to which the publication has advanced
   152  // for this stream or PublicationClosed if closed.
   153  func (pub *Publication) Position() int64 {
   154  	if pub.IsClosed() {
   155  		return PublicationClosed
   156  	}
   157  
   158  	// Spelled out for clarity, the compiler will optimize
   159  	termCount := pub.metaData.ActiveTermCountOff.Get()
   160  	termIndex := termCount % logbuffer.PartitionCount
   161  	termAppender := pub.appenders[termIndex]
   162  	rawTail := termAppender.RawTail()
   163  	termOffset := rawTail & 0xFFFFFFFF
   164  	termId := logbuffer.TermID(rawTail)
   165  
   166  	return computeTermBeginPosition(termId, pub.positionBitsToShift, pub.initialTermID) + termOffset
   167  }
   168  
   169  // Offer is the primary send mechanism on Publication
   170  func (pub *Publication) Offer(buffer *atomic.Buffer, offset int32, length int32, reservedValueSupplier term.ReservedValueSupplier) int64 {
   171  	if pub.IsClosed() {
   172  		return PublicationClosed
   173  	}
   174  
   175  	if reservedValueSupplier == nil {
   176  		reservedValueSupplier = term.DefaultReservedValueSupplier
   177  	}
   178  
   179  	limit := pub.pubLimit.get()
   180  	termCount := pub.metaData.ActiveTermCountOff.Get()
   181  	termIndex := termCount % logbuffer.PartitionCount
   182  	termAppender := pub.appenders[termIndex]
   183  	rawTail := termAppender.RawTail()
   184  	termOffset := rawTail & 0xFFFFFFFF
   185  	termId := logbuffer.TermID(rawTail)
   186  	position := computeTermBeginPosition(termId, pub.positionBitsToShift, pub.initialTermID) + termOffset
   187  
   188  	if termCount != (termId - pub.metaData.InitTermID.Get()) {
   189  		return AdminAction
   190  	}
   191  
   192  	if logger.IsEnabledFor(logging.DEBUG) {
   193  		logger.Debugf("Offering at %d of %d (pubLmt: %v)", position, limit, pub.pubLimit)
   194  	}
   195  	if position >= limit {
   196  		return pub.backPressureStatus(position, length)
   197  	}
   198  	var resultingOffset int64
   199  	if length <= pub.maxPayloadLength {
   200  		resultingOffset, termId = termAppender.AppendUnfragmentedMessage(buffer, offset, length, reservedValueSupplier)
   201  	} else {
   202  		pub.checkForMaxMessageLength(length)
   203  		resultingOffset, termId = termAppender.AppendFragmentedMessage(buffer, offset, length, pub.maxPayloadLength, reservedValueSupplier)
   204  	}
   205  
   206  	return pub.newPosition(termCount, termOffset, termId, position, resultingOffset)
   207  }
   208  
   209  // Offer2 attempts to publish a message composed of two parts, e.g. a header and encapsulated payload.
   210  func (pub *Publication) Offer2(
   211  	bufferOne *atomic.Buffer, offsetOne int32, lengthOne int32,
   212  	bufferTwo *atomic.Buffer, offsetTwo int32, lengthTwo int32,
   213  	reservedValueSupplier term.ReservedValueSupplier,
   214  ) int64 {
   215  	if lengthOne < 0 {
   216  		logger.Debugf("Offered negative length (lengthOne: %d)", lengthOne)
   217  		return 0
   218  	} else if lengthTwo < 0 {
   219  		logger.Debugf("Offered negative length (lengthTwo: %d)", lengthTwo)
   220  		return 0
   221  	}
   222  	length := lengthOne + lengthTwo
   223  	if length < 0 {
   224  		panic(fmt.Sprintf("Length overflow (lengthOne: %d lengthTwo: %d)", lengthOne, lengthTwo))
   225  	}
   226  	if pub.IsClosed() {
   227  		return PublicationClosed
   228  	}
   229  
   230  	if reservedValueSupplier == nil {
   231  		reservedValueSupplier = term.DefaultReservedValueSupplier
   232  	}
   233  
   234  	limit := pub.pubLimit.get()
   235  	termCount := pub.metaData.ActiveTermCountOff.Get()
   236  	termIndex := termCount % logbuffer.PartitionCount
   237  	termAppender := pub.appenders[termIndex]
   238  	rawTail := termAppender.RawTail()
   239  	termOffset := rawTail & 0xFFFFFFFF
   240  	termId := logbuffer.TermID(rawTail)
   241  	position := computeTermBeginPosition(termId, pub.positionBitsToShift, pub.initialTermID) + termOffset
   242  
   243  	if termCount != (termId - pub.metaData.InitTermID.Get()) {
   244  		return AdminAction
   245  	}
   246  
   247  	if logger.IsEnabledFor(logging.DEBUG) {
   248  		logger.Debugf("Offering at %d of %d (pubLmt: %v)", position, limit, pub.pubLimit)
   249  	}
   250  	if position >= limit {
   251  		return pub.backPressureStatus(position, length)
   252  	}
   253  
   254  	var resultingOffset int64
   255  	if length <= pub.maxPayloadLength {
   256  		resultingOffset, termId = termAppender.AppendUnfragmentedMessage2(
   257  			bufferOne, offsetOne, lengthOne,
   258  			bufferTwo, offsetTwo, lengthTwo,
   259  			reservedValueSupplier)
   260  	} else {
   261  		pub.checkForMaxMessageLength(length)
   262  		resultingOffset, termId = termAppender.AppendFragmentedMessage2(
   263  			bufferOne, offsetOne, lengthOne,
   264  			bufferTwo, offsetTwo, lengthTwo,
   265  			pub.maxPayloadLength, reservedValueSupplier)
   266  	}
   267  	return pub.newPosition(termCount, termOffset, termId, position, resultingOffset)
   268  }
   269  
   270  func (pub *Publication) newPosition(termCount int32, termOffset int64, termId int32, position int64, resultingOffset int64) int64 {
   271  	if resultingOffset > 0 {
   272  		return (position - termOffset) + resultingOffset
   273  	}
   274  
   275  	if (position + termOffset) > pub.maxPossiblePosition {
   276  		return MaxPositionExceeded
   277  	}
   278  
   279  	logbuffer.RotateLog(pub.metaData, termCount, termId)
   280  	return AdminAction
   281  }
   282  
   283  func (pub *Publication) backPressureStatus(currentPosition int64, messageLength int32) int64 {
   284  
   285  	if (currentPosition + int64(messageLength)) >= pub.maxPossiblePosition {
   286  		return MaxPositionExceeded
   287  	}
   288  
   289  	if pub.metaData.IsConnected.Get() == 1 {
   290  		return BackPressured
   291  	}
   292  
   293  	return NotConnected
   294  }
   295  
   296  func (pub *Publication) TryClaim(length int32, bufferClaim *logbuffer.Claim) int64 {
   297  	if pub.IsClosed() {
   298  		return PublicationClosed
   299  	}
   300  	pub.checkForMaxPayloadLength(length)
   301  
   302  	limit := pub.pubLimit.get()
   303  	termCount := pub.metaData.ActiveTermCountOff.Get()
   304  	termIndex := termCount % logbuffer.PartitionCount
   305  	termAppender := pub.appenders[termIndex]
   306  	rawTail := termAppender.RawTail()
   307  	termOffset := rawTail & 0xFFFFFFFF
   308  	position := computeTermBeginPosition(logbuffer.TermID(rawTail), pub.positionBitsToShift, pub.initialTermID) + termOffset
   309  
   310  	if position < limit {
   311  		resultingOffset, termId := termAppender.Claim(length, bufferClaim)
   312  
   313  		return pub.newPosition(termCount, termOffset, termId, position, resultingOffset)
   314  	} else {
   315  		return pub.backPressureStatus(position, length)
   316  	}
   317  }
   318  
   319  func (pub *Publication) checkForMaxMessageLength(length int32) {
   320  	if length > pub.maxMessageLength {
   321  		panic(fmt.Sprintf("Encoded message exceeds maxMessageLength of %d, length=%d", pub.maxMessageLength, length))
   322  	}
   323  }
   324  
   325  func (pub *Publication) checkForMaxPayloadLength(length int32) {
   326  	if length > pub.maxPayloadLength {
   327  		panic(fmt.Sprintf("Encoded message exceeds maxPayloadLength of %d, length=%d", pub.maxPayloadLength, length))
   328  	}
   329  }
   330  
   331  func computeTermBeginPosition(activeTermID, positionBitsToShift, initialTermID int32) int64 {
   332  	termCount := int64(activeTermID - initialTermID)
   333  
   334  	return termCount << uint32(positionBitsToShift)
   335  }
   336  
   337  func nextPartitionIndex(currentIndex int32) int32 {
   338  	return util.FastMod3(uint64(currentIndex) + 1)
   339  }