Quantcast
Channel: SQL Service Broker forum
Viewing all articles
Browse latest Browse all 461

Service broker error handling / transaction rollback

$
0
0

We have a service broker activation sp for an auditing service that we are in the process of testing. Our QA purposefully en-queued a message with a bad XML element so it fails during the XML parsing process (does not get to the point of inserting an audit record).  In our catch block, we attempt to do a rollback to the save point after the message is received (so it won't be re-queued and eventually poison the queue) but sql gives us the following error when we do this:

"The current transaction cannot be committed and cannot be rolled back to a save-point. Roll back the entire transaction."

Why does SQL prevent us from rolling back to the save point in this instance?  Is there a good workaround?

ALTER PROCEDURE [dbo].[ImageAuditMessageDequeue]
AS

  SET NOCOUNT ON;

  DECLARE @RecvReqDlgHandle UNIQUEIDENTIFIER;
  DECLARE @RecvReqMsg varbinary(max);
  DECLARE @RecvReqMsgName sysname;
  declare @payload xml;

  DECLARE @ErrorNumber AS INT;
  DECLARE @ErrorMessage as nvarchar(4000);
  DECLARE @ErrorLine as int;
  DECLARE @ErrorProcedure as nvarchar(128);
  DECLARE @ErrorState as int;
  DECLARE @ErrorSeverity as int;
  DECLARE @EventMessage as VARCHAR(MAX);

  WHILE (1=1)
  BEGIN

    BEGIN TRANSACTION;

    WAITFOR
    ( RECEIVE TOP(1)
        @RecvReqDlgHandle = conversation_handle,
        @RecvReqMsg = message_body,
        @RecvReqMsgName = message_type_name
      FROM ImageAuditQueue
    ), TIMEOUT 5000;

    IF (@@ROWCOUNT = 0)
    BEGIN
      ROLLBACK TRANSACTION;
      BREAK;
    END

SAVE TRANSACTION MessageReceivedSavePoint

select @payload = cast(@RecvReqMsg as xml);

    IF @RecvReqMsgName = N'http://schemas.foo.com/messages/ImageAudit/Insert'
    BEGIN
BEGIN TRY
--Ugh, SQL XQuery will return 0 (instead of null) for empty node values that are declared as int so we must workaround this to coerce a null value for empty nodes
declare @companyId as int = cast(nullif(@payload.value('(/Root/ImageAudit/CompanyID)[1]', 'varchar(25)'), '') AS int);

declare @appId as int = @payload.value('(/Root/ImageAudit/AppID)[1]','int');  --error raised here due to bad xml value in message
declare @batchId as char(6) = @payload.value('(/Root/ImageAudit/BatchID)[1]','char(6)');

declare @imageId as int = cast(nullif(@payload.value('(/Root/ImageAudit/ImageID)[1]', 'varchar(25)'), '') AS int);
declare @docId as int = cast(nullif(@payload.value('(/Root/ImageAudit/DocID)[1]', 'varchar(25)'), '') AS int);
declare @docAge as int = cast(nullif(@payload.value('(/Root/ImageAudit/DocAgeInDays)[1]', 'varchar(25)'), '') AS int);

declare @currentDateTime as datetime = @payload.value('(/Root/ImageAudit/CurrentDateTime)[1]','datetime');

declare @userId as int = @payload.value('(/Root/ImageAudit/UserID)[1]','int');
declare @action as varchar(25) = @payload.value('(/Root/ImageAudit/Action)[1]','varchar(25)');

declare @seconds as float = cast(nullif(@payload.value('(/Root/ImageAudit/Seconds)[1]', 'varchar(25)'), '') AS float);

declare @additionalInfo as varchar(max) = @payload.value('(/Root/ImageAudit/AdditionalInfo)[1]','varchar(max)');

INSERT INTO FOTNAudit.dbo.ImageAuditDaily
(AppID, BatchID, ImageID, DocID, CurrentDateTime, UserID, Action, Seconds,CompanyID, DocAgeInDays, AdditionalInfo)
VALUES 
(@appId, @batchId, @imageId, @docId, @currentDateTime, @userId, @action, @seconds, @companyId, @docAge, @additionalInfo)

END CONVERSATION @RecvReqDlgHandle;
END TRY
BEGIN CATCH
-- We do not want to allow a message insertion to fail 5 consecutive times otherwise we risk the queue being stopped / poisoned.
--Because of this we implement our own error handling that simply logs the error and ends the conversation.
--This SHOULD prevent failed insertions from ever poisoning the queue.
SET @ErrorNumber = ERROR_NUMBER();
SET @ErrorSeverity = ERROR_SEVERITY();
SET @ErrorState = ERROR_STATE();
SET @ErrorProcedure = ERROR_PROCEDURE();
SET @ErrorLine = ERROR_LINE();
SET @ErrorMessage = ERROR_MESSAGE();    

SET @EventMessage = CONVERT(VARCHAR(MAX), @payload);
EXEC xp_logevent 60000, @EventMessage, ERROR; --this guy is not transactional so don't need to worry about logevent rollback

PRINT '@EventMessage = ' + @EventMessage
PRINT '@ErrorMessage = ' + @ErrorMessage

--Roll back transaction to the point that message has been deqeued but not processed
--to avoid potential queue poisoning
--This is where encounter the following error:
--"The current transaction cannot be committed and cannot be rolled back to a savepoint. Roll back the entire transaction."
            rollback transaction MessageReceivedSavePoint;        

            --end conversation @RecvReqDlgHandle with error = @ErrorNumber description = @ErrorMessage; 

--No real benefit in signaling a failure to sender so we just end the conversation      
END CONVERSATION @RecvReqDlgHandle;              
END CATCH;
    END
    ELSE IF @RecvReqMsgName = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    BEGIN
       END CONVERSATION @RecvReqDlgHandle;
    END
    ELSE IF @RecvReqMsgName = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
    BEGIN
--Handling Service Broker Error Messages 
--https://technet.microsoft.com/en-us/library/ms171599(v=sql.105).aspx
--Log an error to the sql server log file and event viewer
SET @EventMessage = CONVERT(VARCHAR(MAX), @RecvReqMsg);
EXEC xp_logevent 60000, @EventMessage, ERROR;

PRINT '@EventMessage = ' + @EventMessage

END CONVERSATION @RecvReqDlgHandle;
    END

    COMMIT TRANSACTION;

  END

SQL2014

         

Viewing all articles
Browse latest Browse all 461

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>