Service Broker Internal Activation Poisoning - Where? - sql-server

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.

Related

Service Broker messages inside a transaction

I am writing a stored procedure that would perform the following operations:
BEGIN a transaction
perform some tasks
use Service Broker to kick-off a background process
wait for a response message (with job status) from Service Broker
COMMIT or ROLLBACK transaction, depending on the response message
The issue is that Service Broker communication is not working inside a TRANSACTION:
the message queue has activation enabled, but the associated stored procedure is not executed (PRINT statements in the stored procedure are not written to ERRORLOG file)
RECEIVE command times out
Here's an extract of my code:
-- Comment out the following line to make everything work
begin tran t1
DECLARE #Update_Msg XML([sb].[Service_Broker_xxx_Schemas]) = '
<Request xmlns="xxx">
<Table xmlns="xxx">
<Fields>
xxx
</Fields>
</Table>
<Requested_By>xxx</Requested_By>
</Request>'
DECLARE #conversation_handle UNIQUEIDENTIFIER
,#message_body varbinary(max)
,#message_type_name nvarchar(256)
,#timestamp datetime2
BEGIN DIALOG CONVERSATION #conversation_handle
FROM SERVICE [xxx_Initiating_Service]
TO SERVICE 'xxx_Target_Service'
ON CONTRACT xxx_Contract
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #conversation_handle
MESSAGE TYPE [xxx_Command](#Update_Msg);
select * from sys.transmission_queue with(nolock)
--PRINT #conversation_handle
WAITFOR (
-- just handle one message at a time
RECEIVE TOP(1) #conversation_handle = conversation_handle -- the identifier of the dialog this message was received on
,#message_type_name = message_type_name
,#message_body=message_body -- the message contents
,#timestamp = GETDATE()
FROM [sb].[xxx_Initiator_Queue]
WHERE conversation_handle = #conversation_handle
), TIMEOUT 1000 -- if the queue is empty for one second, give UPDATE and go away
IF ##ROWCOUNT > 0
BEGIN
SELECT ##ROWCOUNT, #message_type_name, CONVERT(XML, #message_body)
END CONVERSATION #conversation_handle;
END
ELSE
BEGIN
PRINT 'Did not receive any response from Service Broker.'
END
-- Comment out the following line to make everything work
commit tran t1
What is the correct way to implement Service Broker messaging inside a transaction?
Sending messages via Service Broker is transactional. That is, if you do begin tran; send;, the message isn't actually sent until you commit.

Untrusted security context in signed activated procedure

I have an activated procedure for a service broker queue that queries a linked server. I have signed the procedure using the method found here. However, I continuously see the following message in the sql server logs:
The activated proc '[dbo].[TestProc]' running on queue 'DBName.dbo.TestReceiveQueue' output the following: 'Access to the remote server is denied because the current security context is not trusted.'
The strange thing is that I have several different activated procedures in the same database, signed by the same certificate, that also do linked server queries, and work fine. For some reason, this procedure refuses to.
Here's some code to (mostly) reproduce the problem. I've created the certificate and associated login already.
CREATE PROCEDURE TestProc
WITH EXECUTE AS OWNER
AS
BEGIN
SET NOCOUNT ON;
DECLARE #convHandle UNIQUEIDENTIFIER;
DECLARE #msgTypeName SYSNAME;
DECLARE #status TINYINT;
DECLARE #srvName NVARCHAR(512);
DECLARE #srvConName NVARCHAR(256);
DECLARE #msgTypeValidation AS NCHAR(2);
DECLARE #msgBody NVARCHAR(256);
DECLARE #cmd AS NVARCHAR(50);
RECEIVE TOP(1)
#convHandle = conversation_handle,
#msgTypeName = message_type_name,
#status = status,
#srvName = service_name,
#srvConName = service_contract_name,
#msgTypeValidation = validation,
#msgBody = CAST(message_body AS NVARCHAR(256))
FROM TestReceiveQueue;
--SELECT #convHandle, #msgBody
IF (##ROWCOUNT != 0)
BEGIN
SELECT * FROM openquery(LINKEDSERVERNAME, 'SELECT * FROM LINKEDSERVERDB.SCHEMA.TABLE')
END CONVERSATION #convHandle
END
END
GO
CREATE MESSAGE TYPE [TestMessageType] VALIDATION = NONE;
CREATE CONTRACT TestContract (TestMessageType SENT BY INITIATOR)
CREATE QUEUE [dbo].[TestReceiveQueue] With STATUS = ON, RETENTION = OFF, ACTIVATION (STATUS = ON, PROCEDURE_NAME = [dbo].[TestProc], MAX_QUEUE_READERS = 1, EXECUTE AS OWNER ), POISON_MESSAGE_HANDLING (STATUS = OFF) ON [PRIMARY]
CREATE QUEUE [dbo].[TestSendQueue] WITH STATUS = ON, RETENTION = OFF, POISON_MESSAGE_HANDLING (STATUS = OFF) ON [PRIMARY]
CREATE SERVICE [TestReceiveService] ON QUEUE [dbo].[TestReceiveQueue] (TestContract)
CREATE SERVICE [TestSendService] ON QUEUE [dbo].[TestSendQueue] (TestContract)
Drop Procedure TestProc
ADD SIGNATURE TO OBJECT::[TestProc]
BY CERTIFICATE [ServiceBrokerProcsCert]
WITH PASSWORD = 'PASSWORDHERE'
GO
Is there any way I can debug this further, to figure out why I'm getting this error? I've tried ssbdiagnose on the conversation and there aren't any configuration errors. I also tried logging the CURRENT_USER inside the activated sproc which came back as dbo.
When I mark the database as trustworthy, it works, of course (but that's what I'm trying to avoid).
If database is TRUSTWORTHY OFF procedure will run only in context of signing user, not its OWNER as you expect.
Assign linked server privileges to user assiociated with ServiceBrokerProcsCert, it is right user in which context signed activation procedure runs.

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).

SQL Server 2008 insert trigger not firing

I have an INSERT trigger on a table that simply executes a job.
Example:
CREATE TABLE test
(
RunDate smalldatetime
)
CREATE TRIGGER StartJob ON test
AFTER INSERT
AS
EXEC msdb.dbo.sp_start_job 'TestJob'
When I insert a record to this table, the job is fired of without any issue. There are a few people, however, that have lower permissions than I do (db_datareader/db_datawriter on the database only); they are able to insert a record to the table, but the trigger does not fire.
I am a SQL Server novice and I was under the impression that users did not need elevated permissions to fire off a trigger (I thought that was one of the big benefits!). Is this a permission issue at the trigger level, or at the job level? What can I do to get around this limitation?
The trigger will execute in the context of the caller, which may or may not have the permissions to access msdb. That seems to be your problem. There are a few ways to extend these permissions using Execute As; they are greatly detailed in this link
Use impersonation within trigger:
CREATE TRIGGER StartJob ON test
with execute as owner
AFTER INSERT
AS
EXEC msdb.dbo.sp_start_job 'TestJob'
And set database to trustworthy (or read about signing in above link):
alter database TestDB set trustworthy on
Another way to go (depending on what operations the agent job performs) would be to leverage a Service Broker queue to handle the stored procedure activation. Your users' context would simply call to Send On the queue while, in an asynchronous process SvcBroker would activate a stored procedure which executed in context of higher elevated user. I would opt for this solution rather than relying on a trigger calling an agent job.
I wanted to test the call to Service Broker, so I wrote this simple test example. Instead of calling an SSIS package I simply send an email, but it is very similar to your situation. Notice I use SET TRUSTWORTHY ON at the top of the script. Please read about the implications of this setting.
To run this sample you will need to substitute your email profile info below, <your_email_address_here>, etc.
use Master;
go
if exists(select * from sys.databases where name = 'TestDB')
drop database TestDB;
create database TestDB;
go
alter database TestDB set ENABLE_BROKER;
go
alter database TestDB set TRUSTWORTHY ON;
use TestDB;
go
------------------------------------------------------------------------------------
-- create procedure that will be called by svc broker
------------------------------------------------------------------------------------
create procedure dbo.usp_SSISCaller
as
set nocount on;
declare #dlgid uniqueidentifier;
begin try
-- * figure out how to start SSIS package from here
-- for now, just send an email to illustrate the async callback
;receive top(1)
#dlgid = conversation_handle
from SSISCallerQueue;
if ##rowcount = 0
begin
return;
end
end conversation #dlgid;
exec msdb.dbo.sp_send_dbmail
#profile_name = '<your_profile_here>',
#importance = 'NORMAL',
#sensitivity = 'NORMAL',
#recipients = '<your_email_address_here>',
#copy_recipients = '',
#blind_copy_recipients = '',
#subject = 'test from ssis caller',
#body = 'testing',
#body_format = 'TEXT';
return 0;
end try
begin catch
declare #msg varchar(max);
select #msg = error_message();
raiserror(#msg, 16, 1);
return -1;
end catch;
go
------------------------------------------------------------------------------------
-- setup svcbroker objects
------------------------------------------------------------------------------------
create contract [//SSISCallerContract]
([http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer] sent by initiator)
create queue SSISCallerQueue
with status = on,
activation (
procedure_name = usp_SSISCaller,
max_queue_readers = 1,
execute as 'dbo' );
create service [//SSISCallerService]
authorization dbo
on queue SSISCallerQueue ([//SSISCallerContract]);
go
return;
-- usage
/*
-- put a row into the queue to trigger the call to usp_SSISCaller
begin transaction;
declare #dlgId uniqueidentifier;
begin dialog conversation #dlgId
from service [//SSISCallerService]
to service '//SSISCallerService',
'CURRENT DATABASE'
on contract [//SSISCallerContract]
with encryption = off;
begin conversation timer (#dlgId)
TIMEOUT = 5; -- seconds
commit transaction;
*/
It would be permissions at the job level. You can possibly assign those users the SQLAgentReaderRole in MSDB to be able to start a job, considering that they would be added to a group that owned the job. If they are not in a group which owns the job, it gets more difficult.

Service Broker : Sys.Conversation_endpoints filling up with CO/CONVERSING messages when using With Cleanup

We recently identified a problem with one of our databases where as a result of a 'fire & forget' setup (i.e: conversations being closed immediately after sending), our sys.conversation_endpoints table was filling up with DI/DISCONNECTED_INBOUND messages. This eventually spilled over into the tempDB, causing it to grow enormously and eat up precious disk space. We eventually resolved this issue by commenting out the line
END CONVERSATION #handle WITH CLEANUP
in our sending SP and closing the conversations in our receiving SP using the same code,
END CONVERSATION #handle WITH CLEANUP
However, we now have a new issue. Since moving servers (and migrating from SQL Server 2005 to SQL Server 2008) we've recently discovered that sys.conversation_endpoints is now filling up with CO/CONVERSING messages, indicating that the conversations are not being closed. The receiving SP is closing them, or at least is running the command to do so, so I don't understand where these messages are coming from.
I've tried going back to ending the conversation at the point of send, but it has no effect. Is it wrong to end conversations on the receiving end using WITH CLEANUP? Or is there some other problem?
This post on techtarget seems to suggest its a bug, and that running a job to cleanup the leftovers is the only solution...
UPDATE:
Pawel pointed out below that I should be avoiding the Fire & Forget Pattern, and I've added an activated SP to the initiator queue to end any conversations. However, sys.conversation_endpoints is STILL filling up, this time with CD/CLOSED messages. Here's the structure of my queues
Send_SP:
DECLARE #h UNIQUEIDENTIFIER
BEGIN DIALOG CONVERSATION #h
FROM SERVICE 'InitiatorQueue' TO SERVICE 'TargetQueue'
ON CONTRACT 'MyContract' WITH ENCRYPTION = OFF;
SEND ON CONVERSATION #h MESSAGE TYPE 'MyMessage' (#msg)
Receive_SP (Activated SP on TargetQueue)
DECLARE #type SYSNAME, #h UNIQUEIDENTIFIER, #msg XML;
DECLARE #target TABLE (
[message_type_name] SYSNAME,
[message_body] VARBINARY(MAX),
[conversation_handle] UNIQUEIDENTIFIER
)
WHILE(1=1)
BEGIN TRANSACTION
WAITFOR(RECEIVE TOP (1000)
[message_type_name],[message_body],[conversation_handle]
FROM TargetQueue INTO #target), TIMEOUT 2000
IF(##rowcount!=0)
BEGIN
WHILE((SELECT count(*) FROM #target) > 0)
BEGIN
SELECT TOP (1) #type = [message_type_name],
#msg = [message_body],
#h = [conversation_handle] FROM #target;
// Handle Message Here
END CONVERSATION #h;
DELETE TOP (1) FROM #target;
END
END
COMMIT TRANSACTION;
End_SP (Activated SP on InitiatorQueue)
DECLARE #type SYSNAME, #h UNIQUEIDENTIFIER, #msg XML;
DECLARE #init TABLE (
[message_type_name] SYSNAME,
[message_body] VARBINARY(MAX),
[conversation_handle] UNIQUEIDENTIFIER
)
WHILE(1=1)
BEGIN TRANSACTION
WAITFOR(RECEIVE TOP (1000)
[message_type_name],[message_body],[conversation_handle]
FROM InitiatorQueue INTO #init), TIMEOUT 2000
IF(##rowcount!=0)
BEGIN
WHILE((SELECT count(*) FROM #init) > 0)
BEGIN
SELECT TOP (1) #type = [message_type_name],
#msg = [message_body],
#h = [conversation_handle] FROM #init;
END CONVERSATION #h;
DELETE TOP (1) FROM #init;
END
END
COMMIT TRANSACTION;
Using the fire-and-forget pattern will inevitably lead to this and other types of issues. Additionally it will make any hypothetical errors go unnoticed. Is there any reason why you can't change the message exchange pattern so that the target issues END CONVERSATION (without cleanup!) once it receives a message and then the initiator only calls END CONVERSATION (again, without cleanup) upon receiving end conversation message from the target?

Resources