I'm using SQL2016 SP2. My objective is to a create Service Broker service using internal activation stored proc., that every time it gets any error, it will just rollback, store the error message into another table, skip that error message, and the service can process the next message. Unfortunately, I found when an error occur, the queue will also be disabled automatically, then I need to manually clear the queue by executing END CONVERSATION WITH CLEANUP for each error message inside the queue, otherwise I can't enable the queue again. But actually I don't want any manual intervention to enable the queue again. So my question is how can I stop SQL Server from disabling the queue even an fatal error occurred inside the internal activation stored proc.? Below is my testing:
USE master; GO CREATE DATABASE [SBTEST3]; GO ALTER DATABASE [SBTEST3] SET ENABLE_BROKER; GO USE [SBTEST3] GO CREATE MESSAGE TYPE [TestPoisonMessage] VALIDATION = NONE; GO CREATE CONTRACT [TestPoisonContract] ([TestPoisonMessage] SENT BY INITIATOR); GO CREATE QUEUE [dbo].[TestPoisonQueue] WITH STATUS = OFF; GO CREATE SERVICE [TestPoisonInitiator] ON QUEUE [dbo].[TestPoisonQueue]; GO CREATE SERVICE [TestPoisonTarget] ON QUEUE [dbo].[TestPoisonQueue] ([TestPoisonContract]); GO CREATE TABLE [dbo].[SBAuditLog] ( [message] nvarchar(4000) NULL, [logTime] datetime2(2) NOT NULL DEFAULT GETDATE(), [err_num] int NULL, [err_msg] nvarchar(4000) NULL ) GO CREATE OR ALTER PROC [dbo].[uspTestPoison] AS SET NOCOUNT ON; DECLARE @message_type varchar(100), @dialog uniqueidentifier, @message_body nvarchar(4000); BEGIN TRY BEGIN TRAN; WAITFOR ( RECEIVE TOP(1) @message_type = message_type_name, @message_body = CAST(message_body AS nvarchar(4000)), @dialog = [conversation_handle] FROM [dbo].[TestPoisonQueue] ), TIMEOUT 500 ; IF (@@ROWCOUNT != 0 AND @message_body IS NOT NULL) BEGIN IF @message_type = 'TestPoisonMessage' BEGIN INSERT INTO SBAuditlog ([message]) VALUES (@message_body); -- Fatal Error RAISERROR ('Fatal Error', 25, 1) WITH LOG; END END CONVERSATION @dialog; END COMMIT; END TRY BEGIN CATCH DECLARE @error int, @message nvarchar(4000); SELECT @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(); -- Uncommitable transaction IF XACT_STATE() = -1 BEGIN ROLLBACK; END -- Commitable transaction ELSE IF XACT_STATE() = 1 BEGIN COMMIT; END -- Failure audit log INSERT INTO SBAuditlog ([message], err_num, err_msg) VALUES (@message_body, @error, @message); -- End Conversion with Error END CONVERSATION @dialog WITH error = @error DESCRIPTION = @message; END CATCH GO ALTER QUEUE [dbo].[TestPoisonQueue] WITH STATUS = ON, ACTIVATION (STATUS = ON, PROCEDURE_NAME = [dbo].[uspTestPoison], MAX_QUEUE_READERS = 1, EXECUTE AS OWNER); GO DECLARE @Handle UNIQUEIDENTIFIER; BEGIN DIALOG CONVERSATION @Handle FROM SERVICE [TestPoisonInitiator] TO SERVICE 'TestPoisonTarget' ON CONTRACT [TestPoisonContract] WITH ENCRYPTION = OFF; SEND ON CONVERSATION @Handle MESSAGE TYPE [TestPoisonMessage] (N'Testing Message');
After running the above SEND command, the queue will be disabled. I need to run below command to generate the END CONVERSATION WITH CLEANUP statement(s), in order to clear it, and then I can re-enable it. (BUT THAT'S NOT WHAT I WANT!):
SELECT name, is_receive_enabled, is_enqueue_enabled FROM sys.service_queues; SELECT 'END CONVERSATION ''' + CAST([conversation_handle] AS varchar(100)) + ''' WITH CLEANUP;' FROM [dbo].[TestPoisonQueue]; END CONVERSATION '...' WITH CLEANUP; ALTER QUEUE [dbo].[TestPoisonQueue] WITH STATUS = ON; SELECT name, is_receive_enabled, is_enqueue_enabled FROM sys.service_queues;