Exemple #1
0
 /// <summary>
 /// Defers the message to the time specified by <paramref name="approximateDueTime"/> at which point in time the message will be
 /// returned to whoever calls <see cref="GetDueMessages"/>
 /// </summary>
 public async Task Defer(DateTimeOffset approximateDueTime, Dictionary <string, string> headers, byte[] body)
 {
     using (var connection = await _connectionProvider.GetConnectionAsync().ConfigureAwait(false))
     {
         using (var command = connection.CreateCommand())
         {
             command.CommandText = $@"
                 INSERT INTO {_tableName.QualifiedName} (
                     `due_time`,
                     `headers`,
                     `body`
                 ) VALUES (
                     @due_time,
                     @headers,
                     @body
                 );";
             var headersString = HeaderSerializer.SerializeToString(headers);
             command.Parameters.Add("due_time", MySqlDbType.DateTime).Value = approximateDueTime.UtcDateTime;
             command.Parameters.Add("headers", MySqlDbType.VarChar, MathUtil.GetNextPowerOfTwo(headersString.Length)).Value = headersString;
             command.Parameters.Add("body", MySqlDbType.VarBinary, MathUtil.GetNextPowerOfTwo(body.Length)).Value           = body;
             await command.ExecuteNonQueryAsync().ConfigureAwait(false);
         }
         await connection.CompleteAsync().ConfigureAwait(false);
     }
 }
