Service broker: internal activation on two instances through TRANSPORT route - sql-server

There are servers A and B with the SQL Server instances. There are created services with a route through TRANSPORT. Messages are successfully received. I'm trying to create an internal activation on B, but the queue stops. What am I doing wrong? Fixed. For each message sent from A to B is returned 5 copies.
SERVER A
USE master
GO
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'PAROLPAROL123'
CREATE CERTIFICATE [GA_SERVER]
WITH SUBJECT = 'GA_SERVER';
GO
CREATE ENDPOINT BROKER
STATE=STARTED
AS TCP (LISTENER_PORT = 4022)
FOR SERVICE_BROKER
(
AUTHENTICATION = CERTIFICATE [GA_SERVER]
);
GRANT CONNECT ON ENDPOINT::BROKER TO [PUBLIC];
GO
CREATE DATABASE DB_A
GO
ALTER DATABASE DB_A
SET ENABLE_BROKER
WITH ROLLBACK IMMEDIATE;
GO
USE DB_A;
CREATE MESSAGE TYPE RequestMT
validation=NONE;
CREATE MESSAGE TYPE ResponseMT
validation=NONE;
CREATE CONTRACT GAContract
(
RequestMT
SENT BY INITIATOR,
ResponseMT
SENT BY TARGET
);
CREATE QUEUE GAQueue
WITH status = ON;
CREATE SERVICE [tcp://10.10.100.56:4022/GAservice]
ON QUEUE GAQueue (GAContract)
GO
GRANT SEND ON SERVICE::[tcp://10.10.100.56:4022/GAservice] TO [PUBLIC]
CREATE ROUTE transport WITH ADDRESS = 'TRANSPORT';
GO
CREATE TABLE [dbo.t1](
[mes] [nvarchar](max) NULL
) ON [PRIMARY];
GO
CREATE PROCEDURE ActivProcA
AS
BEGIN
SET NOCOUNT ON
DECLARE #RecvReqDlgHandle UNIQUEIDENTIFIER;
DECLARE #RecvReqMsg NVARCHAR(1000);
DECLARE #RecvReqMsgName sysname;
BEGIN TRANSACTION;
WHILE (1=1)
BEGIN
WAITFOR
( RECEIVE TOP(1)
#RecvReqDlgHandle = conversation_handle,
#RecvReqMsg = message_body,
#RecvReqMsgName = message_type_name
FROM GAQueue
), TIMEOUT 5000;
IF (##ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION;
-- END CONVERSATION #RecvReqDlgHandle;
BREAK;
END
IF #RecvReqMsgName =
N'ResponseMT'
BEGIN
INSERT INTO [dbo.t1] (mes) VALUES (N'back to A from B ('+ CAST(CURRENT_TIMESTAMP AS NVARCHAR)+N'):'+#RecvReqMsg);
END
ELSE
END CONVERSATION #RecvReqDlgHandle;
COMMIT TRANSACTION;
SET NOCOUNT OFF;
END;
END;
GO
ALTER QUEUE GAQueue
WITH
STATUS = ON,
ACTIVATION
( STATUS = ON,
PROCEDURE_NAME = ActivProcA,
MAX_QUEUE_READERS = 10,
EXECUTE AS SELF
);
GO
SERVER B
USE MASTER
GO
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'PAROLPAROL123'
CREATE CERTIFICATE [GOS_SERVER]
WITH SUBJECT = 'GOS_SERVER';
GO
CREATE ENDPOINT BROKER
STATE=STARTED
AS TCP (LISTENER_PORT = 4022)
FOR SERVICE_BROKER
(
AUTHENTICATION = CERTIFICATE [GOS_SERVER],
);
GRANT CONNECT ON ENDPOINT::BROKER TO [PUBLIC];
GO
CREATE DATABASE DB_B_1
GO
ALTER DATABASE DB_B_1
SET ENABLE_BROKER
WITH ROLLBACK IMMEDIATE;
GO
USE DB_B_1;
CREATE MESSAGE TYPE RequestMT
validation=NONE;
CREATE MESSAGE TYPE ResponseMT
validation=NONE;
CREATE CONTRACT GAContract
(
RequestMT
SENT BY INITIATOR,
ResponseMT
SENT BY TARGET
);
CREATE QUEUE GAQueue
WITH status = OFF;
GO
CREATE SERVICE [tcp://10.10.100.78:4022/GOS_DB_B_1_service]
ON QUEUE GAQueue (GAContract)
GO
GRANT SEND ON SERVICE::[tcp://10.10.100.78:4022/GOS_DB_B_1_service] TO [PUBLIC]
CREATE ROUTE transport WITH ADDRESS = 'TRANSPORT';
GO
CREATE TABLE [dbo.t1](
[mes] [nvarchar](max) NULL
) ON [PRIMARY];
GO
CREATE PROCEDURE ActivProcB
AS
BEGIN
SET NOCOUNT ON;
DECLARE #RecvReqDlgHandle UNIQUEIDENTIFIER;
DECLARE #RecvReqMsg NVARCHAR(100);
DECLARE #RecvReqMsgName sysname;
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
WAITFOR
( RECEIVE TOP(1)
#RecvReqDlgHandle = conversation_handle,
#RecvReqMsg = message_body,
#RecvReqMsgName = message_type_name
FROM GAQueue
), TIMEOUT 5000;
IF (##ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION;
BREAK;
END
IF #RecvReqMsgName =
N'RequestMT'
BEGIN
INSERT INTO [dbo.t1] (mes) VALUES (N'('+ CAST(CURRENT_TIMESTAMP AS NVARCHAR)+N'):'+#RecvReqMsg);
DECLARE #Msg NVARCHAR(100);
DECLARE #I INT;
SET #I = 1;
WHILE #I<6
BEGIN
SET #Msg = N'<MsgFromB1>'+CAST (#I AS NVARCHAR(1))+N' ('+CAST(CURRENT_TIMESTAMP AS NVARCHAR)+N')</MsgFromB1>';
SEND ON CONVERSATION #RecvReqDlgHandle
MESSAGE TYPE
ResponseMT
(#Msg);
SET #I = #I+1;
END
END;
COMMIT TRANSACTION;
END
SET NOCOUNT OFF;
END;
GO
ALTER QUEUE GAQueue
WITH
STATUS = ON,
ACTIVATION
( STATUS = ON,
PROCEDURE_NAME = ActivProcB,
MAX_QUEUE_READERS = 10,
EXECUTE AS SELF
);
GO
SERVER A
USE DB_A
DECLARE #ConversationHandle UNIQUEIDENTIFIER;
DECLARE #RequestMsg NVARCHAR(4000);
BEGIN TRANSACTION
BEGIN DIALOG #ConversationHandle
FROM SERVICE [tcp://10.10.100.56:4022/GAservice]
TO SERVICE 'tcp://10.10.100.78:4022/GOS_DB_B_1_service'
ON CONTRACT GAContract
WITH ENCRYPTION = OFF;
SELECT #RequestMsg =
N'<RequestMsg>test1</RequestMsg>';
SEND
ON CONVERSATION #ConversationHandle
MESSAGE TYPE RequestMT
(#RequestMsg);
COMMIT TRANSACTION;
GO

I'm trying to create an internal activation on B, but the queue stops
That's most likely poison message detection kicking in. It means your activated procedure is raising an exception and cause a rollback. When this happens the output of the procedure (including error messages) is logged into the ERRORLOG. That's where you need to look.
You can also attempt to troubleshoot the procedure by disabling activation and invoking the procedure manually from a query window. This way you'll see directly any error messages/exceptions. To execute under the same context as the activated procedure, issue an EXECUTE AS USER = 'dbo' before invoking the procedure.
See Troubleshooting Activation Stored Procedures.

Related

Fixing ACCESS_METHODS_HOBT_VIRTUAL_ROOT and PAGELATCH_SH waits with Service Broker SEND ON CONVERSATION?

We are having issues with our service broker implementation as volume has scaled up. The code predates me, I have some experience with service broker (specifically around Event Notifications), but not a ton.
Currently, we have a SQL Server 2016 instance with 30 threads picking up messages one-at-a-time (I know, I know) from a queue and doing work upon it, but the Stored Procedure that inserts into the queue is slow with a ton of waits, causing backups in the systems that feed it. Specifically, we see a bunch of PAGELATCH_EX/PAGELATCH_SH in the filegroup that holds the service broker queues that tends to be SEND ON CONVERSATION, but also a ton of ACCESS_METHODS_HOBT_VIRTUAL_ROOT, s well as BROKER_TRANSMISSION_TABLE waits on END CONVERSATION.
First Stored Procedure, the one our app calls to deliver messages for processing.:
CREATE PROCEDURE [etl].[catch_or_release_queue_bundle] ( #xml_bundle XML )
AS
SET NOCOUNT ON
BEGIN
--Initiator Service, Target Service and the Contract
BEGIN DIALOG #InitDlgHandle
FROM SERVICE [//Bundle/Raw/SBInitiatorService]
TO SERVICE '//Bundle/Raw/SBTargetService'
ON CONTRACT [//Bundle/Raw/SBContract]
WITH ENCRYPTION=OFF, LIFETIME = 7200;
--Send the Message
SEND ON CONVERSATION #InitDlgHandle
MESSAGE TYPE [//Bundle/Raw/RequestMessage] (#xml_bundle);
END;
The activated stored procedure that acts upon it:
CREATE PROCEDURE [dbo].[TargetActivProc]
AS
set nocount on
DECLARE #RecvReqDlgHandle UNIQUEIDENTIFIER;
DECLARE #RecvReqMsg xml;
DECLARE #RecvReqMsgName sysname;
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
--changed from a WAITFOR (RECEIVE top(1) )TIMEOUT 5000 to use a waitfor instead
RECEIVE TOP(1)
#RecvReqDlgHandle = conversation_handle,
#RecvReqMsg = message_body,
#RecvReqMsgName = message_type_name
FROM sbTargetQueue
IF (##ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION;
WAITFOR DELAY '00:00:01' --mdb 20221004 22:30 adding this so that it waits 1 second.
BREAK;
END
-- Disabling this portion. This should be the production procedure call
EXEC message_evaluator #RecvReqMsg
IF #RecvReqMsgName =
N'//Bundle/Raw/RequestMessage'
BEGIN
DECLARE #ReplyMsg NVARCHAR(100);
SELECT #ReplyMsg =
N'<ReplyMsg>Message for Initiator service.</ReplyMsg>';
SEND ON CONVERSATION #RecvReqDlgHandle
MESSAGE TYPE
[//Bundle/Raw/ReplyMessage]
(#ReplyMsg);
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
END CONVERSATION #RecvReqDlgHandle;
END
COMMIT TRANSACTION;
END
And the activated stored procedure for the second queue. I honestly have no idea what the purpose of this one is. My only idea is that there's a THIRD queue that was used at one point but no longer receives messages, and this is some sort of rollup SP to close all conversations? There's enough with Service Broker that they added it for a reason, I just don't know why.
CREATE PROCEDURE [dbo].[InititatorActivProc]
AS
SET NOCOUNT on
DECLARE #dh UNIQUEIDENTIFIER;
DECLARE #message_type SYSNAME;
DECLARE #message_body NVARCHAR(4000);
WHILE (1=1)
BEGIN
BEGIN TRANSACTION
WAITFOR (
RECEIVE top(1) #dh = [conversation_handle],
#message_type = [message_type_name],
#message_body = CAST([message_body] AS NVARCHAR(4000))
FROM [sbInitiatorQueue]), TIMEOUT 5000;
IF (##ROWCOUNT = 0)
BEGIN
ROLLBACK TRANSACTION;
BREAK;
END
IF #message_type = '//Bundle/Raw/ReplyMessage' --
BEGIN
END CONVERSATION #dh
END
ELSE
IF #message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
BEGIN
RAISERROR ('Received error %s from service [Target]', 10, 1, #message_body) WITH LOG;
END CONVERSATION #dh
END
COMMIT TRANSACTION;
END

SQL Server service broker end conversation creates new entries on initiator queue

We are using SQL Service Broker, using two services for initiator and target, then we have a C# service listening and receiving messages from queue.
On SQL Side the setup is this:
CREATE MESSAGE TYPE [http://mycompany.com/MyEventRequestMessage] VALIDATION = WELL_FORMED_XML
GO
CREATE MESSAGE TYPE [http://mycompany.com/MyEventResponseMessage] VALIDATION = WELL_FORMED_XML
GO
CREATE CONTRACT [http://mycompany.com/MyMessageContract] ([http://mycompany.com/MyEventRequestMessage] SENT BY INITIATOR,
[http://mycompany.com/MyEventResponseMessage] SENT BY TARGET)
GO
CREATE QUEUE [dbo].[MyEventInitiatorQueue] WITH STATUS = ON , RETENTION = OFF , POISON_MESSAGE_HANDLING (STATUS = ON) ON [PRIMARY]
GO
CREATE QUEUE [dbo].[MyEventTargetQueue] WITH STATUS = ON , RETENTION = OFF , POISON_MESSAGE_HANDLING (STATUS = ON) ON [PRIMARY]
GO
CREATE SERVICE [MyEventInitiatorService] ON QUEUE [dbo].[MyEventInitiatorQueue] ([http://mycompany.com/MyMessageContract])
GO
CREATE SERVICE [MyEventTargetService] ON QUEUE [dbo].[MyEventTargetQueue] ([http://mycompany.com/MyMessageContract])
GO
Then if I insert a message:
DECLARE #MessageType NVARCHAR(128) = 'http://mycompany.com/MyEventRequestMessage'
DECLARE #MessageBody NVARCHAR(128) = 'loremipsum'
DECLARE #FromService NVARCHAR(128) = 'MyEventInitiatorService'
DECLARE #ToService NVARCHAR(128) = 'MyEventTargetService'
DECLARE #Contract NVARCHAR(128) = 'http://mycompany.com/MyMessageContract'
DECLARE #conversation_handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION #conversation_handle
FROM SERVICE #FromService
TO SERVICE #ToService
ON CONTRACT #Contract
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #conversation_handle
MESSAGE TYPE #MessageType(#MessageBody);
It arrives the target queue as expected.
Then on the service we receive the message rom target queue and perform END CONVERSATION.
The message disapears from target queue, but then a new message is inserted on initiator queue, of type http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog.
Even though our service is prepared to end these conversations, it is listening on the target queue.
How can we clear these end dialog messages? Should we listen also on the initiator queue these kind of messages, in order to end the conversation, or should we do anything else?
We are afraid if this causes any side efect on the database or on the server if no action is made.
Thanks.
How can we clear these end dialog messages? Should we listen also on
the initiator queue these kind of messages, in order to end the
conversation, or should we do anything else?
Yes, you also need a service (technically any SQL process) that listens for initiator queue messages to end that side of the conversation when an EndDialog or Error is message received. Consider logging any unexpected message types (i.e. other than EndDialog).
You'll eventually fill up data database with unneeded initiator queue messages if no action is taken,
I think we found a solution that matches Dan's comment:
CREATE PROCEDURE spHandleInitiatorQueueMessages
AS
DECLARE #dh UNIQUEIDENTIFIER;
DECLARE #message_type SYSNAME;
DECLARE #message_body NVARCHAR(4000);
BEGIN TRANSACTION;
WAITFOR (
RECEIVE #dh = [conversation_handle],
#message_type = [message_type_name],
#message_body = CAST([message_body] AS NVARCHAR(4000))
FROM MyInitiatorQueue), TIMEOUT 1000;
WHILE #dh IS NOT NULL
BEGIN
IF #message_type = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
BEGIN
RAISERROR (N'Received error %s from service [Target]', 10, 1, #message_body) WITH LOG;
END
END CONVERSATION #dh;
COMMIT;
SELECT #dh = NULL;
BEGIN TRANSACTION;
WAITFOR (
RECEIVE #dh = [conversation_handle],
#message_type = [message_type_name],
#message_body = CAST([message_body] AS NVARCHAR(4000))
FROM MyInitiatorQueue), TIMEOUT 1000;
END
COMMIT;
GO
ALTER QUEUE MyInitiatorQueue
WITH ACTIVATION (
STATUS = ON,
MAX_QUEUE_READERS = 1,
PROCEDURE_NAME = spHandleInitiatorQueueMessages,
EXECUTE AS OWNER);
GO
Messages began to actually end

Service Broker Internal Activation Poisoning - Where?

I am experiencing poison messages and I am not sure why.
My broker setup looks like this:
CREATE MESSAGE TYPE
[//DB/Schema/RequestMessage]
VALIDATION = WELL_FORMED_XML;
CREATE MESSAGE TYPE
[//DB/Schema/ReplyMessage]
VALIDATION = WELL_FORMED_XML;
CREATE CONTRACT [//DB/Schema/Contract](
[//DB/Schema/RequestMessage] SENT BY INITIATOR,
[//DB/Schema/ReplyMessage] SENT BY TARGET
)
CREATE QUEUE Schema.TargetQueue
CREATE SERVICE [//DB/Schema/TargetService]
ON QUEUE Schema.TargetQueue (
[//DB/Schema/Method3Contract]
)
CREATE QUEUE Schema.InitiatorQueue
CREATE SERVICE [//DB/Schema/InitiatorService]
ON QUEUE Schema.InitiatorQueue
Then I have my internal activation procedure:
CREATE PROCEDURE Schema.Import
AS
DECLARE #RequestHandle UNIQUEIDENTIFIER;
DECLARE #RequestMessage VARCHAR(8);
DECLARE #RequestMessageName sysname;
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
WAITFOR (
RECEIVE TOP(1)
#RequestHandle = conversation_handle,
#RequestMessage = message_body,
#RequestMessageName = message_type_name
FROM
Schema.TargetQueue
), TIMEOUT 5000;
IF (##ROWCOUNT = 0)
BEGIN
COMMIT TRANSACTION;
BREAK;
END
EXEC Schema.ImportStep1 #ID = #RequestMessage;
--EXEC Schema.ImportStep2 #ID = #RequestMessage;
END CONVERSATION #RequestHandle;
COMMIT TRANSACTION;
END
My activation is enabled by:
ALTER QUEUE Schema.TargetQueue
WITH
STATUS = ON,
ACTIVATION
( STATUS = ON,
PROCEDURE_NAME = Schema.Import,
MAX_QUEUE_READERS = 10,
EXECUTE AS SELF
)
I initiate this process with this stored procedure
CREATE PROCEDURE Schema.ImportStart
AS
BEGIN
DECLARE #ID VARCHAR(8);
DECLARE Cursor CURSOR FOR
SELECT ID FROM OtherDatabase.OtherSchema.ImportTable
EXCEPT
SELECT ID FROM Table
OPEN Cursor;
FETCH NEXT FROM Cursor INTO #ID;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #InitiateHandle UNIQUEIDENTIFIER;
DECLARE #RequestMessage VARCHAR(8);
BEGIN TRANSACTION;
BEGIN DIALOG
#InitiateHandle
FROM SERVICE
[//DB/Schema/InitiatorService]
TO SERVICE
N'//DB/Schema/TargetService'
ON CONTRACT
[//DB/Schema/Contract]
WITH
ENCRYPTION = OFF;
SELECT #RequestMessage = #ID;
SEND ON CONVERSATION
#InitiateHandle
MESSAGE TYPE
[//DB/Schema/RequestMessage]
(#RequestMessage);
COMMIT TRANSACTION;
FETCH NEXT FROM Cursor INTO #ID;
END
CLOSE Cursor;
DEALLOCATE Cursor;
END
So how this should work is:
I execute ImportStart
A message for each ID gets generated
Internal activation makes Import steps execute
Instead, I get poison messaging and the queue becomes disabled.
If however,
I I set Schema.TargetQue Activation to OFF
EXEC schema.ImportStart
EXEC schema.Import manually
It works fine.
Any insights anyone?
Well:
Your message types are defined as well_formed_xml, yet you send varchar(8) as a message body. Does it really work?
You use [//DB/Schema/Method3Contract] for the target queue, but do not define it. A misspelling, most likely.
You specify EXECUTE AS SELF in the queue activation. BOL says some mystical thing about this case:
SELF
Specifies that the stored procedure executes as the current user. (The database principal executing this ALTER QUEUE statement.)
I'm not really sure I understand the quoted statement, because it apparently contradicts with your experience. If it would be your user account, everything should have been fine, because you seem to have all permissions necessary to do the job.
So, just in case - who is the owner of the Schema schema? What permissions does this principal possess? And, if it's not you, who executes the alter queue statement (and why)?
Without access to logs, it's significantly more difficult to diagnose the problem, but I would start with creating a new user account with permissions identical to yours, setting it the owner of the Schema schema and then slowly working it down, revoking unnecessary permissions until it breaks. Assuming, of course, it will work at all.

SQL Service Broker

I have created a service broker that stores id's in the queue table. But the problem is when i want to get the id back in the stored procedure it's now formatted as xml. Because the service broker message is XML.
How can i just get the last Id each time because the trigger is fired after an update of a row.
Below my code =>
/****** Object: Trigger [dba].[TriggerCall] Script Date: 6/16/2015 2:55:57 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dba].[TriggerCall] ON [dba].[CallID] FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MessageBody XML
DECLARE #ID varchar(50)
-- Insert statements for trigger here
--get relevant information from inserted/deleted and convert to xml message
SET #MessageBody = (SELECT Id FROM inserted
FOR XML AUTO)
If (#MessageBody IS NOT NULL)
BEGIN
DECLARE #Handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE [TestServiceInitiator]
TO SERVICE 'TestServiceTarget'
ON CONTRACT [TestContract]
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #Handle
MESSAGE TYPE [TestMessage](#MessageBody);
END
END
/****** Object: StoredProcedure [dbo].[usp_GetCall] Script Date: 6/16/2015 2:44:27 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[usp_GetCall]
AS
BEGIN
DECLARE #message_type varchar(100)
DECLARE #dialog uniqueidentifier, #message_body XML;
Declare #Object as Int;
Declare #URL as varchar(255)
Declare #ResponseText as Varchar(8000);
Declare #ID as Varchar(38);
WHILE (1 = 1)
BEGIN -- Receive the next available message from the queue
WAITFOR (
RECEIVE TOP(1) #message_type = message_type_name,
#message_body = CAST(message_body AS XML),
#dialog = conversation_handle
FROM dbo.TestQueue ), TIMEOUT 500 if (##ROWCOUNT = 0 OR #message_body IS NULL)
BEGIN
BREAK
END
ELSE
BEGIN
INSERT INTO [dbo].[testtabel]
([id]
,[callid],
[test])
VALUES
('111', '111', #message_body)
END
END CONVERSATION #dialog
END
END
If I understand your question correctly, you're just looking to rehydrate the ID into an int. If that's the case, the following should do:
use tempdb;
create table inserted (id int);
insert into inserted values (1);
declare #message_body xml;
set #message_body= (select * from inserted for xml auto);
select #message_body.value('(/inserted/#id)[1]', 'int');
The magic is the last line (the rest was just setup for me to test).
I'll take this opportunity to clear up a misconception that you seem to have, though. Triggers in SQL server aren't fired per row, but per batch. So, if you run an update against your table and it updates 50 rows, the trigger gets fired once and the inserted (and deleted) table will have 50 rows in it. Just something to take into account.

SQL broker queue is populating twice

I am using SQL broker to do some asynchronous tasks(In my case sending mails). But the problem I am having is the stored procedure which is run when the the value is inserted to the queue runs twice every time the XML message is passed from a Trigger to the queue.
I have a message type:
CREATE MESSAGE TYPE MailMessage
AUTHORIZATION dbo
VALIDATION = WELL_FORMED_XML
I have a contract:
CREATE CONTRACT MailContract
AUTHORIZATION dbo
(MailMessage SENT BY INITIATOR)
I have a Queue:
CREATE QUEUE dbo.MessageQueue
WITH STATUS=ON,
ACTIVATION (
PROCEDURE_NAME = MailExecuter ,
MAX_QUEUE_READERS = 1,
EXECUTE AS OWNER );
I have two services:
CREATE SERVICE MailSendActivator
AUTHORIZATION dbo
ON QUEUE dbo.MessageQueue (MailContract) ; // I have removed this the contract to make it a initiator but it did not worked out
-- Create target Service
CREATE SERVICE MailSendExec
AUTHORIZATION dbo
ON QUEUE dbo.MessageQueue (MailContract);
Here is my trigger:
CREATE TRIGGER MailSendTrigOnMailQueue ON dbo.MailQueue
FOR INSERT
As
SET NOCOUNT ON;
DECLARE #MessageBody XML
DECLARE #TableId int
SET #MessageBody = (SELECT CreatedDateTime,[Subject], MailType FROM inserted
FOR XML AUTO)
If (#MessageBody IS NOT NULL)
BEGIN
DECLARE #Handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION #Handle
FROM SERVICE MailSendActivator
TO SERVICE 'MailSendExec'
ON CONTRACT MailContract
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #Handle MESSAGE TYPE MailMessage(#MessageBody);
END
I have a stored procedure:
In the stored procedure I am Inserting values to a test table whether the stored procedure is running.
Stored Procedure:
CREATE PROCEDURE dbo.MailExecuter
AS
BEGIN
DECLARE #msgBody XML
DECLARE #dlgId uniqueidentifier
Insert into TestTable(Name, Test) values('MEX','test');
WHILE (1 = 1)
BEGIN
WAITFOR ( RECEIVE TOP(1) #msgBody = CAST(message_body AS XML), #dlgId = conversation_handle FROM dbo.MessageQueue ), TIMEOUT 500
IF (##ROWCOUNT = 0 OR #msgBody IS NULL)
BEGIN
BREAK
END
ELSE
BEGIN
DECLARE #Subject nvarchar(200), #CreatedDateTime datetime, #MailType nvarchar(50)
---EXEC dbo.SendMails 1,1;
END
END CONVERSATION #dlgId
END
END
But the stored procedure is running twice and populate my test table twice. I think the problem is with the send conversation part in the trigger.
I have been struck on this for a long time. Please, Can some one help me on this
You need to add to the RECEIVEprojection list the message_type_name. You must look at what message type you received and only call the mailing routine when the message type is MailMessage. As things are, you will always get a second message, the one from your END DIALOG. You simply need to call END DIALOG again on that case, to close the sending side handle:
WAITFOR (
RECEIVE TOP(1)
#msgBody = CAST(message_body AS XML),
#dlgId = conversation_handle ,
#msgType = message_type_name
FROM dbo.MessageQueue ), TIMEOUT 500
IF (##ROWCOUNT = 0 OR #msgBody IS NULL)
BREAK
ELSE IF #msgType = N'MailMessage'
BEGIN
DECLARE #Subject nvarchar(200), #CreatedDateTime datetime, #MailType nvarchar(50)
---EXEC dbo.SendMails 1,1;
END
END CONVERSATION #dlgId;
Any particular reason you are re-inventing the wheel? This is pretty much how sp_send_dbmail already works (except is using external activation).

Resources