Example #1
0
        internal async Task <int> TryScavenge(
            StreamIdInfo streamIdInfo,
            CancellationToken cancellationToken)
        {
            if (streamIdInfo.PostgresqlStreamId == PostgresqlStreamId.Deleted)
            {
                return(-1);
            }

            try
            {
                using (var connection = await OpenConnection(cancellationToken))
                    using (var transaction = connection.BeginTransaction())
                    {
                        var deletedMessageIds = new List <Guid>();
                        using (var command = BuildFunctionCommand(
                                   _schema.Scavenge,
                                   transaction,
                                   Parameters.StreamId(streamIdInfo.PostgresqlStreamId)))
                            using (var reader = await command.ExecuteReaderAsync(cancellationToken).NotOnCapturedContext())
                            {
                                while (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                                {
                                    deletedMessageIds.Add(reader.GetGuid(0));
                                }
                            }

                        if (Logger.IsInfoEnabled())
                        {
                            Logger.Info($"Found {deletedMessageIds.Count} message(s) for stream {streamIdInfo.PostgresqlStreamId} to scavenge.");
                        }

                        if (deletedMessageIds.Count > 0)
                        {
                            if (Logger.IsDebugEnabled())
                            {
                                Logger.Debug($"Scavenging the following messages on stream {streamIdInfo.PostgresqlStreamId}: {string.Join(", ", deletedMessageIds)}");
                            }

                            await DeleteEventsInternal(
                                streamIdInfo,
                                deletedMessageIds.ToArray(),
                                transaction,
                                cancellationToken).NotOnCapturedContext();
                        }

                        await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();

                        return(deletedMessageIds.Count);
                    }
            }
            catch (Exception ex)
            {
                if (Logger.IsWarnEnabled())
                {
                    Logger.WarnException(
                        $"Scavenge attempt failed on stream {streamIdInfo.PostgresqlStreamId.IdOriginal}. Another attempt will be made when this stream is written to.",
                        ex);
                }
            }

            return(-1);
        }
        internal async Task <int> TryScavenge(
            StreamIdInfo streamId,
            CancellationToken cancellationToken)
        {
            if (streamId.MySqlStreamId == MySqlStreamId.Deleted)
            {
                return(-1);
            }

            try
            {
                using (var connection = await OpenConnection(cancellationToken))
                    using (var transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false))
                    {
                        var deletedMessageIds = new List <Guid>();
                        using (var command = BuildStoredProcedureCall(
                                   _schema.Scavenge,
                                   transaction,
                                   Parameters.StreamId(streamId.MySqlStreamId)))
                            using (var reader = await command
                                                .ExecuteReaderAsync(cancellationToken)
                                                .ConfigureAwait(false))
                            {
                                while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
                                {
                                    deletedMessageIds.Add(reader.GetGuid(0));
                                }
                            }

                        Logger.Info(
                            "Found {deletedMessageIdCount} message(s) for stream {streamId} to scavenge.",
                            deletedMessageIds.Count,
                            streamId.MySqlStreamId.IdOriginal);

                        if (deletedMessageIds.Count > 0)
                        {
                            Logger.Debug(
                                "Scavenging the following messages on stream {streamId}: {deletedMessageIds}",
                                streamId.MySqlStreamId.IdOriginal,
                                deletedMessageIds);
                        }

                        foreach (var deletedMessageId in deletedMessageIds)
                        {
                            await DeleteEventInternal(streamId, deletedMessageId, transaction, cancellationToken);
                        }

                        await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);

                        return(deletedMessageIds.Count);
                    }
            }
            catch (Exception ex)
            {
                Logger.WarnException(
                    "Scavenge attempt failed on stream {streamId}. Another attempt will be made when this stream is written to.",
                    ex,
                    streamId.MySqlStreamId.IdOriginal);
            }

            return(-1);
        }
