예제 #1
0
        public async Task Diagnostic_headers_are_ignored()
        {
            using (var connection = sqlConnectionFactory.OpenNewConnection().GetAwaiter().GetResult())
            {
                using (var transaction = connection.BeginTransaction())
                {
                    var transportTransaction = new TransportTransaction();

                    transportTransaction.Set(connection);
                    transportTransaction.Set(transaction);

                    var context = new ContextBag();
                    context.Set(transportTransaction);

                    var headers = new Dictionary <string, string>
                    {
                        [Headers.TimeToBeReceived] = TimeSpan.FromMinutes(-1).ToString()
                    };
                    var operation = new TransportOperation(
                        new OutgoingMessage("1", headers, new byte[0]),
                        new UnicastAddressTag(validAddress)
                        );

                    await dispatcher.Dispatch(new TransportOperations(operation), transportTransaction, context).ConfigureAwait(false);

                    transaction.Commit();
                }

                var expired = await queue.PurgeBatchOfExpiredMessages(connection, 100).ConfigureAwait(false);

                Assert.AreEqual(0, expired);
            }
        }
예제 #2
0
        public async Task Defaults_to_no_ttbr()
        {
            using (var connection = sqlConnectionFactory.OpenNewConnection().GetAwaiter().GetResult())
            {
                using (var transaction = connection.BeginTransaction())
                {
                    var transportTransaction = new TransportTransaction();

                    transportTransaction.Set(connection);
                    transportTransaction.Set(transaction);

                    var context = new ContextBag();
                    context.Set(transportTransaction);

                    var operation = new TransportOperation(
                        new OutgoingMessage("1", new Dictionary <string, string>(), new byte[0]),
                        new UnicastAddressTag(validAddress)
                        );

                    await dispatcher.Dispatch(new TransportOperations(operation), transportTransaction, context).ConfigureAwait(false);

                    transaction.Commit();
                }

                var expired = await queue.PurgeBatchOfExpiredMessages(connection, 100).ConfigureAwait(false);

                Assert.AreEqual(0, expired);
            }
        }
예제 #3
0
        static TransportTransaction CreateTransportTransaction(string messagePartitionKey, CommittableTransaction transaction, ServiceBusClient serviceBusClient)
        {
            var transportTransaction = new TransportTransaction();

            transportTransaction.Set(serviceBusClient);
            transportTransaction.Set("IncomingQueue.PartitionKey", messagePartitionKey);
            transportTransaction.Set(transaction);
            return(transportTransaction);
        }
예제 #4
0
        /// <summary>
        /// Enables providing SqlConnection and SqlTransaction instances that will be used by send operations. The same connection and transaction
        /// can be used in more than one send operation.
        /// </summary>
        /// <param name="options">The <see cref="SendOptions" /> to extend.</param>
        /// <param name="connection">Open SqlConnection instance to be used by send operations.</param>
        /// <param name="transaction">SqlTransaction instance that will be used by any operations perfromed by the transport.</param>
        public static void UseCustomSqlConnectionAndTransaction(this SendOptions options, SqlConnection connection, SqlTransaction transaction)
        {
            var transportTransaction = new TransportTransaction();

            transportTransaction.Set(connection);
            transportTransaction.Set(transaction);

            options.GetExtensions().Set(transportTransaction);
        }
        TransportTransaction PrepareTransportTransaction(SqlConnection connection)
        {
            var transportTransaction = new TransportTransaction();

            //those resources are meant to be used by anyone except message dispatcher e.g. persister
            transportTransaction.Set(connection);
            transportTransaction.Set(Transaction.Current);

            return(transportTransaction);
        }
예제 #6
0
    public Task DeduplicateSQLMessages(string queue, MessageContext message, Dispatch dispatch, Func <Dispatch, Task> forward)
    {
        #region Deduplicate

        async Task DeduplicateDispatch(TransportOperations messages,
                                       TransportTransaction transaction, ContextBag context)
        {
            using (var conn = await OpenConnection().ConfigureAwait(false))
                using (var tx = conn.BeginTransaction())
                {
                    var duplicateOps = new List <UnicastTransportOperation>();

                    //Detect duplicates
                    foreach (var operation in messages.UnicastTransportOperations)
                    {
                        var messageId = operation.Message.MessageId;
                        if (await WasForwarded(conn, tx, messageId).ConfigureAwait(false))
                        {
                            duplicateOps.Add(operation);
                        }
                    }

                    //Remove duplicates
                    foreach (var duplicateOp in duplicateOps)
                    {
                        messages.UnicastTransportOperations.Remove(duplicateOp);
                    }

                    //Set the connection and transaction for the outgoing op
                    var forwardTransaction = new TransportTransaction();
                    forwardTransaction.Set(conn);
                    forwardTransaction.Set(tx);

                    //Dispatch
                    await dispatch(messages, forwardTransaction, context)
                    .ConfigureAwait(false);

                    //Mark as processed (atomically with dispatch)
                    foreach (var operation in messages.UnicastTransportOperations)
                    {
                        var messageId = operation.Message.MessageId;
                        await MarkAsForwarded(conn, tx, messageId).ConfigureAwait(false);
                    }
                    tx.Commit();
                }
        }

        #endregion

        return(forward(message.TransportTransaction.TryGet <SqlConnection>(out var _)
            ? dispatch
            : DeduplicateDispatch));
    }
