/// <summary> /// Persist the <see cref="IStreamProcessorState" /> for <see cref="StreamProcessorId"/> and <see cref="SubscriptionId"/>. /// Handles <see cref="Partitioned.PartitionedStreamProcessorState"/> separately also. /// IsUpsert option creates the document if one isn't found. /// </summary> /// <param name="id">The <see cref="StreamProcessorId" />.</param> /// <param name="baseStreamProcessorState">The <see cref="IStreamProcessorState" />.</param> /// <param name="cancellationToken">The <see cref="CancellationToken" />.</param> /// <returns>A <see cref="Task" /> representing the asynchronous operation.</returns> public async Task Persist(IStreamProcessorId id, IStreamProcessorState baseStreamProcessorState, CancellationToken cancellationToken) { _logger.PersistingStreamProcessorState(id); try { if (id is SubscriptionId subscriptionId) { if (baseStreamProcessorState is Runtime.Events.Processing.Streams.StreamProcessorState streamProcessorState) { var replacementState = new MongoSubscriptionState( subscriptionId.ProducerMicroserviceId, subscriptionId.ProducerTenantId, subscriptionId.StreamId, subscriptionId.PartitionId, streamProcessorState.Position, streamProcessorState.RetryTime.UtcDateTime, streamProcessorState.FailureReason, streamProcessorState.ProcessingAttempts, streamProcessorState.LastSuccessfullyProcessed.UtcDateTime, streamProcessorState.IsFailing); var states = await _subscriptionStates.Get(subscriptionId.ScopeId, cancellationToken).ConfigureAwait(false); var persistedState = await states.ReplaceOneAsync( CreateFilter(subscriptionId), replacementState, new ReplaceOptions { IsUpsert = true }) .ConfigureAwait(false); } else { throw new UnsupportedStreamProcessorStatewithSubscriptionId(subscriptionId, baseStreamProcessorState); } } else if (baseStreamProcessorState is Runtime.Events.Processing.Streams.Partitioned.StreamProcessorState partitionedStreamProcessorState) { var streamProcessorId = id as StreamProcessorId; var states = await _streamProcessorStates.Get(streamProcessorId.ScopeId, cancellationToken).ConfigureAwait(false); var state = await states.ReplaceOneAsync( CreateFilter(streamProcessorId), new Partitioned.PartitionedStreamProcessorState( streamProcessorId.EventProcessorId, streamProcessorId.SourceStreamId, partitionedStreamProcessorState.Position, partitionedStreamProcessorState.FailingPartitions.ToDictionary( kvp => kvp.Key.Value.ToString(), kvp => new FailingPartitionState( kvp.Value.Position, kvp.Value.RetryTime.UtcDateTime, kvp.Value.Reason, kvp.Value.ProcessingAttempts, kvp.Value.LastFailed.UtcDateTime)), partitionedStreamProcessorState.LastSuccessfullyProcessed.UtcDateTime), new ReplaceOptions { IsUpsert = true }) .ConfigureAwait(false); } else if (baseStreamProcessorState is Runtime.Events.Processing.Streams.StreamProcessorState streamProcessorState) { var streamProcessorId = id as StreamProcessorId; var states = await _streamProcessorStates.Get(streamProcessorId.ScopeId, cancellationToken).ConfigureAwait(false); var state = await states.ReplaceOneAsync( CreateFilter(streamProcessorId), new StreamProcessorState( streamProcessorId.EventProcessorId, streamProcessorId.SourceStreamId, streamProcessorState.Position, streamProcessorState.RetryTime.UtcDateTime, streamProcessorState.FailureReason, streamProcessorState.ProcessingAttempts, streamProcessorState.LastSuccessfullyProcessed.UtcDateTime, streamProcessorState.IsFailing), new ReplaceOptions { IsUpsert = true }) .ConfigureAwait(false); } else { throw new StreamProcessorStateOfUnsupportedType(id, baseStreamProcessorState); } } catch (MongoWaitQueueFullException ex) { throw new EventStoreUnavailable("Mongo wait queue is full", ex); } }