Example #3
0
        protected override async Task <AppendResult> AppendToStreamInternal(
            string streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            int       maxRetries = 2; //TODO too much? too little? configurable?
            Exception exception;

            int retryCount = 0;

            do
            {
                try
                {
                    AppendResult result;
                    var          streamIdInfo = new StreamIdInfo(streamId);

                    using (var connection = await OpenConnection(cancellationToken))
                        using (var transaction = connection.BeginTransaction())
                            using (var command = BuildFunctionCommand(
                                       _schema.AppendToStream,
                                       transaction,
                                       Parameters.StreamId(streamIdInfo.PostgresqlStreamId),
                                       Parameters.StreamIdOriginal(streamIdInfo.PostgresqlStreamId),
                                       Parameters.MetadataStreamId(streamIdInfo.MetadataPosgresqlStreamId),
                                       Parameters.ExpectedVersion(expectedVersion),
                                       Parameters.CreatedUtc(_settings.GetUtcNow?.Invoke()),
                                       Parameters.NewStreamMessages(messages)))
                            {
                                try
                                {
                                    using (var reader = await command
                                                        .ExecuteReaderAsync(cancellationToken)
                                                        .NotOnCapturedContext())
                                    {
                                        await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                                        result = new AppendResult(reader.GetInt32(0), reader.GetInt64(1));
                                    }

                                    await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();
                                }
                                catch (PostgresException ex) when(ex.IsWrongExpectedVersion())
                                {
                                    await transaction.RollbackAsync(cancellationToken).NotOnCapturedContext();

                                    throw new WrongExpectedVersionException(
                                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                                  streamIdInfo.PostgresqlStreamId.IdOriginal,
                                                  expectedVersion),
                                              ex);
                                }
                            }

                    if (_settings.ScavengeAsynchronously)
                    {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                        Task.Run(() => TryScavenge(streamIdInfo, cancellationToken), cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                    }
                    else
                    {
                        await TryScavenge(streamIdInfo, cancellationToken).NotOnCapturedContext();
                    }

                    return(result);
                }
                catch (PostgresException ex) when(ex.IsDeadlock())
                {
                    exception = ex;
                    retryCount++;
                }
            } while(retryCount < maxRetries);

            ExceptionDispatchInfo.Capture(exception).Throw();
            return(default); // never actually run
        protected override async Task <SetStreamMetadataResult> SetStreamMetadataInternal(
            string streamId,
            int expectedStreamMetadataVersion,
            int?maxAge,
            int?maxCount,
            string metadataJson,
            CancellationToken cancellationToken)
        {
            var metadata = new MetadataMessage
            {
                StreamId = streamId,
                MaxAge   = maxAge,
                MaxCount = maxCount,
                MetaJson = metadataJson
            };

            var metadataMessageJsonData = SimpleJson.SerializeObject(metadata);

            var streamIdInfo = new StreamIdInfo(streamId);

            var currentVersion = Parameters.CurrentVersion();

            try
            {
                using (var connection = await OpenConnection(cancellationToken))
                    using (var transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false))
                        using (var command = BuildStoredProcedureCall(
                                   _schema.SetStreamMetadata,
                                   transaction,
                                   Parameters.StreamId(streamIdInfo.MySqlStreamId),
                                   Parameters.MetadataStreamId(streamIdInfo.MetadataMySqlStreamId),
                                   Parameters.MetadataStreamIdOriginal(streamIdInfo.MetadataMySqlStreamId),
                                   Parameters.OptionalMaxAge(metadata.MaxAge),
                                   Parameters.OptionalMaxCount(metadata.MaxCount),
                                   Parameters.ExpectedVersion(expectedStreamMetadataVersion),
                                   Parameters.CreatedUtc(_settings.GetUtcNow?.Invoke()),
                                   Parameters.MetadataMessageMessageId(
                                       streamIdInfo.MetadataMySqlStreamId,
                                       expectedStreamMetadataVersion,
                                       metadataMessageJsonData),
                                   Parameters.MetadataMessageType(),
                                   Parameters.MetadataMessageJsonData(metadataMessageJsonData),
                                   currentVersion,
                                   Parameters.CurrentPosition()))
                        {
                            await command.ExecuteNonQueryAsync(cancellationToken).NotOnCapturedContext();

                            await transaction.CommitAsync(cancellationToken).NotOnCapturedContext();
                        }

                await TryScavenge(streamIdInfo, cancellationToken);

                return(new SetStreamMetadataResult((int)currentVersion.Value));
            }
            catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
            {
                throw new WrongExpectedVersionException(
                          ErrorMessages.AppendFailedWrongExpectedVersion(
                              streamIdInfo.MetadataMySqlStreamId.IdOriginal,
                              expectedStreamMetadataVersion),
                          streamIdInfo.MetadataMySqlStreamId.IdOriginal,
                          ExpectedVersion.Any,
                          ex);
            }
        }
        protected override async Task <ReadAllPage> ReadAllForwardsInternal(
            long fromPositionExlusive,
            int maxCount,
            bool prefetch,
            ReadNextAllPage readNext,
            CancellationToken cancellationToken)
        {
            maxCount = maxCount == int.MaxValue ? maxCount - 1 : maxCount;
            long ordinal = fromPositionExlusive;

            using (var connection = _createConnection())
            {
                await connection.OpenAsync(cancellationToken).NotOnCapturedContext();

                var commandText = prefetch ? _scripts.ReadAllForwardWithData : _scripts.ReadAllForward;
                using (var command = new SqlCommand(commandText, connection))
                {
                    command.Parameters.AddWithValue("ordinal", ordinal);
                    command.Parameters.AddWithValue("count", maxCount + 1); //Read extra row to see if at end or not
                    var reader = await command
                                 .ExecuteReaderAsync(cancellationToken)
                                 .NotOnCapturedContext();

                    List <StreamMessage> messages = new List <StreamMessage>();
                    if (!reader.HasRows)
                    {
                        return(new ReadAllPage(
                                   fromPositionExlusive,
                                   fromPositionExlusive,
                                   true,
                                   ReadDirection.Forward,
                                   readNext,
                                   messages.ToArray()));
                    }

                    while (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                    {
                        if (messages.Count == maxCount)
                        {
                            messages.Add(default(StreamMessage));
                        }
                        else
                        {
                            var streamId      = reader.GetString(0);
                            var streamVersion = reader.GetInt32(1);
                            ordinal = reader.GetInt64(2);
                            var eventId      = reader.GetGuid(3);
                            var created      = reader.GetDateTime(4);
                            var type         = reader.GetString(5);
                            var jsonMetadata = reader.GetString(6);

                            Func <CancellationToken, Task <string> > getJsonData;
                            if (prefetch)
                            {
                                var jsonData = reader.GetString(7);
                                getJsonData = _ => Task.FromResult(jsonData);
                            }
                            else
                            {
                                var streamIdInfo = new StreamIdInfo(streamId);
                                getJsonData = ct => GetJsonData(streamIdInfo.SqlStreamId.Id, streamVersion, ct);
                            }

                            var message = new StreamMessage(streamId,
                                                            eventId,
                                                            streamVersion,
                                                            ordinal,
                                                            created,
                                                            type,
                                                            jsonMetadata,
                                                            getJsonData);

                            messages.Add(message);
                        }
                    }

                    bool isEnd = true;

                    if (messages.Count == maxCount + 1) // An extra row was read, we're not at the end
                    {
                        isEnd = false;
                        messages.RemoveAt(maxCount);
                    }

                    var nextPosition = messages[messages.Count - 1].Position + 1;

                    return(new ReadAllPage(
                               fromPositionExlusive,
                               nextPosition,
                               isEnd,
                               ReadDirection.Forward,
                               readNext,
                               messages.ToArray()));
                }
            }
        }
        protected override async Task <SetStreamMetadataResult> SetStreamMetadataInternal(
            string streamId,
            int expectedStreamMetadataVersion,
            int?maxAge,
            int?maxCount,
            string metadataJson,
            CancellationToken cancellationToken)
        {
            MsSqlAppendResult result;

            using (var connection = _createConnection())
            {
                var streamIdInfo = new StreamIdInfo(streamId);

                await connection.OpenAsync(cancellationToken).NotOnCapturedContext();

                using (var transaction = connection.BeginTransaction())
                {
                    var metadataMessage = new MetadataMessage
                    {
                        StreamId = streamId,
                        MaxAge   = maxAge,
                        MaxCount = maxCount,
                        MetaJson = metadataJson
                    };
                    var json      = SimpleJson.SerializeObject(metadataMessage);
                    var messageId = MetadataMessageIdGenerator.Create(
                        streamId,
                        expectedStreamMetadataVersion,
                        json);
                    var newStreamMessage = new NewStreamMessage(messageId, "$stream-metadata", json);

                    result = await AppendToStreamInternal(
                        connection,
                        transaction,
                        streamIdInfo.MetadataSqlStreamId,
                        expectedStreamMetadataVersion,
                        new[] { newStreamMessage },
                        cancellationToken);

                    using (var command = new SqlCommand(_scripts.SetStreamMetadata, connection, transaction))
                    {
                        command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                        {
                            Value = streamIdInfo.SqlStreamId.Id
                        });
                        command.Parameters.AddWithValue("streamIdOriginal", streamIdInfo.SqlStreamId.IdOriginal);
                        command.Parameters.Add("maxAge", SqlDbType.Int);
                        command.Parameters["maxAge"].Value = maxAge ?? -1;
                        command.Parameters.Add("maxCount", SqlDbType.Int);
                        command.Parameters["maxCount"].Value = maxCount ?? -1;
                        await command.ExecuteNonQueryAsync(cancellationToken);
                    }

                    transaction.Commit();
                }
            }

            await CheckStreamMaxCount(streamId, maxCount, cancellationToken);

            return(new SetStreamMetadataResult(result.CurrentVersion));
        }
