github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/image_impl.go (about) 1 /* 2 Copyright 2016 Stanislav Liberman 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package aeron 18 19 import ( 20 "github.com/lirm/aeron-go/aeron/atomic" 21 "github.com/lirm/aeron-go/aeron/logbuffer" 22 "github.com/lirm/aeron-go/aeron/logbuffer/term" 23 "github.com/lirm/aeron-go/aeron/util" 24 ) 25 26 type image struct { 27 sourceIdentity string 28 logBuffers *logbuffer.LogBuffers 29 termBuffers [logbuffer.PartitionCount]*atomic.Buffer 30 subscriberPosition Position 31 header logbuffer.Header 32 isClosed atomic.Bool 33 isEos bool 34 35 termLengthMask int32 36 positionBitsToShift uint8 37 sessionID int32 38 joinPosition int64 39 finalPosition int64 40 subscriptionRegistrationID int64 41 correlationID int64 42 } 43 44 // NewImage wraps around provided LogBuffers setting up the structures for polling 45 func NewImage(sessionID int32, correlationID int64, logBuffers *logbuffer.LogBuffers) *image { 46 47 image := new(image) 48 49 image.correlationID = correlationID 50 image.sessionID = sessionID 51 image.logBuffers = logBuffers 52 for i := 0; i < logbuffer.PartitionCount; i++ { 53 image.termBuffers[i] = logBuffers.Buffer(i) 54 } 55 capacity := logBuffers.Buffer(0).Capacity() 56 image.termLengthMask = capacity - 1 57 image.positionBitsToShift = util.NumberOfTrailingZeroes(uint32(capacity)) 58 image.header.SetInitialTermID(logBuffers.Meta().InitTermID.Get()) 59 image.header.SetPositionBitsToShift(int32(image.positionBitsToShift)) 60 image.isClosed.Set(false) 61 62 return image 63 } 64 65 // IsClosed returns whether this image has been closed. No further operations are valid. 66 func (image *image) IsClosed() bool { 67 return image.isClosed.Get() 68 } 69 70 // Poll for new messages in a stream. If new messages are found beyond the 71 // last consumed position then they will be delivered to the FragmentHandler 72 // up to a limited number of fragments as specified. Use a FragmentAssembler 73 // to assemble messages which span multiple fragments. Returns the number of 74 // fragments that have been consumed successfully, as well as an error if a 75 // fragment had one. A fragment with an error will cause Poll to terminate 76 // early, even if fragmentLimit has not been hit. 77 // 78 //go:norace 79 func (image *image) Poll(handler term.FragmentHandler, fragmentLimit int) int { 80 if image.IsClosed() { 81 return 0 82 } 83 84 position := image.subscriberPosition.get() 85 termOffset := int32(position) & image.termLengthMask 86 index := indexByPosition(position, image.positionBitsToShift) 87 termBuffer := image.termBuffers[index] 88 89 offset, result := term.Read(termBuffer, termOffset, handler, fragmentLimit, &image.header) 90 91 newPosition := position + int64(offset-termOffset) 92 if newPosition > position { 93 image.subscriberPosition.set(newPosition) 94 } 95 return result 96 } 97 98 // BoundedPoll polls for new messages in a stream. If new messages are found 99 // beyond the last consumed position then they will be delivered to the 100 // FragmentHandler up to a limited number of fragments as specified or the 101 // maximum position specified. Use a FragmentAssembler to assemble messages 102 // which span multiple fragments. Returns the number of fragments that have been 103 // consumed successfully, as well as an error if a fragment had one. A fragment 104 // with an error will cause BoundedPoll to terminate early, even if neither 105 // limit has been hit. 106 func (image *image) BoundedPoll( 107 handler term.FragmentHandler, 108 limitPosition int64, 109 fragmentLimit int, 110 ) int { 111 if image.IsClosed() { 112 return 0 113 } 114 115 fragmentsRead := 0 116 initialPosition := image.subscriberPosition.get() 117 initialOffset := int32(initialPosition) & image.termLengthMask 118 offset := initialOffset 119 120 index := indexByPosition(initialPosition, image.positionBitsToShift) 121 termBuffer := image.termBuffers[index] 122 123 capacity := termBuffer.Capacity() 124 limitOffset := int32(limitPosition-initialPosition) + offset 125 if limitOffset > capacity { 126 limitOffset = capacity 127 } 128 header := &image.header 129 header.Wrap(termBuffer.Ptr(), termBuffer.Capacity()) 130 131 for fragmentsRead < fragmentLimit && offset < limitOffset { 132 length := logbuffer.GetFrameLength(termBuffer, offset) 133 if length <= 0 { 134 break 135 } 136 137 frameOffset := offset 138 alignedLength := util.AlignInt32(length, logbuffer.FrameAlignment) 139 offset += alignedLength 140 141 if logbuffer.IsPaddingFrame(termBuffer, frameOffset) { 142 continue 143 } 144 fragmentsRead++ 145 header.SetOffset(frameOffset) 146 147 handler(termBuffer, frameOffset+logbuffer.DataFrameHeader.Length, 148 length-logbuffer.DataFrameHeader.Length, header) 149 } 150 resultingPosition := initialPosition + int64(offset-initialOffset) 151 if resultingPosition > initialPosition { 152 image.subscriberPosition.set(resultingPosition) 153 } 154 return fragmentsRead 155 } 156 157 // ControlledPoll polls for new messages in a stream. If new messages are found 158 // beyond the last consumed position then they will be delivered to the 159 // ControlledFragmentHandler up to a limited number of fragments as 160 // specified. 161 // 162 // To assemble messages that span multiple fragments then use 163 // ControlledFragmentAssembler. Returns the number of fragments that have been 164 // consumed. 165 func (image *image) ControlledPoll( 166 handler term.ControlledFragmentHandler, 167 fragmentLimit int, 168 ) int { 169 if image.IsClosed() { 170 return 0 171 } 172 173 fragmentsRead := 0 174 initialPosition := image.subscriberPosition.get() 175 initialOffset := int32(initialPosition) & image.termLengthMask 176 offset := initialOffset 177 178 index := indexByPosition(initialPosition, image.positionBitsToShift) 179 termBuffer := image.termBuffers[index] 180 181 capacity := termBuffer.Capacity() 182 header := &image.header 183 header.Wrap(termBuffer.Ptr(), termBuffer.Capacity()) 184 185 for fragmentsRead < fragmentLimit && offset < capacity { 186 length := logbuffer.GetFrameLength(termBuffer, offset) 187 if length <= 0 { 188 break 189 } 190 191 frameOffset := offset 192 alignedLength := util.AlignInt32(length, logbuffer.FrameAlignment) 193 offset += alignedLength 194 195 if logbuffer.IsPaddingFrame(termBuffer, frameOffset) { 196 continue 197 } 198 fragmentsRead++ 199 header.SetOffset(frameOffset) 200 201 action := handler(termBuffer, frameOffset+logbuffer.DataFrameHeader.Length, 202 length-logbuffer.DataFrameHeader.Length, header) 203 if action == term.ControlledPollActionAbort { 204 fragmentsRead-- 205 offset -= alignedLength 206 break 207 } 208 if action == term.ControlledPollActionBreak { 209 break 210 } 211 if action == term.ControlledPollActionCommit { 212 initialPosition += int64(offset - initialOffset) 213 initialOffset = offset 214 image.subscriberPosition.set(initialPosition) 215 } 216 } 217 resultingPosition := initialPosition + int64(offset-initialOffset) 218 if resultingPosition > initialPosition { 219 image.subscriberPosition.set(resultingPosition) 220 } 221 return fragmentsRead 222 } 223 224 // Position returns the position this image has been consumed to by the subscriber. 225 func (image *image) Position() int64 { 226 if image.IsClosed() { 227 return image.finalPosition 228 } 229 return image.subscriberPosition.get() 230 } 231 232 // IsEndOfStream returns if the current consumed position at the end of the stream? 233 func (image *image) IsEndOfStream() bool { 234 if image.IsClosed() { 235 return image.isEos 236 } 237 return image.subscriberPosition.get() >= image.logBuffers.Meta().EndOfStreamPosOff.Get() 238 } 239 240 // SessionID returns the sessionId for the steam of messages. 241 func (image *image) SessionID() int32 { 242 return image.sessionID 243 } 244 245 // CorrelationID returns the correlationId for identification of the image with the media driver. 246 func (image *image) CorrelationID() int64 { 247 return image.correlationID 248 } 249 250 // SubscriptionRegistrationID returns the registrationId for the Subscription of the image. 251 func (image *image) SubscriptionRegistrationID() int64 { 252 return image.subscriptionRegistrationID 253 } 254 255 // TermBufferLength returns the length in bytes for each term partition in the log buffer. 256 func (image *image) TermBufferLength() int32 { 257 return image.termLengthMask + 1 258 } 259 260 // ActiveTransportCount returns the number of observed active 261 // transports within the image liveness timeout. 262 // 263 // Returns 0 if the image is closed, if no datagrams have arrived or the image is IPC 264 func (image *image) ActiveTransportCount() int32 { 265 return image.logBuffers.Meta().ActiveTransportCount() 266 } 267 268 // Close the image and mappings. The image becomes unusable after closing. 269 func (image *image) Close() error { 270 var err error 271 if image.isClosed.CompareAndSet(false, true) { 272 image.finalPosition = image.subscriberPosition.get() 273 image.isEos = image.finalPosition >= 274 image.logBuffers.Meta().EndOfStreamPosOff.Get() 275 logger.Debugf("Closing %v", image) 276 err = image.logBuffers.Close() 277 } 278 return err 279 } 280 281 func indexByPosition(position int64, positionBitsToShift uint8) int32 { 282 term := uint64(position) >> positionBitsToShift 283 return util.FastMod3(term) 284 }