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 }