Example #7
0
        protected override async Task <AppendResult> AppendToStreamInternal(
            string streamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var streamIdInfo = new StreamIdInfo(streamId);

            if (_settings.AppendDeadlockRetryAttempts == 0)
            {
                try
                {
                    return(messages.Length == 0
                        ? await CreateEmptyStream(streamIdInfo, expectedVersion, cancellationToken)
                        : await AppendMessagesToStream(streamIdInfo, expectedVersion, messages, cancellationToken));
                }
                catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                  streamIdInfo.MySqlStreamId.IdOriginal,
                                  expectedVersion),
                              streamIdInfo.MySqlStreamId.IdOriginal,
                              expectedVersion,
                              ex);
                }
            }

            var retryableExceptions = new List <Exception>();

            while (retryableExceptions.Count <= _settings.AppendDeadlockRetryAttempts)
            {
                try
                {
                    return(messages.Length == 0
                        ? await CreateEmptyStream(streamIdInfo, expectedVersion, cancellationToken)
                        : await AppendMessagesToStream(streamIdInfo, expectedVersion, messages, cancellationToken));
                }
                catch (MySqlException ex) when(ex.IsDeadlock())
                {
                    retryableExceptions.Add(ex);
                }
                catch (MySqlException ex) when(ex.IsWrongExpectedVersion())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(
                                  streamIdInfo.MySqlStreamId.IdOriginal,
                                  expectedVersion),
                              streamIdInfo.MySqlStreamId.IdOriginal,
                              expectedVersion,
                              ex);
                }
            }
            throw new WrongExpectedVersionException(
                      MySqlErrorMessages.AppendFailedDeadlock(
                          streamIdInfo.MySqlStreamId.IdOriginal,
                          expectedVersion,
                          _settings.AppendDeadlockRetryAttempts),
                      streamIdInfo.MySqlStreamId.IdOriginal,
                      expectedVersion,
                      new AggregateException(retryableExceptions));
        }