Exemple #2
0
 /// <summary>
 /// Saves a snapshot of the saga data along with the given metadata
 /// </summary>
 public async Task Save(ISagaData sagaData, Dictionary <string, string> sagaAuditMetadata)
 {
     using (var connection = await _connectionProvider.GetConnectionAsync())
     {
         using (var command = connection.CreateCommand())
         {
             command.CommandText = $@"
                 INSERT INTO {_tableName.QualifiedName} (
                     `id`,
                     `revision`,
                     `data`,
                     `metadata`
                 ) VALUES (
                     @id, 
                     @revision, 
                     @data,
                     @metadata
                 )";
             var dataString     = DataSerializer.SerializeToString(sagaData);
             var metadataString = MetadataSerializer.SerializeToString(sagaAuditMetadata);
             command.Parameters.Add("id", MySqlDbType.Guid).Value        = sagaData.Id;
             command.Parameters.Add("revision", MySqlDbType.Int32).Value = sagaData.Revision;
             command.Parameters.Add("data", MySqlDbType.VarChar, MathUtil.GetNextPowerOfTwo(dataString.Length)).Value         = dataString;
             command.Parameters.Add("metadata", MySqlDbType.VarChar, MathUtil.GetNextPowerOfTwo(metadataString.Length)).Value = metadataString;
             Console.WriteLine($"OK WE'RE SAVING SAGA SNAPSHOT {sagaData.Id} rev. {sagaData.Revision} NOW");
             await command.ExecuteNonQueryAsync().ConfigureAwait(false);
         }
         await connection.CompleteAsync().ConfigureAwait(false);
     }
 }
        /// <summary>
        /// Acquire a lock for given key
        /// </summary>
        /// <param name="key">Locking key</param>
        /// <param name="cancellationToken">Cancellation token which will be cancelled if Rebus shuts down. Can be used if e.g. distributed locks have a timeout associated with them</param>
        /// <returns>True if the lock was acquired, false if not</returns>
        public async Task <bool> AcquireLockAsync(string key, CancellationToken cancellationToken)
        {
            using (var connection = await _connectionProvider.GetConnectionAsync().ConfigureAwait(false))
            {
                // See if the lock exists and bail if someone already has it
                if (await IsLockAcquiredAsync(key, cancellationToken))
                {
                    return(false);
                }

                // Lock does not exist, so try to insert it. Because two threads could end up here at the same
                // time, if we get a duplicate entry exception, assume someone got there before us and fail out
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = $@"
                        INSERT INTO {_lockTableName.QualifiedName} (
                            lock_key,
                            expiration
                        ) VALUES (
                            @lock_key,
                            @expiration
                        )";
                    command.Parameters.Add("lock_key", MySqlDbType.VarChar, LockKeyColumnSize).Value = key;
                    command.Parameters.Add("expiration", MySqlDbType.DateTime).Value = _rebusTime.Now.Add(_lockExpirationTimeout);
                    try
                    {
                        await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);

                        await connection.CompleteAsync().ConfigureAwait(false);
                    }
                    catch (MySqlException exception) when(exception.ErrorCode == MySqlErrorCode.DuplicateKeyEntry)
                    {
                        // Someone got there before us so bail and try again later
                        return(false);
                    }
                    return(true);
                }
            }
        }
 /// <summary>
 /// Gets all destination addresses for the given topic
 /// </summary>
 public async Task <string[]> GetSubscriberAddresses(string topic)
 {
     using (var connection = await _connectionProvider.GetConnectionAsync())
     {
         using (var command = connection.CreateCommand())
         {
             command.CommandText = $@"
                 SELECT address 
                 FROM {_tableName.QualifiedName} 
                 WHERE topic = @topic";
             command.Parameters.Add("topic", MySqlDbType.VarChar, _topicLength).Value = topic;
             var subscriberAddresses = new List <string>();
             using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false))
             {
                 while (await reader.ReadAsync().ConfigureAwait(false))
                 {
                     var address = (string)reader["address"];
                     subscriberAddresses.Add(address);
                 }
             }
             return(subscriberAddresses.ToArray());
         }
     }
 }
        /// <summary>
        /// Gets the outbound message buffer for sending of messages
        /// </summary>
        /// <param name="context">Transaction context containing the message buffer</param>
        ConcurrentQueue <AddressedTransportMessage> GetOutboundMessageBuffer(ITransactionContext context)
        {
            return(context.GetOrAdd(OutboundMessageBufferKey, () =>
            {
                var outgoingMessages = new ConcurrentQueue <AddressedTransportMessage>();

                async Task SendOutgoingMessages(ITransactionContext _)
                {
                    using (var connection = await _connectionProvider.GetConnectionAsync())
                    {
                        while (outgoingMessages.IsEmpty == false)
                        {
                            if (outgoingMessages.TryDequeue(out var addressed) == false)
                            {
                                break;
                            }

                            await InnerSend(addressed.DestinationAddress, addressed.Message, connection);
                        }
        /// <summary>
        /// Saves the data from the given source stream under the given ID
        /// </summary>
        public async Task Save(string id, Stream source, Dictionary <string, string> metadata = null)
        {
            var metadataToWrite = new Dictionary <string, string>(metadata ?? new Dictionary <string, string>())
            {
                [MetadataKeys.SaveTime] = _rebusTime.Now.ToString("O")
            };

            try
            {
                using (var connection = await _connectionProvider.GetConnectionAsync().ConfigureAwait(false))
                {
                    using (var command = connection.CreateCommand())
                    {
                        var metadataBytes = TextEncoding.GetBytes(_dictionarySerializer.SerializeToString(metadataToWrite));
                        using (var memoryStream = new MemoryStream())
                        {
                            await source.CopyToAsync(memoryStream);

                            var sourceBytes = memoryStream.ToArray();
                            command.CommandTimeout = _commandTimeout;
                            command.CommandText    = $@"
                                INSERT INTO {_tableName.QualifiedName} (
                                    `id`, 
                                    `meta`,
                                    `data`, 
                                    `creation_time`,
                                    `last_read_time`) 
                                VALUES (
                                    @id, 
                                    @meta, 
                                    @data, 
                                    @now,
                                    null
                                )";
                            command.Parameters.Add("id", MySqlDbType.VarChar, 200).Value = id;
                            command.Parameters.Add("meta", MySqlDbType.VarBinary, MathUtil.GetNextPowerOfTwo(metadataBytes.Length)).Value = metadataBytes;
                            command.Parameters.Add("data", MySqlDbType.VarBinary, MathUtil.GetNextPowerOfTwo(sourceBytes.Length)).Value   = sourceBytes;
                            command.Parameters.Add("now", MySqlDbType.DateTime).Value = _rebusTime.Now.DateTime;
                            await command.ExecuteNonQueryAsync().ConfigureAwait(false);
                        }
                    }
                    await connection.CompleteAsync().ConfigureAwait(false);
                }
            }
            catch (Exception exception)
            {
                throw new RebusApplicationException(exception, $"Could not save data with ID {id}");
            }
        }
        /// <summary>
        /// Handle retrieving a message from the queue, decoding it, and performing any transaction maintenance.
        /// </summary>
        /// <param name="context">Transaction context the receive is operating on</param>
        /// <param name="cancellationToken">Token to abort processing</param>
        /// <returns>A <seealso cref="TransportMessage"/> or <c>null</c> if no message can be dequeued</returns>
        protected virtual async Task <TransportMessage> ReceiveInternal(ITransactionContext context, CancellationToken cancellationToken)
        {
            TransportMessage transportMessage;

            using (var connection = await _connectionProvider.GetConnectionAsync().ConfigureAwait(false))
            {
                using (var command = connection.CreateCommand())
                {
                    var tableName = _receiveTableName.QualifiedName;
                    command.CommandText = $@"
                        SELECT id INTO @id
                        FROM {tableName} 
                        WHERE visible < now(6) AND 
                              expiration > now(6) AND
                              processing = 0 
                        ORDER BY priority DESC, 
                                 visible ASC, 
                                 id ASC 
                        LIMIT 1
                        FOR UPDATE;

                        SELECT id,
                               headers,
                               body
                        FROM {tableName}
                        WHERE id = @id
                        LIMIT 1;

                        UPDATE {tableName} 
                        SET processing = 1 
                        WHERE id = @id;

                        SET @id = null";
                    try
                    {
                        using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
                        {
                            transportMessage = await ExtractTransportMessageFromReader(reader, cancellationToken).ConfigureAwait(false);

                            if (transportMessage == null)
                            {
                                return(null);
                            }

                            var messageId = (long)reader["id"];
                            ApplyTransactionSemantics(context, messageId);
                        }
                    }
                    catch (MySqlException exception) when(exception.ErrorCode == MySqlErrorCode.LockDeadlock)
                    {
                        // If we get a transaction deadlock here, simply return null and assume there is nothing to process
                        return(null);
                    }
                    catch (Exception exception) when(cancellationToken.IsCancellationRequested)
                    {
                        // ADO.NET does not throw the right exception when the task gets cancelled - therefore we need to do this:
                        throw new TaskCanceledException("Receive operation was cancelled", exception);
                    }
                }
                await connection.CompleteAsync().ConfigureAwait(false);
            }

            return(transportMessage);
        }
        /// <summary>
        /// Queries the saga index for an instance with the given <paramref name="sagaDataType"/> with a
        /// a property named <paramref name="propertyName"/> and the value <paramref name="propertyValue"/>
        /// </summary>
        public async Task <ISagaData> Find(Type sagaDataType, string propertyName, object propertyValue)
        {
            if (sagaDataType == null)
            {
                throw new ArgumentNullException(nameof(sagaDataType));
            }
            if (propertyName == null)
            {
                throw new ArgumentNullException(nameof(propertyName));
            }
            if (propertyValue == null)
            {
                throw new ArgumentNullException(nameof(propertyValue));
            }

            using (var connection = await _connectionProvider.GetConnectionAsync().ConfigureAwait(false))
            {
                using (var command = connection.CreateCommand())
                {
                    if (propertyName.Equals(IdPropertyName, StringComparison.OrdinalIgnoreCase))
                    {
                        command.CommandText = $@"
                            SELECT data 
                            FROM {_dataTableName.QualifiedName} 
                            WHERE id = @value 
                            LIMIT 1";
                    }
                    else
                    {
                        command.CommandText = $@"
                            SELECT s.data AS data 
                            FROM {_dataTableName.QualifiedName} s 
                                JOIN {_indexTableName.QualifiedName} i ON (s.id = i.saga_id) 
                            WHERE i.`saga_type` = @saga_type AND 
                                  i.`key` = @key AND 
                                  i.`value` = @value 
                            LIMIT 1";
                        var sagaTypeName = GetSagaTypeName(sagaDataType);
                        command.Parameters.Add("key", MySqlDbType.VarChar, propertyName.Length).Value       = propertyName;
                        command.Parameters.Add("saga_type", MySqlDbType.VarChar, sagaTypeName.Length).Value = sagaTypeName;
                    }
                    var correlationPropertyValue = GetCorrelationPropertyValue(propertyValue);
                    command.Parameters.Add("value", MySqlDbType.VarChar, MathUtil.GetNextPowerOfTwo(correlationPropertyValue.Length)).Value = correlationPropertyValue;
                    using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false))
                    {
                        if (!await reader.ReadAsync().ConfigureAwait(false))
                        {
                            return(null);
                        }
                        var value = GetData(reader);
                        try
                        {
                            var sagaData = _sagaSerializer.DeserializeFromString(sagaDataType, value);
                            return(sagaData);
                        }
                        catch (Exception exception)
                        {
                            throw new RebusApplicationException(exception, $"An error occurred while attempting to deserialize '{value}' into a {sagaDataType}");
                        }
                    }
                }
            }
        }
Exemple #9
0
        public async Task CommitAsync(string package, IPackagePatch patch)
        {
            using (var connection = await _connectionProvider.GetConnectionAsync().ConfigureAwait(false))
                using (var transaction = connection.BeginTransaction()) {
                    var tasks = new List <Task>();

                    // Publish versions
                    if (patch.PublishedVersions?.Count > 0)
                    {
                        var packages = new List <PackageVersionEntity>();
                        var tarballs = new List <TarballEntity>();

                        foreach (var version in patch.PublishedVersions)
                        {
                            packages.Add(ToEntity(version));
                            tarballs.Add(ToEntity(version.Tarball));
                        }

                        tasks.Add(connection.ExecuteAsync(CreatePackageVersionQuery, packages, transaction));
                        tasks.Add(connection.ExecuteAsync(CreateTarballQuery, tarballs, transaction));
                    }

                    // Update versions
                    if (patch.UpdatedVersions?.Count > 0)
                    {
                        var versions = patch.UpdatedVersions.Select(ToEntity);
                        tasks.Add(connection.ExecuteAsync(UpdatePackageVersionQuery, versions, transaction));
                    }

                    //  Delete versions
                    if (patch.DeletedVersions?.Count > 0)
                    {
                        var versions = patch.DeletedVersions.Select(id => new PackageVersionEntity()
                        {
                            Package   = id.Name,
                            Version   = id.Version,
                            Published = false,
                            Manifest  = null // This is COALESCED in the UPDATE so it won't actually clobber the manifest
                        });

                        tasks.Add(connection.ExecuteAsync(UpdatePackageVersionQuery, versions, transaction));
                    }

                    // Update/create dist tags
                    if (patch.UpdatedDistTags?.Count > 0)
                    {
                        var tags = patch.UpdatedDistTags.Select(kvp => new DistTagEntity()
                        {
                            Package = package,
                            Tag     = kvp.Key,
                            Version = kvp.Value
                        });

                        tasks.Add(connection.ExecuteAsync(CreateDistTagQuery, tags, transaction));
                    }

                    // Delete dist tags
                    if (patch.DeletedDistTags?.Count > 0)
                    {
                        var param = new { Package = package, Tags = patch.DeletedDistTags };
                        tasks.Add(connection.ExecuteAsync(DeleteDistTagsQuery, param, transaction));
                    }

                    try {
                        await Task.WhenAll(tasks).ConfigureAwait(false);

                        transaction.Commit();
                    }
                    catch (SqliteException ex) {
                        if (ex.SqliteErrorCode != ErrorSqliteConstraint || ex.SqliteExtendedErrorCode != ErrorSqliteConstraintUnique)
                        {
                            throw;
                        }

                        var identifier = patch.PublishedVersions?.Count == 1 ? patch.PublishedVersions[0].Id : null;
                        throw new DuplicatePackageVersionException(identifier, ex);
                    }
                }
        }