Bassam Gamal
Bassam Gamal

Reputation: 701

Broker Service Transaction Management

I am implementing many SSB working on two different instances. They are data push pattern based on asynchronous triggers.

My SQL Info is as shown below: Microsoft SQL Server Management Studio 10.50.2500.0 Microsoft Analysis Services Client Tools 10.50.2500.0 Microsoft Data Access Components (MDAC) 6.1.7601.17514 Microsoft MSXML 3.0 4.0 5.0 6.0 Microsoft Internet Explorer 9.0.8112.16421 Microsoft .NET Framework 2.0.50727.5448 Operating System 6.1.7601

My scenarios are mainly as shown below

  1. Multiple Records are inserted as Bulk in table or One Record.

  2. This data is sent to another DB.

  3. The activation procedure starts between BEGIN TRAN and END TRAN.

  4. It validates this message.

  5. If validation not succeeded, this message should be removed from Queue and ACk is sent back to infrom that message was invalid using a different SSB objects.

  6. Else, ACK is sent infroming message is read successfully.

  7. Then, Activation Procedure call another Procedure to process the message body.

  8. This USP_Process_Records is between BEGIN TRAN AND END TRAN too.

  9. For so many reasons, this procedure might fail according to some buisness needs I've.

  10. Either it'll Pro SQL Server 2008 Service Broker.

  11. So in the Activation Procedure, it'll either go into failure condition for the USP_Process_Records or go to BEGIN CATCH part and rollback the transaction and send failure ACK.

  12. At the end, I found that the previous read success ack isnt' sent at all and the second is sent normally.

    • So I am very confused for Transaction Management in Broker Service.

    • Should I use use BEGIN TRAN for each separated task and remove it from Receive and Process UPS?

    • Should I use TRY, CATCH inside USP_Process_Records too and return errors to USP_Receive_Records?

    • Should I modify my TRY, CATCH blocks ar Receive to avoid this issues

At the end, I want All acks to be sent even if something went wrong after and want to avoid Poison messages and rolling back at all. Thanks in advance.

-BTW I've used rusanu blog for Broker Service Error Handling and Read Pro SQL Server 2008 Service Broker Transaction Management part.

Find below sample for USP.

--USP_Receive_Records

BEGIN TRY
BEGIN TRAN

        WHILE 1=1
        BEGIN
            SELECT @ReplyMessage = NULL, @TargetDlgHandle = NULL

            WAITFOR (RECEIVE TOP(1)
            @TargetDlgHandle=Conversation_Handle
            ,@ReplyMessage = CAST(message_body AS XML)
            ,@ReplyMessageName = Message_Type_Name
            FROM Q_Service_Receive), TIMEOUT 1000

            IF @TargetDlgHandle IS NULL 
            BREAK

            --Check if the message has the same message type expected
            IF @ReplyMessageName=N'Service_Msg'  
            BEGIN
                        --Send Batch Read Success ACK
                        --Send ACK Here
                        EXEC [dbo].[USP_ACKMsg_Send] @ACKMsg, @Service_Msg;
                        --Handle ACK Send failed!

                        -- Execute the USP_Service_Msg_Process for the batch rows
                        EXECUTE USP_Service_Msg_Process @ReplyMessageName, @RC OUTPUT;

                        --Case Processing Succeeded             
                        IF @RC=0
                        BEGIN
                            --Send Batch Read Success ACK
                        END

                        --SEND ACK Processing failed with Return Code to define cause of the error
                        ELSE
                        BEGIN
                            --Send Batch Processing Failed ACK
                        END

            END 
            END CONVERSATION @TargetDlgHandle;
        END
    COMMIT TRAN;
    END TRY

    BEGIN CATCH
    if (XACT_STATE()) = -1
        BEGIN
              rollback transaction;
        END;
    if (XACT_STATE()) = 1
        BEGIN
    DECLARE @error int, @message nvarchar(4000), @handle uniqueidentifier;
    SELECT @error = ERROR_NUMBER(), @message = ERROR_MESSAGE();
    END conversation @handle with error = @error description = @message;
    COMMIT;
        END
    END CATCH
END

--USP_Process_Records
BEGIN TRAN 
        While(@nCount <= @nodesCount)
        BEGIN

        IF(@S_HIS_Status = '02')
            BEGIN
                -- check N_Paid_Trans_ID is not nuul or zero or empty
                IF( @N_GET_ID IS NULL OR @N_GET_ID = 0 OR @N_GET_ID = '')
                    BEGIN
                        SET @RC = 8
                        RETURN;
                    END

                EXECUTE USP_Handle_Delivered_Service @N_GET_ID, @RC OUTPUT
                SELECT @myERROR = @@ERROR--, @myRowCount = @@ROWCOUNT
                IF @myERROR <> 0 OR @RC <> 0
                BEGIN
                    ROLLBACK TRAN
                END
            END

            --A lot of similar cases
END TRAN

Upvotes: 1

Views: 1204

Answers (1)

Remus Rusanu
Remus Rusanu

Reputation: 294177

You are mixing BEGIN TRY/BEGIN CATCH blocks with old style @@ERROR checks. It makes both error handling and transaction handling pretty much impossible to manage. Consider this snippet of code:

SELECT @myERROR = @@ERROR--, @myRowCount = @@ROWCOUNT
IF @myERROR <> 0 OR @RC <> 0
BEGIN
   ROLLBACK TRAN
END

Can you follow the control flow and the transaction flow involved here? The code is executing in the context of being called from a TRY/CATCH block, so the @@ERROR case should never occur and the control flow should jump to the CATCH block. But wait, what if the procedure is called from a different context when there is no TRY/CATCH block? then the @@ERROR case can be taken but that implies the control flow continues! Even when a TRY/CATCH contest is set up, if @RC is non-zero the transaction is rolled back but control flow continues to the next statements which will now execute in the context of per-statement standalone transactions since the overall encompassing transaction has rolled back! In other words in such case you may send an response Ack to a message you did not receive (you just rolled it back!). No wonder you are seeing cases when behavior seems erratic.

I recommend you stick with only one style of error handling (and the only sane style is BEGIN TRY/BEGIN CATCH blocks). Do not rollback intentionally in case of application logic error, but instead use RAISERROR and rely on the CATCH block to rollback as necessary. Also do style your procedure after the template shown at Exception handling and nested transactions. This template allows for message-by-message decision to rollback to a safepoint in the transaction in case of error (ie. commit your RECEIVE batch of messages, even if some of the messages occurred an error in processing).

Upvotes: 3

Related Questions