github.com/lirm/aeron-go@v0.0.0-20230415210743-920325491dc4/archive/archive.go (about)

     1  // Copyright (C) 2021-2022 Talos, Inc.
     2  // Copyright (C) 2014-2021 Real Logic Limited.
     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  // Package archive provides API access to Aeron's archive-media-driver
    17  package archive
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/lirm/aeron-go/aeron"
    26  	"github.com/lirm/aeron-go/aeron/atomic"
    27  	"github.com/lirm/aeron-go/aeron/logbuffer"
    28  	"github.com/lirm/aeron-go/aeron/logging"
    29  	"github.com/lirm/aeron-go/archive/codecs"
    30  )
    31  
    32  // Archive is the primary interface to the media driver for managing archiving
    33  type Archive struct {
    34  	aeron        *aeron.Aeron            // Embedded aeron
    35  	aeronContext *aeron.Context          // Embedded aeron context, see context.go for available wrapper functions
    36  	Options      *Options                // Configuration options
    37  	SessionID    int64                   // Allocated by the archiving media driver
    38  	Proxy        *Proxy                  // For outgoing protocol messages (publish/request)
    39  	Control      *Control                // For incoming protocol messages (subscribe/reponse)
    40  	Events       *RecordingEventsAdapter // For async recording events (must be enabled)
    41  	Listeners    *ArchiveListeners       // Per client event listeners for async callbacks
    42  	mtx          sync.Mutex              // To ensure no overlapped I/O on archive RPC calls
    43  }
    44  
    45  // Constant values used to control behaviour of StartReplay
    46  const (
    47  	RecordingPositionNull = int64(-1)        // Replay a stream from the start.
    48  	RecordingLengthNull   = int64(-1)        // Replay will follow a live recording
    49  	RecordingLengthMax    = int64(2<<31 - 1) // Replay the whole stream
    50  )
    51  
    52  // replication flag used for duplication instead of extension, see Replicate and variants
    53  const (
    54  	RecordingIdNullValue = int32(-1)
    55  )
    56  
    57  // Listeners may be set to get callbacks on various operations.  This
    58  // is a global as internally the aeron library provides no context
    59  // within the the FragmentAssemblers without any user data (or other
    60  // context). Listeners.ErrorListener() if set will be called if for
    61  // example protocol unmarshalling goes wrong.
    62  
    63  // ArchiveListeners contains all the callbacks
    64  // By default only the ErrorListener is set to a logging listener.  If
    65  // "archive" is at loglevel DEBUG then logging listeners are set for
    66  // all listeners.
    67  //
    68  // The signal listener will be called in normal operation if set.
    69  //
    70  // The image listeners will be be called in normal operation if set.
    71  //
    72  // The RecordingEvent listeners require RecordingEventEnable() to be called
    73  // as well as having the RecordingEvent Poll() called by user code.
    74  type ArchiveListeners struct {
    75  	// Called on errors for things like uncorrelated control messages
    76  	ErrorListener func(error)
    77  
    78  	// Async protocol events if enabled
    79  	RecordingEventStartedListener  func(*codecs.RecordingStarted)
    80  	RecordingEventProgressListener func(*codecs.RecordingProgress)
    81  	RecordingEventStoppedListener  func(*codecs.RecordingStopped)
    82  
    83  	// Async protocol event
    84  	RecordingSignalListener func(*codecs.RecordingSignalEvent)
    85  
    86  	// Async events from the underlying Aeron instance
    87  	NewSubscriptionListener  func(string, int32, int64)
    88  	NewPublicationListener   func(string, int32, int32, int64)
    89  	AvailableImageListener   func(aeron.Image)
    90  	UnavailableImageListener func(aeron.Image)
    91  }
    92  
    93  // LoggingErrorListener is set by default and will report internal failures when
    94  // returning an error is not possible
    95  func LoggingErrorListener(err error) {
    96  	logger.Errorf("Error: %s", err.Error())
    97  }
    98  
    99  // LoggingRecordingSignalListener (called by default only in DEBUG)
   100  func LoggingRecordingSignalListener(rse *codecs.RecordingSignalEvent) {
   101  	logger.Infof("RecordingSignalListener, signal event is %#v", rse)
   102  }
   103  
   104  // LoggingRecordingEventStartedListener (called by default only in DEBUG)
   105  func LoggingRecordingEventStartedListener(rs *codecs.RecordingStarted) {
   106  	logger.Infof("RecordingEventStartedListener: %#v", rs)
   107  }
   108  
   109  // LoggingRecordingEventProgressListener (called by default only in DEBUG)
   110  func LoggingRecordingEventProgressListener(rp *codecs.RecordingProgress) {
   111  	logger.Infof("RecordingEventProgressListener, event is %#v", rp)
   112  }
   113  
   114  // LoggingRecordingEventStoppedListener (called by default only in DEBUG)
   115  func LoggingRecordingEventStoppedListener(rs *codecs.RecordingStopped) {
   116  	logger.Infof("RecordingEventStoppedListener, event is %#v", rs)
   117  }
   118  
   119  // LoggingNewSubscriptionListener from underlying aeron (called by default only in DEBUG)
   120  func LoggingNewSubscriptionListener(channel string, stream int32, correlationID int64) {
   121  	logger.Infof("NewSubscriptionListener(channel:%s stream:%d correlationID:%d)", channel, stream, correlationID)
   122  }
   123  
   124  // LoggingNewPublicationListener from underlying aeron (called by default only in DEBUG)
   125  func LoggingNewPublicationListener(channel string, stream int32, session int32, regID int64) {
   126  	logger.Infof("NewPublicationListener(channel:%s stream:%d, session:%d, regID:%d)", channel, stream, session, regID)
   127  }
   128  
   129  // LoggingAvailableImageListener from underlying aeron (called by default only in DEBUG)
   130  func LoggingAvailableImageListener(image aeron.Image) {
   131  	logger.Infof("NewAvailableImageListener, sessionId is %d", image.SessionID())
   132  }
   133  
   134  // LoggingUnavailableImageListener from underlying aeron (called by default only in DEBUG)
   135  func LoggingUnavailableImageListener(image aeron.Image) {
   136  	logger.Infof("NewUnavalableImageListener, sessionId is %d", image.SessionID())
   137  }
   138  
   139  // Also set globally (and set via the Options) is the protocol
   140  // marshalling checks. When unmarshalling we lack context so we need a
   141  // global copy of the options values which we set before calling
   142  // Poll() to ensure it's current
   143  //
   144  // Use the Options structure to set this
   145  var rangeChecking bool
   146  
   147  // Logging handler
   148  var logger = logging.MustGetLogger("archive")
   149  
   150  // Map correlation IDs to Control structures for the fragment
   151  // assemblers.  A common usage case would be a goroutine per archive
   152  // instance and for this case a sync.Map should be a little more
   153  // efficient.
   154  var correlations sync.Map // [int64]*Control
   155  
   156  // For creating unique correlationIDs via nextCorrelationID()
   157  var _correlationID atomic.Long
   158  
   159  // Inititialization
   160  func init() {
   161  	_correlationID.Set(time.Now().UnixNano())
   162  }
   163  
   164  // Utility to create a new correlation Id
   165  func nextCorrelationID() int64 {
   166  	return _correlationID.Inc()
   167  }
   168  
   169  // ReplaySessionIdToStreamId utility function to convert a ReplaySessionID into a streamID
   170  func ReplaySessionIdToStreamId(replaySessionID int64) int32 {
   171  	// It's actually just the least significant 32 bits
   172  	return int32(replaySessionID)
   173  }
   174  
   175  // AddSessionIdToChannel utility function to add a session to a channel URI
   176  // On failure it will return the original and an error
   177  func AddSessionIdToChannel(channel string, sessionID int32) (string, error) {
   178  	uri, err := aeron.ParseChannelUri(channel)
   179  	if err != nil {
   180  		return channel, err
   181  	}
   182  	uri.Set("session-id", fmt.Sprint(sessionID))
   183  	return uri.String(), nil
   184  }
   185  
   186  // NewArchive factory method to create an Archive instance
   187  // You may provide your own archive Options or otherwise one will be created from defaults
   188  // You may provide your own aeron Context or otherwise one will be created from defaults
   189  func NewArchive(options *Options, context *aeron.Context) (*Archive, error) {
   190  	var err error
   191  
   192  	archive := new(Archive)
   193  	archive.aeron = new(aeron.Aeron)
   194  	archive.aeronContext = context
   195  
   196  	defer func() {
   197  		if err != nil {
   198  			archive.Close()
   199  		}
   200  	}()
   201  
   202  	// Use the provided options or use our defaults
   203  	if options != nil {
   204  		archive.Options = options
   205  	} else {
   206  		if archive.Options == nil {
   207  			// Create a new set
   208  			archive.Options = DefaultOptions()
   209  		}
   210  	}
   211  
   212  	// Set the logging levels
   213  	logging.SetLevel(archive.Options.ArchiveLoglevel, "archive")
   214  	logging.SetLevel(archive.Options.AeronLoglevel, "aeron")
   215  	logging.SetLevel(archive.Options.AeronLoglevel, "memmap")
   216  	logging.SetLevel(archive.Options.AeronLoglevel, "driver")
   217  	logging.SetLevel(archive.Options.AeronLoglevel, "counters")
   218  	logging.SetLevel(archive.Options.AeronLoglevel, "logbuffers")
   219  	logging.SetLevel(archive.Options.AeronLoglevel, "buffer")
   220  	logging.SetLevel(archive.Options.AeronLoglevel, "rb")
   221  
   222  	// Setup the Control (subscriber/response)
   223  	archive.Control = new(Control)
   224  	archive.Control.archive = archive
   225  	archive.Control.fragmentAssembler = aeron.NewControlledFragmentAssembler(
   226  		archive.Control.onFragment, aeron.DefaultFragmentAssemblyBufferLength)
   227  
   228  	// Setup the Proxy (publisher/request)
   229  	archive.Proxy = new(Proxy)
   230  	archive.Proxy.archive = archive
   231  	archive.Proxy.marshaller = codecs.NewSbeGoMarshaller()
   232  
   233  	// Setup Recording Events (although it's not enabled by default)
   234  	archive.Events = new(RecordingEventsAdapter)
   235  	archive.Events.archive = archive
   236  
   237  	// Create the listeners and populate
   238  	archive.Listeners = new(ArchiveListeners)
   239  	archive.Listeners.ErrorListener = LoggingErrorListener
   240  
   241  	// In Debug mode initialize our listeners with simple loggers
   242  	// Note that these actually log at INFO so you can do this manually for INFO if you like
   243  	if logging.GetLevel("archive") >= logging.DEBUG {
   244  		logger.Debugf("Setting logging listeners")
   245  
   246  		archive.Listeners.RecordingEventStartedListener = LoggingRecordingEventStartedListener
   247  		archive.Listeners.RecordingEventProgressListener = LoggingRecordingEventProgressListener
   248  		archive.Listeners.RecordingEventStoppedListener = LoggingRecordingEventStoppedListener
   249  
   250  		archive.Listeners.RecordingSignalListener = LoggingRecordingSignalListener
   251  
   252  		archive.Listeners.AvailableImageListener = LoggingAvailableImageListener
   253  		archive.Listeners.UnavailableImageListener = LoggingUnavailableImageListener
   254  
   255  		archive.Listeners.NewSubscriptionListener = LoggingNewSubscriptionListener
   256  		archive.Listeners.NewPublicationListener = LoggingNewPublicationListener
   257  
   258  		archive.aeronContext.NewSubscriptionHandler(archive.Listeners.NewSubscriptionListener)
   259  		archive.aeronContext.NewPublicationHandler(archive.Listeners.NewPublicationListener)
   260  	}
   261  
   262  	// Connect the underlying aeron
   263  	archive.aeron, err = aeron.Connect(archive.aeronContext)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	// and then the subscription, it's poller and initiate a connection
   269  	sub, err := archive.aeron.AddSubscription(archive.Options.ResponseChannel, archive.Options.ResponseStream)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	archive.Control.Subscription = sub
   274  	logger.Debugf("Control response subscription: %#v", archive.Control.Subscription)
   275  
   276  	start := time.Now()
   277  	responseChannel := archive.Control.Subscription.TryResolveChannelEndpointPort()
   278  	for responseChannel == "" {
   279  		archive.Options.IdleStrategy.Idle(0)
   280  		responseChannel = archive.Control.Subscription.TryResolveChannelEndpointPort()
   281  		if time.Since(start) > archive.Options.Timeout {
   282  			err = fmt.Errorf("Resolving channel endpoint for %s failed", archive.Control.Subscription.Channel())
   283  			logger.Errorf(err.Error())
   284  			return nil, err
   285  		}
   286  	}
   287  
   288  	// Create the publication half for the proxy that looks after sending requests on that
   289  	pub, err := archive.aeron.AddExclusivePublication(archive.Options.RequestChannel, archive.Options.RequestStream)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	archive.Proxy.Publication = pub
   294  	logger.Debugf("Proxy request publication: %#v", archive.Proxy.Publication)
   295  
   296  	// And intitiate the connection
   297  	archive.Control.State.state = ControlStateConnectRequestSent
   298  	correlationID := nextCorrelationID()
   299  	logger.Debugf("NewArchive correlationID is %d", correlationID)
   300  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   301  	defer correlations.Delete(correlationID)           // Clear the lookup
   302  
   303  	// Use Auth if requested
   304  	if archive.Options.AuthEnabled {
   305  		if err = archive.Proxy.AuthConnectRequest(correlationID, archive.Options.ResponseStream, responseChannel, archive.Options.AuthCredentials); err != nil {
   306  			logger.Errorf("AuthConnectRequest failed: %s", err)
   307  			return nil, err
   308  		}
   309  	} else {
   310  		if err = archive.Proxy.ConnectRequest(correlationID, archive.Options.ResponseStream, responseChannel); err != nil {
   311  			logger.Errorf("ConnectRequest failed: %s", err)
   312  			return nil, err
   313  		}
   314  	}
   315  
   316  	start = time.Now()
   317  	pollContext := PollContext{archive.Control, correlationID}
   318  
   319  	for archive.Control.State.state != ControlStateConnected && archive.Control.State.err == nil {
   320  		fragments := archive.Control.poll(
   321  			func(buf *atomic.Buffer, offset int32, length int32, header *logbuffer.Header) {
   322  				ConnectionControlFragmentHandler(&pollContext, buf, offset, length, header)
   323  			}, 1)
   324  		if fragments > 0 {
   325  			logger.Debugf("Read %d fragment(s)", fragments)
   326  		}
   327  
   328  		// Check for timeout
   329  		if time.Since(start) > archive.Options.Timeout {
   330  			archive.Control.State.state = ControlStateTimedOut
   331  			archive.Control.State.err = fmt.Errorf("operation timed out")
   332  			break
   333  		} else {
   334  			archive.Options.IdleStrategy.Idle(0)
   335  		}
   336  	}
   337  
   338  	if err = archive.Control.State.err; err != nil {
   339  		logger.Errorf("Connect failed: %s", err)
   340  		return nil, err
   341  	} else if archive.Control.State.state != ControlStateConnected {
   342  		logger.Error("Connect failed")
   343  	} else {
   344  		logger.Infof("Archive connection established for sessionId:%d", archive.SessionID)
   345  	}
   346  
   347  	return archive, archive.Control.State.err
   348  }
   349  
   350  // Close will terminate client conductor and remove all publications and subscriptions from the media driver
   351  func (archive *Archive) Close() error {
   352  	archive.mtx.Lock()
   353  	defer archive.mtx.Unlock()
   354  
   355  	if archive.Proxy.Publication != nil {
   356  		archive.Proxy.CloseSessionRequest()
   357  		archive.Proxy.Publication.Close()
   358  	}
   359  
   360  	if archive.Control.Subscription != nil {
   361  		archive.Control.Subscription.Close()
   362  	}
   363  
   364  	if archive.aeron != nil {
   365  		return archive.aeron.Close()
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  // Aeron returns the archive's aeron client.
   372  func (archive *Archive) Aeron() *aeron.Aeron {
   373  	return archive.aeron
   374  }
   375  
   376  // SetAeronDir sets the root directory for media driver files
   377  func (archive *Archive) SetAeronDir(dir string) {
   378  	archive.aeronContext.AeronDir(dir)
   379  }
   380  
   381  // SetAeronMediaDriverTimeout sets the timeout for keep alives to media driver
   382  func (archive *Archive) SetAeronMediaDriverTimeout(timeout time.Duration) {
   383  	archive.aeronContext.MediaDriverTimeout(timeout)
   384  }
   385  
   386  // SetAeronResourceLingerTimeout sets the timeout for resource cleanup after they're released
   387  func (archive *Archive) SetAeronResourceLingerTimeout(timeout time.Duration) {
   388  	archive.aeronContext.ResourceLingerTimeout(timeout)
   389  }
   390  
   391  // SetAeronInterServiceTimeout sets the timeout for client heartbeat
   392  func (archive *Archive) SetAeronInterServiceTimeout(timeout time.Duration) {
   393  	archive.aeronContext.InterServiceTimeout(timeout)
   394  }
   395  
   396  // SetAeronPublicationConnectionTimeout sets the timeout for publications
   397  func (archive *Archive) SetAeronPublicationConnectionTimeout(timeout time.Duration) {
   398  	archive.aeronContext.PublicationConnectionTimeout(timeout)
   399  }
   400  
   401  // AeronCncFileName returns the name of the Counters file
   402  func (archive *Archive) AeronCncFileName() string {
   403  	return archive.aeronContext.CncFileName()
   404  }
   405  
   406  // EnableRecordingEvents starts recording events flowing
   407  // Events are returned via the three callbacks which should be
   408  // overridden from the default logging listeners defined in the Listeners
   409  func (archive *Archive) EnableRecordingEvents() error {
   410  	sub, err := archive.aeron.AddSubscription(archive.Options.RecordingEventsChannel, archive.Options.RecordingEventsStream)
   411  	if err != nil {
   412  		return err
   413  	}
   414  	archive.Events.Subscription = sub
   415  	archive.Events.Enabled = true
   416  	logger.Debugf("RecordingEvents subscription: %#v", archive.Events.Subscription)
   417  	return nil
   418  }
   419  
   420  // IsRecordingEventsConnected returns true if the recording events subscription
   421  // is connected.
   422  func (archive *Archive) IsRecordingEventsConnected() (bool, error) {
   423  	if !archive.Events.Enabled {
   424  		return false, errors.New("recording events not enabled")
   425  	}
   426  	return archive.Events.Subscription.IsConnected(), nil
   427  }
   428  
   429  // DisableRecordingEvents stops recording events flowing
   430  func (archive *Archive) DisableRecordingEvents() {
   431  	archive.Events.Subscription.Close()
   432  	archive.Events.Enabled = false
   433  	logger.Debugf("RecordingEvents subscription closed")
   434  }
   435  
   436  // RecordingEventsPoll is used to poll for recording events
   437  func (archive *Archive) RecordingEventsPoll() int {
   438  	archive.mtx.Lock()
   439  	defer archive.mtx.Unlock()
   440  	return archive.Events.PollWithContext(nil, 1)
   441  }
   442  
   443  // PollForErrorResponse polls the response stream for an error draining the queue.
   444  //
   445  // This may be used to check for errors, to dispatch async events, and
   446  // to catch up on messages not for this session if for example the
   447  // same channel and stream are in use by other sessions.
   448  //
   449  // Returns an error if we detect an archive operation in progress
   450  // and a count of how many messages were consumed
   451  func (archive *Archive) PollForErrorResponse() (int, error) {
   452  	archive.mtx.Lock()
   453  	defer archive.mtx.Unlock()
   454  	return archive.Control.PollForErrorResponse()
   455  }
   456  
   457  // AddSubscription will add a new subscription to the driver.
   458  //
   459  // Returns a channel, which can be used for either blocking or non-blocking wait for media driver confirmation
   460  func (archive *Archive) AddSubscription(channel string, streamID int32) (*aeron.Subscription, error) {
   461  	return archive.aeron.AddSubscription(channel, streamID)
   462  }
   463  
   464  // AddPublication will add a new publication to the driver.
   465  //
   466  // If such a publication already exists within ClientConductor the same instance
   467  // will be returned.
   468  //
   469  // Returns a channel, which can be used for either blocking or
   470  // non-blocking want for media driver confirmation
   471  func (archive *Archive) AddPublication(channel string, streamID int32) (*aeron.Publication, error) {
   472  	return archive.aeron.AddPublication(channel, streamID)
   473  }
   474  
   475  // AddExclusivePublication will add a new exclusive publication to the driver.
   476  //
   477  // If such a publication already exists within ClientConductor the
   478  // same instance will be returned.
   479  //
   480  // Returns a channel, which can be used for either blocking or
   481  // non-blocking want for media driver confirmation
   482  func (archive *Archive) AddExclusivePublication(channel string, streamID int32) (*aeron.Publication, error) {
   483  	return archive.aeron.AddExclusivePublication(channel, streamID)
   484  }
   485  
   486  // ClientId returns the client identity that has been allocated for communicating with the media driver.
   487  func (archive *Archive) ClientId() int64 {
   488  	return archive.aeron.ClientID()
   489  }
   490  
   491  // StartRecording a channel/stream
   492  //
   493  // Channels that include sessionId parameters are considered different
   494  // than channels without sessionIds. If a publication matches both a
   495  // sessionId specific channel recording and a non-sessionId specific
   496  // recording, it will be recorded twice.
   497  //
   498  // Returns (subscriptionId, nil) or (0, error) on failure.  The
   499  // SubscriptionId can be used in StopRecordingBySubscription()
   500  func (archive *Archive) StartRecording(channel string, stream int32, isLocal bool, autoStop bool) (int64, error) {
   501  	correlationID := nextCorrelationID()
   502  	logger.Debugf("StartRecording(%s:%d), correlationID:%d", channel, stream, correlationID)
   503  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   504  	defer correlations.Delete(correlationID)           // Clear the lookup
   505  
   506  	archive.mtx.Lock()
   507  	defer archive.mtx.Unlock()
   508  	if err := archive.Proxy.StartRecordingRequest(correlationID, stream, isLocal, autoStop, channel); err != nil {
   509  		return 0, err
   510  	}
   511  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   512  }
   513  
   514  // StopRecording can be performed by RecordingID, by SubscriptionId,
   515  // by Publication, or by a channel/stream pairing (default)
   516  
   517  // StopRecording by Channel and Stream
   518  //
   519  // Channels that include sessionId parameters are considered different than channels without sessionIds. Stopping
   520  // recording on a channel without a sessionId parameter will not stop the recording of any sessionId specific
   521  // recordings that use the same channel and streamID.
   522  func (archive *Archive) StopRecording(channel string, stream int32) error {
   523  	correlationID := nextCorrelationID()
   524  	logger.Debugf("StopRecording(%s:%d, correlationID:%d)", channel, stream, correlationID)
   525  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   526  	defer correlations.Delete(correlationID)           // Clear the lookup
   527  
   528  	archive.mtx.Lock()
   529  	defer archive.mtx.Unlock()
   530  	if err := archive.Proxy.StopRecordingRequest(correlationID, stream, channel); err != nil {
   531  		return err
   532  	}
   533  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
   534  	return err
   535  }
   536  
   537  // StopRecordingByIdentity for the RecordingIdentity looked up in ListRecording*()
   538  //
   539  // Returns True if the recording was stopped or false if the recording is not currently active
   540  // and (false, error) if something went wrong
   541  func (archive *Archive) StopRecordingByIdentity(recordingID int64) (bool, error) {
   542  	correlationID := nextCorrelationID()
   543  	logger.Debugf("StopRecordingByIdentity(%d), correlationID:%d", recordingID, correlationID)
   544  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   545  	defer correlations.Delete(correlationID)           // Clear the lookup
   546  
   547  	archive.mtx.Lock()
   548  	defer archive.mtx.Unlock()
   549  	if err := archive.Proxy.StopRecordingByIdentityRequest(correlationID, recordingID); err != nil {
   550  		return false, err
   551  	}
   552  	res, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
   553  	if res < 0 {
   554  		logger.Debugf("StopRecordingByIdentity result was %d", res)
   555  	}
   556  
   557  	if err != nil {
   558  		return false, err
   559  	}
   560  	return res >= 0, err
   561  }
   562  
   563  // StopRecordingBySubscriptionId as returned by StartRecording
   564  //
   565  // Channels that include sessionId parameters are considered different than channels without sessionIds. Stopping
   566  // recording on a channel without a sessionId parameter will not stop the recording of any sessionId specific
   567  // recordings that use the same channel and streamID.
   568  //
   569  // Returns error on failure, nil on success
   570  func (archive *Archive) StopRecordingBySubscriptionId(subscriptionID int64) error {
   571  	correlationID := nextCorrelationID()
   572  	logger.Debugf("StopRecordingBySubscriptionId(%d), correlationID:%d", subscriptionID, correlationID)
   573  
   574  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   575  	defer correlations.Delete(correlationID)           // Clear the lookup
   576  
   577  	archive.mtx.Lock()
   578  	defer archive.mtx.Unlock()
   579  	if err := archive.Proxy.StopRecordingSubscriptionRequest(correlationID, subscriptionID); err != nil {
   580  		return err
   581  	}
   582  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
   583  	return err
   584  }
   585  
   586  // StopRecordingByPublication to stop recording a sessionId specific
   587  // recording that pertains to the given Publication
   588  //
   589  // Returns error on failure, nil on success
   590  func (archive *Archive) StopRecordingByPublication(publication aeron.Publication) error {
   591  	channel, err := AddSessionIdToChannel(publication.Channel(), publication.SessionID())
   592  	if err != nil {
   593  		return err
   594  	}
   595  	return archive.StopRecording(channel, publication.StreamID())
   596  }
   597  
   598  // AddRecordedPublication to set it up forrecording.
   599  //
   600  // This creates a per-session recording which can fail if:
   601  // Sending the request fails - see error for detail
   602  // Publication.IsOriginal() is false // FIXME: check semantics
   603  func (archive *Archive) AddRecordedPublication(channel string, stream int32) (*aeron.Publication, error) {
   604  
   605  	// This can fail in aeron via log.Fatalf(), not much we can do
   606  	publication, err := archive.AddPublication(channel, stream)
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  	if !publication.IsOriginal() {
   611  		return nil, fmt.Errorf("publication already added for channel=%s stream=%d", channel, stream)
   612  	}
   613  
   614  	correlationID := nextCorrelationID()
   615  	logger.Debugf("AddRecordedPublication(), correlationID:%d", correlationID)
   616  
   617  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   618  	defer correlations.Delete(correlationID)           // Clear the lookup
   619  
   620  	sessionChannel, err := AddSessionIdToChannel(publication.Channel(), publication.SessionID())
   621  	if err != nil {
   622  		publication.Close()
   623  		return nil, err
   624  	}
   625  
   626  	archive.mtx.Lock()
   627  	defer archive.mtx.Unlock()
   628  	if err := archive.Proxy.StartRecordingRequest(correlationID, stream, true, false, sessionChannel); err != nil {
   629  		publication.Close()
   630  		return nil, err
   631  	}
   632  
   633  	if _, err := archive.Control.PollForResponse(correlationID, archive.SessionID); err != nil {
   634  		publication.Close()
   635  		return nil, err
   636  	}
   637  
   638  	return publication, nil
   639  }
   640  
   641  // ListRecordings up to recordCount recording descriptors
   642  func (archive *Archive) ListRecordings(fromRecordingID int64, recordCount int32) ([]*codecs.RecordingDescriptor, error) {
   643  	correlationID := nextCorrelationID()
   644  	logger.Debugf("ListRecordings(%d, %d), correlationID:%d", fromRecordingID, recordCount, correlationID)
   645  
   646  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   647  	defer correlations.Delete(correlationID)           // Clear the lookup
   648  
   649  	archive.mtx.Lock()
   650  	defer archive.mtx.Unlock()
   651  	if err := archive.Proxy.ListRecordingsRequest(correlationID, fromRecordingID, recordCount); err != nil {
   652  		return nil, err
   653  	}
   654  	if err := archive.Control.PollForDescriptors(correlationID, archive.SessionID, recordCount); err != nil {
   655  		return nil, err
   656  	}
   657  
   658  	// If there's a ControlResponse let's see what transpired
   659  	response := archive.Control.Results.ControlResponse
   660  	if response != nil {
   661  		switch response.Code {
   662  		case codecs.ControlResponseCode.ERROR:
   663  			return nil, fmt.Errorf("response for correlationID %d (relevantId %d) failed %s", response.CorrelationId, response.RelevantId, response.ErrorMessage)
   664  
   665  		case codecs.ControlResponseCode.RECORDING_UNKNOWN:
   666  			return archive.Control.Results.RecordingDescriptors, nil
   667  		}
   668  	}
   669  
   670  	// Otherwise we can return our results
   671  	return archive.Control.Results.RecordingDescriptors, nil
   672  }
   673  
   674  // ListRecordingsForUri will list up to recordCount recording descriptors from fromRecordingID
   675  // with a limit of recordCount for a given channel and stream.
   676  //
   677  // Returns the number of descriptors consumed. If fromRecordingID is
   678  // greater than the largest known we return 0.
   679  func (archive *Archive) ListRecordingsForUri(fromRecordingID int64, recordCount int32, channelFragment string, stream int32) ([]*codecs.RecordingDescriptor, error) {
   680  	correlationID := nextCorrelationID()
   681  	logger.Debugf("ListRecordingsForUri(%d, %d, %s, %d), correlationID:%d", fromRecordingID, recordCount, channelFragment, stream, correlationID)
   682  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   683  	defer correlations.Delete(correlationID)           // Clear the lookup
   684  
   685  	archive.mtx.Lock()
   686  	defer archive.mtx.Unlock()
   687  	if err := archive.Proxy.ListRecordingsForUriRequest(correlationID, fromRecordingID, recordCount, stream, channelFragment); err != nil {
   688  		return nil, err
   689  	}
   690  
   691  	if err := archive.Control.PollForDescriptors(correlationID, archive.SessionID, recordCount); err != nil {
   692  		return nil, err
   693  	}
   694  
   695  	// If there's a ControlResponse let's see what transpired
   696  	response := archive.Control.Results.ControlResponse
   697  	if response != nil {
   698  		switch response.Code {
   699  		case codecs.ControlResponseCode.ERROR:
   700  			return nil, fmt.Errorf("response for correlationID %d (relevantId %d) failed %s", response.CorrelationId, response.RelevantId, response.ErrorMessage)
   701  
   702  		case codecs.ControlResponseCode.RECORDING_UNKNOWN:
   703  			return archive.Control.Results.RecordingDescriptors, nil
   704  		}
   705  	}
   706  
   707  	// Otherwise we can return our results
   708  	return archive.Control.Results.RecordingDescriptors, nil
   709  }
   710  
   711  // ListRecording will fetch the recording descriptor for a recordingID
   712  //
   713  // Returns a single recording descriptor or nil if there was no match
   714  func (archive *Archive) ListRecording(recordingID int64) (*codecs.RecordingDescriptor, error) {
   715  	correlationID := nextCorrelationID()
   716  	logger.Debugf("ListRecording(%d), correlationID:%d", recordingID, correlationID)
   717  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   718  	defer correlations.Delete(correlationID)           // Clear the lookup
   719  
   720  	archive.mtx.Lock()
   721  	defer archive.mtx.Unlock()
   722  	if err := archive.Proxy.ListRecordingRequest(correlationID, recordingID); err != nil {
   723  		return nil, err
   724  	}
   725  	if err := archive.Control.PollForDescriptors(correlationID, archive.SessionID, 1); err != nil {
   726  		return nil, err
   727  	}
   728  
   729  	// If there's a ControlResponse let's see what transpired
   730  	response := archive.Control.Results.ControlResponse
   731  	if response != nil {
   732  		switch response.Code {
   733  		case codecs.ControlResponseCode.ERROR:
   734  			return nil, fmt.Errorf("response for correlationID %d (relevantId %d) failed %s", response.CorrelationId, response.RelevantId, response.ErrorMessage)
   735  
   736  		case codecs.ControlResponseCode.RECORDING_UNKNOWN:
   737  			return nil, nil
   738  		}
   739  	}
   740  
   741  	// Otherwise we can return our results
   742  	return archive.Control.Results.RecordingDescriptors[0], nil
   743  }
   744  
   745  // StartReplay for a length in bytes of a recording from a position.
   746  //
   747  // If the position is RecordingPositionNull (-1) then the stream will
   748  // be replayed from the start.
   749  //
   750  // If the length is RecordingLengthMax (2^31-1) the replay will follow
   751  // a live recording.
   752  //
   753  // If the length is RecordingLengthNull (-1) the replay will
   754  // replay the whole stream of unknown length.
   755  //
   756  // The lower 32-bits of the returned value contains the ImageSessionID() of the received replay. All
   757  // 64-bits are required to uniquely identify the replay when calling StopReplay(). The lower 32-bits
   758  // can be obtained by casting the int64 value to an int32. See ReplaySessionIdToStreamId() helper.
   759  //
   760  // Returns a ReplaySessionID - the id of the replay session which will be the same as the Image sessionId
   761  // of the received replay for correlation with the matching channel and stream id in the lower 32 bits
   762  func (archive *Archive) StartReplay(recordingID int64, position int64, length int64, replayChannel string, replayStream int32) (int64, error) {
   763  
   764  	correlationID := nextCorrelationID()
   765  	// logger.Debugf("StartReplay(%d, %d, %d, %s, %d), correlationID:%d", recordingID, position, length, replayChannel, replayStream, correlationID)
   766  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   767  	defer correlations.Delete(correlationID)           // Clear the lookup
   768  
   769  	archive.mtx.Lock()
   770  	defer archive.mtx.Unlock()
   771  	if err := archive.Proxy.ReplayRequest(correlationID, recordingID, position, length, replayChannel, replayStream); err != nil {
   772  		return 0, err
   773  	}
   774  
   775  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   776  }
   777  
   778  // BoundedReplay to start a replay for a length in bytes of a
   779  // recording from a position bounded by a position counter.
   780  //
   781  // If the position is RecordingPositionNull (-1) then the stream will
   782  // be replayed from the start.
   783  //
   784  // If the length is RecordingLengthMax (2^31-1) the replay will follow
   785  // a live recording.
   786  //
   787  // If the length is RecordingLengthNull (-1) the replay will
   788  // replay the whole stream of unknown length.
   789  //
   790  // The lower 32-bits of the returned value contains the ImageSessionID() of the received replay. All
   791  // 64-bits are required to uniquely identify the replay when calling StopReplay(). The lower 32-bits
   792  // can be obtained by casting the int64 value to an int32. See ReplaySessionIdToStreamId() helper.
   793  //
   794  // Returns a ReplaySessionID - the id of the replay session which will be the same as the Image sessionId
   795  // of the received replay for correlation with the matching channel and stream id in the lower 32 bits
   796  func (archive *Archive) BoundedReplay(recordingID int64, position int64, length int64, limitCounterID int32, replayStream int32, replayChannel string) (int64, error) {
   797  	correlationID := nextCorrelationID()
   798  	logger.Debugf("BoundedReplay(%d, %d, %d, %d, %d, %s), correlationID:%d", recordingID, position, length, limitCounterID, replayStream, correlationID)
   799  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   800  	defer correlations.Delete(correlationID)           // Clear the lookup
   801  
   802  	archive.mtx.Lock()
   803  	defer archive.mtx.Unlock()
   804  	if err := archive.Proxy.BoundedReplayRequest(correlationID, recordingID, position, length, limitCounterID, replayStream, replayChannel); err != nil {
   805  		return 0, err
   806  	}
   807  
   808  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   809  }
   810  
   811  // StopReplay for a  session.
   812  //
   813  // Returns error on failure, nil on success
   814  func (archive *Archive) StopReplay(replaySessionID int64) error {
   815  	correlationID := nextCorrelationID()
   816  	logger.Debugf("StopReplay(%d), correlationID:%d", replaySessionID, correlationID)
   817  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   818  	defer correlations.Delete(correlationID)           // Clear the lookup
   819  
   820  	archive.mtx.Lock()
   821  	defer archive.mtx.Unlock()
   822  	if err := archive.Proxy.StopReplayRequest(correlationID, replaySessionID); err != nil {
   823  		return err
   824  	}
   825  
   826  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
   827  	return err
   828  }
   829  
   830  // StopAllReplays for a given recordingID
   831  //
   832  // Returns error on failure, nil on success
   833  func (archive *Archive) StopAllReplays(recordingID int64) error {
   834  	correlationID := nextCorrelationID()
   835  	logger.Debugf("StopAllReplays(%d), correlationID:%d", recordingID, correlationID)
   836  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   837  	defer correlations.Delete(correlationID)           // Clear the lookup
   838  
   839  	archive.mtx.Lock()
   840  	defer archive.mtx.Unlock()
   841  	if err := archive.Proxy.StopAllReplaysRequest(correlationID, recordingID); err != nil {
   842  		return err
   843  	}
   844  
   845  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
   846  	return err
   847  }
   848  
   849  // ExtendRecording to extend an existing non-active recording of a channel and stream pairing.
   850  //
   851  // The channel must be configured for the initial position from which it will be extended.
   852  //
   853  // Returns the subscriptionId of the recording that can be passed to StopRecording()
   854  func (archive *Archive) ExtendRecording(recordingID int64, stream int32, sourceLocation codecs.SourceLocationEnum, autoStop bool, channel string) (int64, error) {
   855  	correlationID := nextCorrelationID()
   856  	logger.Debugf("ExtendRecording(%d, %d, %d, %t, %s), correlationID:%d", recordingID, stream, sourceLocation, autoStop, channel, correlationID)
   857  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   858  	defer correlations.Delete(correlationID)           // Clear the lookup
   859  
   860  	archive.mtx.Lock()
   861  	defer archive.mtx.Unlock()
   862  	if err := archive.Proxy.ExtendRecordingRequest(correlationID, recordingID, stream, sourceLocation, autoStop, channel); err != nil {
   863  		return 0, err
   864  	}
   865  
   866  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   867  }
   868  
   869  // GetRecordingPosition of the position recorded for an active recording.
   870  //
   871  // Returns the recording position or if there are no active
   872  // recordings then RecordingPositionNull.
   873  func (archive *Archive) GetRecordingPosition(recordingID int64) (int64, error) {
   874  	correlationID := nextCorrelationID()
   875  	logger.Debugf("getRecordingPosition(%d), correlationID:%d", recordingID, correlationID)
   876  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   877  	defer correlations.Delete(correlationID)           // Clear the lookup
   878  
   879  	archive.mtx.Lock()
   880  	defer archive.mtx.Unlock()
   881  	if err := archive.Proxy.RecordingPositionRequest(correlationID, recordingID); err != nil {
   882  		return 0, err
   883  	}
   884  
   885  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   886  }
   887  
   888  // TruncateRecording of a stopped recording to a given position that
   889  // is less than the stopped position. The provided position must be on
   890  // a fragment boundary. Truncating a recording to the start position
   891  // effectively deletes the recording. If the truncate operation will
   892  // result in deleting segments then this will occur
   893  // asynchronously. Before extending a truncated recording which has
   894  // segments being asynchronously being deleted then you should await
   895  // completion via the RecordingSignal Delete
   896  //
   897  // Returns nil on success, error on failre
   898  func (archive *Archive) TruncateRecording(recordingID int64, position int64) error {
   899  	correlationID := nextCorrelationID()
   900  	logger.Debugf("TruncateRecording(%d %d), correlationID:%d", recordingID, position, correlationID)
   901  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   902  	defer correlations.Delete(correlationID)           // Clear the lookup
   903  
   904  	archive.mtx.Lock()
   905  	defer archive.mtx.Unlock()
   906  	if err := archive.Proxy.TruncateRecordingRequest(correlationID, recordingID, position); err != nil {
   907  		return err
   908  	}
   909  
   910  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
   911  	return err
   912  }
   913  
   914  // GetStartPosition for a recording.
   915  //
   916  // Return the start position of the recording or (0, error) on failure
   917  func (archive *Archive) GetStartPosition(recordingID int64) (int64, error) {
   918  	correlationID := nextCorrelationID()
   919  	logger.Debugf("GetStartPosition(%d), correlationID:%d", recordingID, correlationID)
   920  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   921  	defer correlations.Delete(correlationID)           // Clear the lookup
   922  
   923  	archive.mtx.Lock()
   924  	defer archive.mtx.Unlock()
   925  	if err := archive.Proxy.StartPositionRequest(correlationID, recordingID); err != nil {
   926  		return 0, err
   927  	}
   928  
   929  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   930  }
   931  
   932  // GetStopPosition for a recording.
   933  //
   934  // Return the stop position, or RecordingPositionNull if still active.
   935  func (archive *Archive) GetStopPosition(recordingID int64) (int64, error) {
   936  	correlationID := nextCorrelationID()
   937  	logger.Debugf("GetStopPosition(%d), correlationID:%d", recordingID, correlationID)
   938  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   939  	defer correlations.Delete(correlationID)           // Clear the lookup
   940  
   941  	archive.mtx.Lock()
   942  	defer archive.mtx.Unlock()
   943  	if err := archive.Proxy.StopPositionRequest(correlationID, recordingID); err != nil {
   944  		return 0, err
   945  	}
   946  
   947  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   948  }
   949  
   950  // FindLastMatchingRecording that matches the given criteria.
   951  //
   952  // Returns the RecordingID or RecordingIdNullValue if no match
   953  func (archive *Archive) FindLastMatchingRecording(minRecordingID int64, sessionID int32, stream int32, channel string) (int64, error) {
   954  	correlationID := nextCorrelationID()
   955  	logger.Debugf("FindLastMatchingRecording(%d, %d, %d, %s), correlationID:%d", minRecordingID, sessionID, stream, correlationID)
   956  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   957  	defer correlations.Delete(correlationID)           // Clear the lookup
   958  
   959  	archive.mtx.Lock()
   960  	defer archive.mtx.Unlock()
   961  	if err := archive.Proxy.FindLastMatchingRecordingRequest(correlationID, minRecordingID, sessionID, stream, channel); err != nil {
   962  		return 0, err
   963  	}
   964  
   965  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
   966  }
   967  
   968  // ListRecordingSubscriptions to list the active recording
   969  // subscriptions in the archive create via StartRecording or
   970  // ExtendRecording.
   971  //
   972  // Returns a (possibly empty) list of RecordingSubscriptionDescriptors
   973  func (archive *Archive) ListRecordingSubscriptions(pseudoIndex int32, subscriptionCount int32, applyStreamID bool, stream int32, channelFragment string) ([]*codecs.RecordingSubscriptionDescriptor, error) {
   974  	correlationID := nextCorrelationID()
   975  	logger.Debugf("ListRecordingSubscriptions(%, %d, %t, %d, %sd), correlationID:%d", pseudoIndex, subscriptionCount, applyStreamID, stream, channelFragment, correlationID)
   976  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
   977  	defer correlations.Delete(correlationID)           // Clear the lookup
   978  
   979  	archive.mtx.Lock()
   980  	defer archive.mtx.Unlock()
   981  	if err := archive.Proxy.ListRecordingSubscriptionsRequest(correlationID, pseudoIndex, subscriptionCount, applyStreamID, stream, channelFragment); err != nil {
   982  		return nil, err
   983  	}
   984  
   985  	if err := archive.Control.PollForDescriptors(correlationID, archive.SessionID, subscriptionCount); err != nil {
   986  		return nil, err
   987  	}
   988  
   989  	// If there's a ControlResponse let's see what transpired
   990  	response := archive.Control.Results.ControlResponse
   991  	if response != nil {
   992  		switch response.Code {
   993  		case codecs.ControlResponseCode.ERROR:
   994  			return nil, fmt.Errorf("response for correlationID %d (relevantId %d) failed %s", response.CorrelationId, response.RelevantId, response.ErrorMessage)
   995  
   996  		case codecs.ControlResponseCode.SUBSCRIPTION_UNKNOWN:
   997  			return archive.Control.Results.RecordingSubscriptionDescriptors, nil
   998  		}
   999  	}
  1000  
  1001  	// Otherwise we can return our results
  1002  	return archive.Control.Results.RecordingSubscriptionDescriptors, nil
  1003  
  1004  }
  1005  
  1006  // DetachSegments from the beginning of a recording up to the
  1007  // provided new start position. The new start position must be first
  1008  // byte position of a segment after the existing start position.  It
  1009  // is not possible to detach segments which are active for recording
  1010  // or being replayed.
  1011  //
  1012  // Returns error on failure, nil on success
  1013  func (archive *Archive) DetachSegments(recordingID int64, newStartPosition int64) error {
  1014  	correlationID := nextCorrelationID()
  1015  	logger.Debugf("DetachSegments(%d, %d), correlationID:%d", recordingID, correlationID)
  1016  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1017  	defer correlations.Delete(correlationID)           // Clear the lookup
  1018  
  1019  	archive.mtx.Lock()
  1020  	defer archive.mtx.Unlock()
  1021  	if err := archive.Proxy.DetachSegmentsRequest(correlationID, recordingID, newStartPosition); err != nil {
  1022  		return err
  1023  	}
  1024  
  1025  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
  1026  	return err
  1027  }
  1028  
  1029  // DeleteDetachedSegments which have been previously detached from a recording.
  1030  //
  1031  // Returns the count of deleted segment files.
  1032  func (archive *Archive) DeleteDetachedSegments(recordingID int64) (int64, error) {
  1033  	correlationID := nextCorrelationID()
  1034  	logger.Debugf("DeleteDetachedSegments(%d), correlationID:%d", recordingID, correlationID)
  1035  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1036  	defer correlations.Delete(correlationID)           // Clear the lookup
  1037  
  1038  	archive.mtx.Lock()
  1039  	defer archive.mtx.Unlock()
  1040  	if err := archive.Proxy.DeleteDetachedSegmentsRequest(correlationID, recordingID); err != nil {
  1041  		return 0, err
  1042  	}
  1043  
  1044  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1045  }
  1046  
  1047  // PurgeSegments (detach and delete) to segments from the beginning of
  1048  // a recording up to the provided new start position. The new start
  1049  // position must be first byte position of a segment after the
  1050  // existing start position. It is not possible to detach segments
  1051  // which are active for recording or being replayed.
  1052  //
  1053  // Returns the count of deleted segment files.
  1054  func (archive *Archive) PurgeSegments(recordingID int64, newStartPosition int64) (int64, error) {
  1055  	correlationID := nextCorrelationID()
  1056  	logger.Debugf("PurgeSegments(%d, %d), correlationID:%d", recordingID, newStartPosition, correlationID)
  1057  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1058  	defer correlations.Delete(correlationID)           // Clear the lookup
  1059  
  1060  	archive.mtx.Lock()
  1061  	defer archive.mtx.Unlock()
  1062  	if err := archive.Proxy.PurgeSegmentsRequest(correlationID, recordingID, newStartPosition); err != nil {
  1063  		return 0, err
  1064  	}
  1065  
  1066  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1067  }
  1068  
  1069  // AttachSegments to the beginning of a recording to restore history
  1070  // that was previously detached.
  1071  // Segment files must match the existing recording and join exactly to
  1072  // the start position of the recording they are being attached to.
  1073  //
  1074  // Returns the count of attached segment files.
  1075  func (archive *Archive) AttachSegments(recordingID int64) (int64, error) {
  1076  	correlationID := nextCorrelationID()
  1077  	logger.Debugf("AttachSegments(%d), correlationID:%d", recordingID, correlationID)
  1078  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1079  	defer correlations.Delete(correlationID)           // Clear the lookup
  1080  
  1081  	archive.mtx.Lock()
  1082  	defer archive.mtx.Unlock()
  1083  	if err := archive.Proxy.AttachSegmentsRequest(correlationID, recordingID); err != nil {
  1084  		return 0, err
  1085  	}
  1086  
  1087  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1088  }
  1089  
  1090  // MigrateSegments from a source recording and attach them to the
  1091  // beginning of a destination recording.
  1092  //
  1093  // The source recording must match the destination recording for
  1094  // segment length, term length, mtu length, stream id, plus the stop
  1095  // position and term id of the source must join with the start
  1096  // position of the destination and be on a segment boundary.
  1097  //
  1098  // The source recording will be effectively truncated back to its
  1099  // start position after the migration.  Returns the count of attached
  1100  // segment files.
  1101  //
  1102  // Returns the count of attached segment files.
  1103  func (archive *Archive) MigrateSegments(recordingID int64, position int64) (int64, error) {
  1104  	correlationID := nextCorrelationID()
  1105  	logger.Debugf("MigrateSegments(%d, %d), correlationID:%d", recordingID, position, correlationID)
  1106  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1107  	defer correlations.Delete(correlationID)           // Clear the lookup
  1108  
  1109  	archive.mtx.Lock()
  1110  	defer archive.mtx.Unlock()
  1111  	if err := archive.Proxy.MigrateSegmentsRequest(correlationID, recordingID, position); err != nil {
  1112  		return 0, err
  1113  	}
  1114  
  1115  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1116  }
  1117  
  1118  // KeepAlive sends a simple packet to the media-driver
  1119  //
  1120  // Returns error on failure, nil on success
  1121  func (archive *Archive) KeepAlive() error {
  1122  	correlationID := nextCorrelationID()
  1123  	logger.Debugf("KeepAlive(), correlationID:%d", correlationID)
  1124  
  1125  	return archive.Proxy.KeepAliveRequest(correlationID)
  1126  }
  1127  
  1128  // Replicate a recording from a source archive to a destination which
  1129  // can be considered a backup for a primary archive. The source
  1130  // recording will be replayed via the provided replay channel and use
  1131  // the original stream id.  If the destination recording id is
  1132  // RecordingIdNullValue (-1) then a new destination recording is
  1133  // created, otherwise the provided destination recording id will be
  1134  // extended. The details of the source recording descriptor will be
  1135  // replicated.
  1136  //
  1137  // For a source recording that is still active the replay can merge
  1138  // with the live stream and then follow it directly and no longer
  1139  // require the replay from the source. This would require a multicast
  1140  // live destination.
  1141  //
  1142  // srcRecordingID     recording id which must exist in the source archive.
  1143  // dstRecordingID     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.
  1144  // srcControlStreamID remote control stream id for the source archive to instruct the replay on.
  1145  // srcControlChannel  remote control channel for the source archive to instruct the replay on.
  1146  // liveDestination    destination for the live stream if merge is required. nil for no merge.
  1147  //
  1148  // Returns the replication session id which can be passed StopReplication()
  1149  func (archive *Archive) Replicate(srcRecordingID int64, dstRecordingID int64, srcControlStreamID int32, srcControlChannel string, liveDestination string) (int64, error) {
  1150  	correlationID := nextCorrelationID()
  1151  	logger.Debugf("Replicate(%d, %d, %d, %s, %s), correlationID:%d", srcRecordingID, dstRecordingID, srcControlStreamID, srcControlChannel, liveDestination, correlationID)
  1152  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1153  	defer correlations.Delete(correlationID)           // Clear the lookup
  1154  
  1155  	archive.mtx.Lock()
  1156  	defer archive.mtx.Unlock()
  1157  	if err := archive.Proxy.ReplicateRequest(correlationID, srcRecordingID, dstRecordingID, srcControlStreamID, srcControlChannel, liveDestination); err != nil {
  1158  		return 0, err
  1159  	}
  1160  
  1161  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1162  }
  1163  
  1164  // Replicate2 will replicate a recording from a source archive to a
  1165  // destination which can be considered a backup for a primary
  1166  // archive. The source recording will be replayed via the provided
  1167  // replay channel and use the original stream id.  If the destination
  1168  // recording id is RecordingIdNullValue (-1) then a new destination
  1169  // recording is created, otherwise the provided destination recording
  1170  // id will be extended. The details of the source recording descriptor
  1171  // will be replicated.
  1172  //
  1173  // For a source recording that is still active the replay can merge
  1174  // with the live stream and then follow it directly and no longer
  1175  // require the replay from the source. This would require a multicast
  1176  // live destination.
  1177  //
  1178  // srcRecordingID     recording id which must exist in the source archive.
  1179  // dstRecordingID     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.
  1180  // stopPosition       position to stop the replication. RecordingPositionNull to stop at end of current recording.
  1181  // srcControlStreamID remote control stream id for the source archive to instruct the replay on.
  1182  // srcControlChannel  remote control channel for the source archive to instruct the replay on.
  1183  // liveDestination    destination for the live stream if merge is required. nil for no merge.
  1184  // replicationChannel channel over which the replication will occur. Empty or null for default channel.
  1185  //
  1186  // Returns the replication session id which can be passed StopReplication()
  1187  func (archive *Archive) Replicate2(srcRecordingID int64, dstRecordingID int64, stopPosition int64, channelTagID int64, srcControlStreamID int32, srcControlChannel string, liveDestination string, replicationChannel string) (int64, error) {
  1188  	correlationID := nextCorrelationID()
  1189  	logger.Debugf("Replicate2(%d, %d, %d, %d, %d, %s, %s, %s), correlationID:%d", srcRecordingID, dstRecordingID, stopPosition, channelTagID, srcControlStreamID, srcControlChannel, liveDestination, replicationChannel, correlationID)
  1190  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1191  	defer correlations.Delete(correlationID)           // Clear the lookup
  1192  
  1193  	archive.mtx.Lock()
  1194  	defer archive.mtx.Unlock()
  1195  	if err := archive.Proxy.ReplicateRequest2(correlationID, srcRecordingID, dstRecordingID, stopPosition, channelTagID, srcControlStreamID, srcControlChannel, liveDestination, replicationChannel); err != nil {
  1196  		return 0, err
  1197  	}
  1198  
  1199  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1200  }
  1201  
  1202  // TaggedReplicate to replicate a recording from a source archive to a
  1203  // destination which can be considered a backup for a primary
  1204  // archive. The source recording will be replayed via the provided
  1205  // replay channel and use the original stream id.  If the destination
  1206  // recording id is RecordingIdNullValue (-1) then a new destination
  1207  // recording is created, otherwise the provided destination recording
  1208  // id will be extended. The details of the source recording descriptor
  1209  // will be replicated.
  1210  //
  1211  // The subscription used in the archive will be tagged
  1212  // with the provided tags. For a source recording that is still active
  1213  // the replay can merge with the live stream and then follow it
  1214  // directly and no longer require the replay from the source. This
  1215  // would require a multicast live destination.
  1216  //
  1217  // srcRecordingID     recording id which must exist in the source archive.
  1218  // dstRecordingID     recording to extend in the destination, otherwise {@link io.aeron.Aeron#NULL_VALUE}.
  1219  // channelTagID       used to tag the replication subscription.
  1220  // subscriptionTagID  used to tag the replication subscription.
  1221  // srcControlStreamID remote control stream id for the source archive to instruct the replay on.
  1222  // srcControlChannel  remote control channel for the source archive to instruct the replay on.
  1223  // liveDestination    destination for the live stream if merge is required. nil for no merge.
  1224  //
  1225  // Returns the replication session id which can be passed StopReplication()
  1226  func (archive *Archive) TaggedReplicate(srcRecordingID int64, dstRecordingID int64, channelTagID int64, subscriptionTagID int64, srcControlStreamID int32, srcControlChannel string, liveDestination string) (int64, error) {
  1227  	correlationID := nextCorrelationID()
  1228  	logger.Debugf("TaggedReplicate(%d, %d, %d, %d, %d, %s, %s), correlationID:%d", srcRecordingID, dstRecordingID, channelTagID, subscriptionTagID, srcControlStreamID, srcControlChannel, liveDestination, correlationID)
  1229  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1230  	defer correlations.Delete(correlationID)           // Clear the lookup
  1231  
  1232  	archive.mtx.Lock()
  1233  	defer archive.mtx.Unlock()
  1234  	if err := archive.Proxy.TaggedReplicateRequest(correlationID, srcRecordingID, dstRecordingID, channelTagID, subscriptionTagID, srcControlStreamID, srcControlChannel, liveDestination); err != nil {
  1235  		return 0, err
  1236  	}
  1237  
  1238  	return archive.Control.PollForResponse(correlationID, archive.SessionID)
  1239  }
  1240  
  1241  // StopReplication of a replication request
  1242  //
  1243  // Returns error on failure, nil on success
  1244  func (archive *Archive) StopReplication(replicationID int64) error {
  1245  	correlationID := nextCorrelationID()
  1246  	logger.Debugf("StopReplication(%d), correlationID:%d", replicationID, correlationID)
  1247  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1248  	defer correlations.Delete(correlationID)           // Clear the lookup
  1249  
  1250  	archive.mtx.Lock()
  1251  	defer archive.mtx.Unlock()
  1252  	if err := archive.Proxy.StopReplicationRequest(correlationID, replicationID); err != nil {
  1253  		return err
  1254  	}
  1255  
  1256  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
  1257  	return err
  1258  }
  1259  
  1260  // PurgeRecording of a stopped recording, i.e. mark recording as
  1261  // Invalid and delete the corresponding segment files. The space in
  1262  // the Catalog will be reclaimed upon compaction.
  1263  //
  1264  // Returns error on failure, nil on success
  1265  func (archive *Archive) PurgeRecording(recordingID int64) error {
  1266  	correlationID := nextCorrelationID()
  1267  	logger.Debugf("PurgeRecording(%d), correlationID:%d", recordingID, correlationID)
  1268  	correlations.Store(correlationID, archive.Control) // For subsequent lookup in the fragment assemblers
  1269  	defer correlations.Delete(correlationID)           // Clear the lookup
  1270  
  1271  	archive.mtx.Lock()
  1272  	defer archive.mtx.Unlock()
  1273  	if err := archive.Proxy.PurgeRecordingRequest(correlationID, recordingID); err != nil {
  1274  		return err
  1275  	}
  1276  
  1277  	_, err := archive.Control.PollForResponse(correlationID, archive.SessionID)
  1278  	return err
  1279  }