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  }