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 }