Example #8
0
        protected override async Task <ReadAllPage> ReadAllForwardsInternal(
            long fromPosition,
            int maxCount,
            bool prefetch,
            ReadNextAllPage readNext,
            CancellationToken cancellationToken)
        {
            maxCount = maxCount == int.MaxValue ? maxCount - 1 : maxCount;
            long position = fromPosition;

            using (var connection = _createConnection())
            {
                await connection.OpenAsync(cancellationToken).ConfigureAwait(false);

                var commandText = prefetch ? _scripts.ReadAllForwardWithData : _scripts.ReadAllForward;
                using (var command = new SqlCommand(commandText, connection))
                {
                    command.CommandTimeout = _commandTimeout;
                    command.Parameters.AddWithValue("position", position);
                    command.Parameters.AddWithValue("count", maxCount + 1); //Read extra row to see if at end or not
                    var reader = await command
                                 .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken)
                                 .ConfigureAwait(false);

                    if (!reader.HasRows)
                    {
                        return(new ReadAllPage(
                                   fromPosition,
                                   fromPosition,
                                   true,
                                   ReadDirection.Forward,
                                   readNext,
                                   Array.Empty <StreamMessage>()));
                    }

                    var messages = new List <(StreamMessage, int?)>();

                    while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
                    {
                        var ordinal       = 0;
                        var streamId      = reader.GetString(ordinal++);
                        var maxAge        = reader.GetNullableInt32(ordinal++);
                        var streamVersion = reader.GetInt32(ordinal++);
                        position = reader.GetInt64(ordinal++);
                        var eventId      = reader.GetGuid(ordinal++);
                        var created      = reader.GetDateTime(ordinal++);
                        var type         = reader.GetString(ordinal++);
                        var jsonMetadata = reader.GetString(ordinal++);

                        Func <CancellationToken, Task <string> > getJsonData;
                        if (prefetch)
                        {
                            var jsonData = await reader.GetTextReader(ordinal).ReadToEndAsync();

                            getJsonData = _ => Task.FromResult(jsonData);
                        }
                        else
                        {
                            var streamIdInfo = new StreamIdInfo(streamId);
                            getJsonData = ct => GetJsonData(streamIdInfo.SqlStreamId.Id, streamVersion, ct);
                        }

                        var message = new StreamMessage(streamId,
                                                        eventId,
                                                        streamVersion,
                                                        position,
                                                        created,
                                                        type,
                                                        jsonMetadata,
                                                        getJsonData);

                        messages.Add((message, maxAge));
                    }

                    bool isEnd = true;

                    if (messages.Count == maxCount + 1) // An extra row was read, we're not at the end
                    {
                        isEnd = false;
                        messages.RemoveAt(maxCount);
                    }

                    var filteredMessages = FilterExpired(messages);

                    var nextPosition = filteredMessages[filteredMessages.Count - 1].Position + 1;

                    return(new ReadAllPage(
                               fromPosition,
                               nextPosition,
                               isEnd,
                               ReadDirection.Forward,
                               readNext,
                               filteredMessages.ToArray()));
                }
            }
        }