예제 #7
0
        /// <summary>
        /// Enables the use of custom SqlTransaction instances for publish operations. The same transaction can be used in more than one publish operation.
        /// </summary>
        /// <param name="options">The <see cref="PublishOptions" /> to extend.</param>
        /// <param name="transaction">SqlTransaction instance that will be used by any operations performed by the transport.</param>
        public static void UseCustomSqlTransaction(this PublishOptions options, SqlTransaction transaction)
        {
            // When dispatching, the TransportTransaction is overwritten.
            // The only way for a custom transaction to work is by using immediate dispatch and messages should only appear when the user commits the custom transaction.
            // Which is exactly what will happen after NServiceBus dispatches this message immediately.
            options.RequireImmediateDispatch();

            var transportTransaction = new TransportTransaction();

            transportTransaction.Set(SettingsKeys.IsUserProvidedTransactionKey, true);
            transportTransaction.Set(SettingsKeys.TransportTransactionSqlTransactionKey, transaction);
            options.GetExtensions().Set(transportTransaction);
        }
예제 #8
0
        TransportTransaction CreateTransportTransaction(string incomingQueuePartitionKey, CommittableTransaction transaction)
        {
            var transportTransaction = new TransportTransaction();

            if (transportSettings.TransportTransactionMode == TransportTransactionMode.SendsAtomicWithReceive)
            {
                transportTransaction.Set(serviceBusClient);
                transportTransaction.Set("IncomingQueue.PartitionKey", incomingQueuePartitionKey);
                transportTransaction.Set(transaction);
            }

            return(transportTransaction);
        }
예제 #9
0
        static async Task Forward(RawContext context, Func <RawContext, Task> next, SqlConnection conn, SqlTransaction trans)
        {
            var receivedTransportTransaction = context.Extensions.Get <TransportTransaction>();
            var sqlTransportTransaction      = new TransportTransaction();

            sqlTransportTransaction.Set(conn);
            sqlTransportTransaction.Set(trans);

            context.Extensions.Set(sqlTransportTransaction);

            await next(context);

            context.Extensions.Set(receivedTransportTransaction);
        }
    public override async Task Invoke(IOutgoingPhysicalMessageContext context, Func <Task> next)
    {
        if (!DedupePipelineState.TryGet(context, out var dedupePipelineState))
        {
            await next();

            return;
        }

        var connectionTask = connectionBuilder(CancellationToken.None);

        if (context.Extensions.TryGet(out TransportTransaction _))
        {
            throw new NotSupportedException("Deduplication is currently designed to be used from outside the NServiceBus pipeline. For example to dedup messages being sent from inside a web service endpoint.");
        }

        var messageId = GetMessageId(context);

        var transportTransaction = new TransportTransaction();

        context.Extensions.Set(transportTransaction);
        await using var connection  = await connectionTask;
        await using var transaction = (SqlTransaction) await connection.BeginTransactionAsync();

        transportTransaction.Set(connection);
        transportTransaction.Set(transaction);

        var dedupeManager = new DedupeManager(transaction, table);
        var writeResult   = await dedupeManager.WriteDedupRecord(messageId, dedupePipelineState.Context);

        dedupePipelineState.DedupeOutcome = writeResult.DedupeOutcome;
        dedupePipelineState.Context       = writeResult.Context;
        if (dedupePipelineState.DedupeOutcome == DedupeOutcome.Deduplicated)
        {
            logger.Info($"Message deduplicated. MessageId: {messageId}");
            return;
        }

        await next();

        var commitResult = await dedupeManager.CommitWithDedupCheck(messageId, dedupePipelineState.Context);

        dedupePipelineState.DedupeOutcome = commitResult.DedupeOutcome;
        dedupePipelineState.Context       = commitResult.Context;
        if (commitResult.DedupeOutcome == DedupeOutcome.Deduplicated)
        {
            logger.Info($"Message deduplicated. MessageId: {messageId}");
        }
    }
예제 #11
0
    public async Task Adapts_transport_connection()
    {
        var transportTransaction = new TransportTransaction();

        var transportConnection = dbConnection();
        var transaction         = transportConnection.BeginTransaction();

        transportTransaction.Set("System.Data.SqlClient.SqlConnection", transportConnection);
        transportTransaction.Set("System.Data.SqlClient.SqlTransaction", transaction);

        var result = await sqlDialect.Convert().TryAdaptTransportConnection(transportTransaction, new ContextBag(), () => throw new Exception("Should not be called"),
                                                                            (conn, tx, arg3) => new StorageSession(conn, tx, false, null));

        Assert.IsNotNull(result);
    }
