github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/aeron/subscription.go (about) 1 /* 2 Copyright 2016 Stanislav Liberman 3 Copyright (C) 2022 Talos, Inc. 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 "errors" 22 ctr "github.com/lirm/aeron-go/aeron/counters" 23 "strings" 24 25 "github.com/lirm/aeron-go/aeron/atomic" 26 "github.com/lirm/aeron-go/aeron/logbuffer/term" 27 ) 28 29 const ( 30 ChannelStatusNoIdAllocated = -1 // Channel status counter not allocated for IPC channels 31 ChannelStatusErrored = -1 // Channel has errored. Check logs for information 32 ChannelStatusInitializing = 0 // Channel is being initialized 33 ChannelStatusActive = 1 // Channel has finished initialization and is active 34 ChannelStatusClosing = 2 // Channel is being closed 35 ) 36 37 // ChannelStatusString provides a convenience method for logging and error handling 38 func ChannelStatusString(channelStatus int) string { 39 switch channelStatus { 40 case ChannelStatusErrored: 41 return "ChannelStatusErrored" 42 case ChannelStatusInitializing: 43 return "ChannelStatusInitializing" 44 case ChannelStatusActive: 45 return "ChannelStatusActive" 46 case ChannelStatusClosing: 47 return "ChannelStatusClosing" 48 default: 49 return "Unknown" 50 } 51 } 52 53 // From LocalSocketAddressStatus.Java 54 const ( 55 ChannelStatusIdOffset = 0 56 LocalSocketAddressLengthOffset = ChannelStatusIdOffset + 4 57 LocalSocketAddressStringOffset = LocalSocketAddressLengthOffset + 4 58 ) 59 const LocalSocketAddressStatusCounterTypeId = 14 60 61 type ReceivingConductor interface { 62 CounterReader() *ctr.Reader 63 releaseSubscription(regID int64, images []Image) error 64 AddRcvDestination(registrationID int64, endpointChannel string) error 65 RemoveRcvDestination(registrationID int64, endpointChannel string) error 66 } 67 68 // Subscription is the object responsible for receiving messages from media driver. It is specific to a channel and 69 // stream ID combination. 70 type Subscription struct { 71 conductor ReceivingConductor 72 channel string 73 roundRobinIndex int 74 registrationID int64 75 streamID int32 76 channelStatusID int32 77 images *ImageList 78 isClosed atomic.Bool 79 availableImageHandler AvailableImageHandler 80 unavailableImageHandler UnavailableImageHandler 81 } 82 83 // NewSubscription is a factory method to create new subscription to be added to the media driver 84 func NewSubscription( 85 conductor ReceivingConductor, 86 channel string, 87 registrationID int64, 88 streamID int32, 89 channelStatusID int32, 90 availableImagehandler AvailableImageHandler, 91 unavailableImageHandler UnavailableImageHandler) *Subscription { 92 sub := new(Subscription) 93 sub.images = NewImageList() 94 sub.conductor = conductor 95 sub.channel = channel 96 sub.registrationID = registrationID 97 sub.streamID = streamID 98 sub.channelStatusID = channelStatusID 99 sub.roundRobinIndex = 0 100 sub.isClosed.Set(false) 101 sub.availableImageHandler = availableImagehandler 102 sub.unavailableImageHandler = unavailableImageHandler 103 104 return sub 105 } 106 107 // Channel returns the media address for delivery to the channel. 108 func (sub *Subscription) Channel() string { 109 return sub.channel 110 } 111 112 // StreamID returns Stream identity for scoping within the channel media address. 113 func (sub *Subscription) StreamID() int32 { 114 return sub.streamID 115 } 116 117 // IsClosed returns whether this subscription has been closed. 118 func (sub *Subscription) IsClosed() bool { 119 return sub.isClosed.Get() 120 } 121 122 // ChannelStatus returns the status of the media channel for this Subscription. 123 // The status will be ChannelStatusErrored if a socket exception on setup or ChannelStatusActive if all is well. 124 func (sub *Subscription) ChannelStatus() int { 125 if sub.IsClosed() { 126 return ChannelStatusNoIdAllocated 127 } 128 if sub.channelStatusID == -1 { // IPC channels don't have a channel status counter 129 return ChannelStatusActive 130 } 131 return int(sub.conductor.CounterReader().GetCounterValue(sub.channelStatusID)) 132 } 133 134 // ChannelStatusId returns the counter ID used to represent the channel status of this Subscription. 135 func (sub *Subscription) ChannelStatusId() int32 { 136 return sub.channelStatusID 137 } 138 139 // Close will release all images in this subscription, send command to the driver and block waiting for response from 140 // the media driver. Images will be lingered by the ClientConductor. 141 func (sub *Subscription) Close() error { 142 if sub.isClosed.CompareAndSet(false, true) { 143 images := sub.images.Empty() 144 return sub.conductor.releaseSubscription(sub.registrationID, images) 145 } 146 147 return nil 148 } 149 150 // AvailableImageHandler returns a callback used to indicate when an Image becomes available under this Subscription. 151 // The handler may be nil. 152 func (sub *Subscription) AvailableImageHandler() AvailableImageHandler { 153 return sub.availableImageHandler 154 } 155 156 // UnavailableImageHandler returns a callback used to indicate when an Image goes unavailable under this Subscription. 157 // The handler may be nil. 158 func (sub *Subscription) UnavailableImageHandler() UnavailableImageHandler { 159 return sub.unavailableImageHandler 160 } 161 162 // Poll is the primary receive mechanism on subscription. 163 func (sub *Subscription) Poll(handler term.FragmentHandler, fragmentLimit int) int { 164 img := sub.images.Get() 165 length := len(img) 166 var fragmentsRead int 167 168 if length > 0 { 169 startingIndex := sub.roundRobinIndex 170 sub.roundRobinIndex++ 171 if startingIndex >= length { 172 sub.roundRobinIndex = 0 173 startingIndex = 0 174 } 175 176 for i := startingIndex; i < length && fragmentsRead < fragmentLimit; i++ { 177 var newFrags int 178 newFrags = img[i].Poll(handler, fragmentLimit-fragmentsRead) 179 fragmentsRead += newFrags 180 } 181 182 for i := 0; i < startingIndex && fragmentsRead < fragmentLimit; i++ { 183 var newFrags int 184 newFrags = img[i].Poll(handler, fragmentLimit-fragmentsRead) 185 fragmentsRead += newFrags 186 } 187 } 188 189 return fragmentsRead 190 } 191 192 // ControlledPoll polls in a controlled manner the image s under the subscription for available message fragments. 193 // Control is applied to fragments in the stream. If more fragments can be read on another stream 194 // they will even if BREAK or ABORT is returned from the fragment handler. 195 // 196 // Each fragment read will be a whole message if it is under MTU length. If larger than MTU then it will come 197 // as a series of fragments ordered within a session. 198 // Returns the number of fragments received. 199 func (sub *Subscription) ControlledPoll(handler term.ControlledFragmentHandler, fragmentLimit int) int { 200 201 img := sub.images.Get() 202 length := len(img) 203 var fragmentsRead int 204 205 if length > 0 { 206 startingIndex := sub.roundRobinIndex 207 sub.roundRobinIndex++ 208 if startingIndex >= length { 209 sub.roundRobinIndex = 0 210 startingIndex = 0 211 } 212 213 for i := startingIndex; i < length && fragmentsRead < fragmentLimit; i++ { 214 var newFrags int 215 newFrags = img[i].ControlledPoll(handler, fragmentLimit-fragmentsRead) 216 fragmentsRead += newFrags 217 } 218 219 for i := 0; i < startingIndex && fragmentsRead < fragmentLimit; i++ { 220 var newFrags int 221 newFrags = img[i].ControlledPoll(handler, fragmentLimit-fragmentsRead) 222 fragmentsRead += newFrags 223 } 224 } 225 226 return fragmentsRead 227 } 228 229 //go:norace 230 func (sub *Subscription) hasImage(sessionID int32) bool { 231 img := sub.images.Get() 232 for _, image := range img { 233 if image.SessionID() == sessionID { 234 return true 235 } 236 } 237 return false 238 } 239 240 //go:norace 241 func (sub *Subscription) addImage(image Image) *[]Image { 242 243 images := sub.images.Get() 244 245 sub.images.Set(append(images, image)) 246 247 return &images 248 } 249 250 //go:norace 251 func (sub *Subscription) removeImage(correlationID int64) Image { 252 253 img := sub.images.Get() 254 for ix, image := range img { 255 if image.CorrelationID() == correlationID { 256 logger.Debugf("Removing image %v for subscription %d", image, sub.registrationID) 257 258 img[ix] = img[len(img)-1] 259 img = img[:len(img)-1] 260 261 sub.images.Set(img) 262 263 return image 264 } 265 } 266 return nil 267 } 268 269 // RegistrationID returns the registration id. 270 func (sub *Subscription) RegistrationID() int64 { 271 return sub.registrationID 272 } 273 274 // IsConnected returns if this subscription is connected by having at least one open publication image. 275 func (sub *Subscription) IsConnected() bool { 276 for _, image := range sub.images.Get() { 277 if !image.IsClosed() { 278 return true 279 } 280 } 281 return false 282 } 283 284 // HasImages is a helper method checking whether this subscription has any images associated with it. 285 func (sub *Subscription) HasImages() bool { 286 images := sub.images.Get() 287 return len(images) > 0 288 } 289 290 // ImageCount count of images associated with this subscription. 291 func (sub *Subscription) ImageCount() int { 292 images := sub.images.Get() 293 return len(images) 294 } 295 296 // ImageBySessionID returns the associated with the given sessionId. 297 func (sub *Subscription) ImageBySessionID(sessionID int32) Image { 298 img := sub.images.Get() 299 for _, image := range img { 300 if image.SessionID() == sessionID { 301 return image 302 } 303 } 304 return nil 305 } 306 307 // ResolvedEndpoint finds the resolved endpoint for the channel. This 308 // may be nil if MDS is used and no destination is yet added. 309 // The result is simply the first in the list of addresses found if 310 // multiple addresses exist 311 func (sub *Subscription) ResolvedEndpoint() string { 312 reader := sub.conductor.CounterReader() 313 if sub.ChannelStatus() != ChannelStatusActive { 314 return "" 315 } 316 var endpoint string 317 reader.ScanForType(LocalSocketAddressStatusCounterTypeId, func(counterId int32, keyBuffer *atomic.Buffer) bool { 318 channelStatusId := keyBuffer.GetInt32(ChannelStatusIdOffset) 319 length := keyBuffer.GetInt32(LocalSocketAddressLengthOffset) 320 if channelStatusId == sub.channelStatusID && length > 0 && reader.GetCounterValue(counterId) == ChannelStatusActive { 321 endpoint = string(keyBuffer.GetBytesArray(LocalSocketAddressStringOffset, length)) 322 return false 323 } 324 return true 325 }) 326 return endpoint 327 } 328 329 // TryResolveChannelEndpointPort resolves the channel endpoint and replaces it with the port from the 330 // ephemeral range when 0 was provided. If there are no addresses, or if there is more than one, returned from 331 // LocalSocketAddresses() then the original channel is returned. 332 // If the channel is not ACTIVE, then empty string will be returned. 333 func (sub *Subscription) TryResolveChannelEndpointPort() string { 334 if sub.ChannelStatus() != ChannelStatusActive { 335 return "" 336 } 337 localSocketAddresses := sub.LocalSocketAddresses() 338 if len(localSocketAddresses) != 1 { 339 return sub.channel 340 } 341 uri, err := ParseChannelUri(sub.channel) 342 if err != nil { 343 logger.Warningf("error parsing channel (%s): %v", sub.channel, err) 344 return sub.channel 345 } 346 endpoint := uri.Get("endpoint") 347 if strings.HasSuffix(endpoint, ":0") { 348 resolvedEndpoint := localSocketAddresses[0] 349 i := strings.LastIndex(resolvedEndpoint, ":") 350 uri.Set("endpoint", endpoint[:(len(endpoint)-2)]+resolvedEndpoint[i:]) 351 return uri.String() 352 } 353 return sub.channel 354 } 355 356 // LocalSocketAddresses fetches the local socket addresses for this subscription. 357 func (sub *Subscription) LocalSocketAddresses() []string { 358 if sub.ChannelStatus() != ChannelStatusActive { 359 return nil 360 } 361 var bindings []string 362 reader := sub.conductor.CounterReader() 363 reader.ScanForType(LocalSocketAddressStatusCounterTypeId, func(counterId int32, keyBuffer *atomic.Buffer) bool { 364 channelStatusId := keyBuffer.GetInt32(ChannelStatusIdOffset) 365 length := keyBuffer.GetInt32(LocalSocketAddressLengthOffset) 366 if channelStatusId == sub.channelStatusID && length > 0 && reader.GetCounterValue(counterId) == ChannelStatusActive { 367 bindings = append(bindings, string(keyBuffer.GetBytesArray(LocalSocketAddressStringOffset, length))) 368 } 369 return true 370 }) 371 return bindings 372 } 373 374 // AddDestination adds a destination manually to a multi-destination Subscription. 375 func (sub *Subscription) AddDestination(endpointChannel string) error { 376 if sub.IsClosed() { 377 return errors.New("subscription is closed") 378 } 379 380 return sub.conductor.AddRcvDestination(sub.registrationID, endpointChannel) 381 } 382 383 // RemoveDestination removes a destination manually from a multi-destination Subscription. 384 func (sub *Subscription) RemoveDestination(endpointChannel string) error { 385 if sub.IsClosed() { 386 return errors.New("subscription is closed") 387 } 388 389 return sub.conductor.RemoveRcvDestination(sub.registrationID, endpointChannel) 390 } 391 392 // IsConnectedTo is a helper function used primarily by tests, which is used within the same process to verify that 393 // subscription is connected to a specific publication. 394 func IsConnectedTo(sub *Subscription, pub *Publication) bool { 395 img := sub.images.Get() 396 if sub.channel == pub.channel && sub.streamID == pub.streamID { 397 for _, image := range img { 398 if image.SessionID() == pub.sessionID { 399 return true 400 } 401 } 402 } 403 404 return false 405 }