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  }