예제 #12
0
        private async Task ProcessFileWithTransaction(string filePath, string messageId)
        {
            using (var transaction = new DirectoryBasedTransaction(_messageDirectory))
            {
                transaction.BeginTransaction(filePath);

                var messageFile       = File.ReadAllLines(transaction.FileToProcess);
                var bodyPath          = messageFile.First();
                var messageHeaderJson = string.Join("", messageFile.Skip(1));
                var messageHeaders    = HeaderSerializer.DeSerialize(messageHeaderJson);

                if (RemoveFileIfExpired(messageHeaders, transaction))
                {
                    return;
                }

                var fileContents         = File.ReadAllBytes(bodyPath);
                var transportTransaction = new TransportTransaction();
                transportTransaction.Set(transaction);

                var shouldCommit = await HandleMessageWithRetries(messageId, messageHeaders, fileContents, transportTransaction, 1);

                if (shouldCommit)
                {
                    transaction.Commit();
                }
            }
        }
예제 #13
0
        public async Task It_invokes_callbacks_when_session_is_completed()
        {
            using (var scope = new TransactionScope())
            {
                var transportTransaction = new TransportTransaction();
                transportTransaction.Set(Transaction.Current);

                var callbackInvoked = false;
                var adapter         = new NHibernateSynchronizedStorageAdapter(SessionFactory);

                using (var storageSession = await adapter.TryAdapt(transportTransaction, new ContextBag()))
                {
                    storageSession.Session(); //Make sure session is initialized
                    storageSession.OnSaveChanges(s =>
                    {
                        callbackInvoked = true;
                        return(Task.FromResult(0));
                    });

                    await storageSession.CompleteAsync();

                    Assert.IsTrue(callbackInvoked);
                }

                scope.Complete();
            }
        }
        async Task<bool> ProcessMessage(MessageQueueTransaction msmqTransaction, Message message, Dictionary<string, string> headers)
        {
            var transportTransaction = new TransportTransaction();

            transportTransaction.Set(msmqTransaction);

            MsmqFailureInfoStorage.ProcessingFailureInfo failureInfo;

            var shouldTryProcessMessage = true;

            if (failureInfoStorage.TryGetFailureInfoForMessage(message.Id, out failureInfo))
            {
                var errorHandleResult = await HandleError(message, headers, failureInfo.Exception, transportTransaction, failureInfo.NumberOfProcessingAttempts).ConfigureAwait(false);

                shouldTryProcessMessage = errorHandleResult != ErrorHandleResult.Handled;
            }

            if (shouldTryProcessMessage)
            {
                try
                {
                    using (var bodyStream = message.BodyStream)
                    {
                        var shouldAbortMessageProcessing = await TryProcessMessage(message.Id, headers, bodyStream, transportTransaction).ConfigureAwait(false);

                        if (shouldAbortMessageProcessing)
                        {
                            return false;
                        }
                    }
                }
                catch (Exception exception)
                {
                    failureInfoStorage.RecordFailureInfoForMessage(message.Id, exception);

                    return false;
                }
            }

            failureInfoStorage.ClearFailureInfoForMessage(message.Id);

            return true;
        }
        public async Task Save_fails_when_data_changes_between_concurrent_instances()
        {
            var saga = new TestSagaData
            {
                Id = Guid.NewGuid()
            };

            var persister = new InMemorySagaPersister();
            var storageAdapter = new InMemoryTransactionalSynchronizedStorageAdapter();
            var insertSession = new InMemorySynchronizedStorageSession();

            await persister.Save(saga, SagaMetadataHelper.GetMetadata<TestSaga>(saga), insertSession, new ContextBag());
            await insertSession.CompleteAsync();

            Assert.That(async () =>
            {
                using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
                {
                    Transaction.Current.EnlistDurable(EnlistmentWhichEnforcesDtcEscalation.Id, new EnlistmentWhichEnforcesDtcEscalation(), EnlistmentOptions.None);

                    var transportTransaction = new TransportTransaction();
                    transportTransaction.Set(Transaction.Current);

                    var unenlistedSession = new InMemorySynchronizedStorageSession();

                    var enlistedSession = await storageAdapter.TryAdapt(transportTransaction, new ContextBag());

                    var unenlistedSessionContext = new ContextBag();
                    var unenlistedRecord = await persister.Get<TestSagaData>(saga.Id, unenlistedSession, unenlistedSessionContext);
                    var enlistedSessionContext = new ContextBag();
                    var enlistedRecord = await persister.Get<TestSagaData>("Id", saga.Id, enlistedSession, enlistedSessionContext);

                    await persister.Update(unenlistedRecord, unenlistedSession, unenlistedSessionContext);
                    await persister.Update(enlistedRecord, enlistedSession, enlistedSessionContext);

                    await unenlistedSession.CompleteAsync();

                    tx.Complete();
                }
            }, Throws.Exception.TypeOf<TransactionAbortedException>());
        }