Error Handling in Service Broker procedures

October 31, 2007

Error handling in T-SQL traditionally has been a sort of misterious voo-doo for most developers, with it’s mixture of error severities, SET settings like <a _\_designer:dtid="844424930131970" href="http://msdn2.microsoft.com/en-us/library/ms188792.aspx">XACT\_ABORT</a>, ARITHABORT or ARITHIGNORE and the options to handle the error on the server or at the client. For a long time now the best resource I know on this subject was, and perhaps still is, Erland Sommarskog set of articles at http://www.sommarskog.se/error-handling-I.html and http://www.sommarskog.se/error-handling-II.html. But the introduction of Service Broker activated procedures adds some new issues to consider when designing your application and this post is about what these issues are these and how to best cope with them. 

@@ERROR or TRY…CATCH?

Well first things first, lets get this out of our way: check @@error or use BEGIN TRY… BEGIN CATCH? In my opinion, the advantages offered by the TRY… CATCH blocks are overwhelming and you should steer away from checking @@ERROR after each statement. Even if you may find some immediate scenario that would seem simpler using an explicit check for @@error, writing code using TRY…CATCH blocks pays of on the long run. For me the most important differentiatin factor between the two is actually the human factor: code writen with @@error checks is difficult to read and impossible to maintain. Keeping the discipline in writing code to check for @@error after each statement is tedious and errorprone. @@error checks are not ‘<a _\_designer:dtid="844424930131980" href="http://www.amazon.com/Effective-C%2B%2B-Addison-Wesley-Professional-Computing/dp/0321334876/ref=pd\_bbs\_sr\_1/002-9386102-7810448?ie=UTF8&s=books&qid=1193824559&sr=8-1">programming in the future tense</a>‘ as Scott Meyers would put it: future versions in the database engine could introduce behavior changes that raise new errors your application is not prepared to deal with. The TRY…CATCH blocks solve so many of these issues that they are clearly the winner in my book.

Service Broker Error Handling

Why is Service Broker error handling something special? Shouldn’t the normal common wisdom of writing Transact-SQL apply to Service Broker as well? Well yes, but as it turns out there is a twist to the story. Service Broker is an integrated part of the SQL Server database engine and it follows all the normal Transact-SQL language parsing, compilation and execution rules like any other database engine componet, including everything related to errors and exception handling. But the typical Service Broker application is deployed in a quite different fashion than the non-broker applications: it’s an activated procedure that runs in the server background threads. The first major difference is that there is no client application to surface the error to, so any error that happens will have to be dealt with by the procedure. This means that the very purpose of error handling in a Service Broker application is different than in an ordinary procedure: Service Broker error handling has to capture any error and somehow react to this error at the level of Service Broker semantics, namely conversations. Most times this means that the conversation that received the message that triggered the error has to be ended with an error so that the partener service is notified about this problem and, sometimes, some compensation logic has to be run to undo the effects of this conversation had so far. The partener will receive the error message and in turn run its own compensation to complete (with error) the business transaction represented by the conversation..

Lets roll a quick example. Supose we have a trivial service whose role is to insert received message content into a target table (similar to what an audit service perhaps would do). Lets go ahead and build our DDL objects:

use tempdb;

go

       

— A table for the purpose of the demo

create table [target_table] (

      [id] int not null primary key,

      [key] nvarchar(256),

      [value] nvarchar(256));

go

       

— Our initiator service

create queue [sender_queue];

create service [sender_service]

      on queue [sender_queue];

go

       

— Our target service

create queue [target_queue];

create service [target_service]

      on queue [target_queue]

      ([DEFAULT]);

go

       

<p __designer:dtid="844424930132095" style="margin: 0in 0in 0pt" class="MsoNormal">create procedure<span __designer:dtid=”844424930132100” style=”color: #000000”> [usp_target]</p> <p __designer:dtid="844424930132102" style="margin: 0in 0in 0pt" class="MsoNormal">as</p> <p __designer:dtid="844424930132105" style="margin: 0in 0in 0pt" class="MsoNormal">begin</p> <p __designer:dtid="844424930132108" style="margin: 0in 0in 0pt" class="MsoNormal">      set nocount on;</p> <p __designer:dtid="844424930132118" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @handle uniqueidentifier;</p> <p __designer:dtid="844424930132126" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @messageType sysname;</p> <p __designer:dtid="844424930132134" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @payload xml;</p> <p __designer:dtid="844424930132142" style="margin: 0in 0in 0pt" class="MsoNormal">      </p> <p __designer:dtid="844424930132146" style="margin: 0in 0in 0pt" class="MsoNormal">      begin try</p> <p __designer:dtid="844424930132153" style="margin: 0in 0in 0pt" class="MsoNormal">            begin transaction;</p> <p __designer:dtid="844424930132161" style="margin: 0in 0in 0pt" class="MsoNormal">            waitfor (receive top (1)</p> <p __designer:dtid="844424930132175" style="margin: 0in 0in 0pt" class="MsoNormal">                  @handle = <span __designer:dtid=”844424930132181” style=”color: blue”>conversation_handle,</span></p> <p __designer:dtid="844424930132184" style="margin: 0in 0in 0pt" class="MsoNormal">                  @messageType =<span __designer:dtid=”844424930132189” style=”color: #000000”> message_type_name,</span></p> <p __designer:dtid="844424930132192" style="margin: 0in 0in 0pt" class="MsoNormal">                  @payload = cast(<span __designer:dtid=”844424930132200” style=”color: #000000”>message_body as xml)</span></p> <p __designer:dtid="844424930132206" style="margin: 0in 0in 0pt" class="MsoNormal">                  from<span __designer:dtid=”844424930132210” style=”color: #000000”> [target_queue]), timeout 1000;</span></p> <p __designer:dtid="844424930132217" style="margin: 0in 0in 0pt" class="MsoNormal">            if (0 != @@rowcount)</p> <p __designer:dtid="844424930132229" style="margin: 0in 0in 0pt" class="MsoNormal">            begin</p> <p __designer:dtid="844424930132234" style="margin: 0in 0in 0pt" class="MsoNormal">                  if (@messageType = ‘DEFAULT’)</p> <p __designer:dtid="844424930132246" style="margin: 0in 0in 0pt" class="MsoNormal">                  begin</p> <p __designer:dtid="844424930132251" style="margin: 0in 0in 0pt" class="MsoNormal">                        insert into<span __designer:dtid=”844424930132257” style=”color: #000000”> [target_table] (</span></p> <p __designer:dtid="844424930132260" style="margin: 0in 0in 0pt" class="MsoNormal">                              [id], [key], [value]) </p> <p __designer:dtid="844424930132271" style="margin: 0in 0in 0pt" class="MsoNormal">                              select e.value(‘@id’, ‘int’) as [id],</p> <p __designer:dtid="844424930132289" style="margin: 0in 0in 0pt" class="MsoNormal">                                    e.value(‘@key’, ‘nvarchar(256)’) as [key],</p> <p __designer:dtid="844424930132306" style="margin: 0in 0in 0pt" class="MsoNormal">                                    e.value(‘@value’, ‘nvarchar(256)’) as [value]</p> <p __designer:dtid="844424930132322" style="margin: 0in 0in 0pt" class="MsoNormal">                                    from @payload.nodes(‘//payload/entry’) p(e);</p> <p __designer:dtid="844424930132337" style="margin: 0in 0in 0pt" class="MsoNormal">                  end;</p> <p __designer:dtid="844424930132343" style="margin: 0in 0in 0pt" class="MsoNormal">                  end conversation @handle;</p> <p __designer:dtid="844424930132352" style="margin: 0in 0in 0pt" class="MsoNormal">            end </p> <p __designer:dtid="844424930132358" style="margin: 0in 0in 0pt" class="MsoNormal">            commit;</p> <p __designer:dtid="844424930132364" style="margin: 0in 0in 0pt" class="MsoNormal">      end try</p> <p __designer:dtid="844424930132371" style="margin: 0in 0in 0pt" class="MsoNormal">      begin catch</p> <p __designer:dtid="844424930132378" style="margin: 0in 0in 0pt" class="MsoNormal">            declare @error int, @message nvarchar(4000);</p> <p __designer:dtid="844424930132391" style="margin: 0in 0in 0pt" class="MsoNormal">            select @error = <span __designer:dtid=”844424930132398” style=”color: fuchsia”>ERROR_NUMBER(), @message = <span __designer:dtid=”844424930132403” style=”color: fuchsia”>ERROR_MESSAGE</span>();</span></p> <p __designer:dtid="844424930132406" style="margin: 0in 0in 0pt" class="MsoNormal">            end conversation @handle with error = @error description = @message;</p> <p __designer:dtid="844424930132423" style="margin: 0in 0in 0pt" class="MsoNormal">            commit;</p> <p __designer:dtid="844424930132429" style="margin: 0in 0in 0pt" class="MsoNormal">      end catch;</p> <p __designer:dtid="844424930132437" style="margin: 0in 0in 0pt" class="MsoNormal">end</p> <p __designer:dtid="844424930132440" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930132444" style="margin: 0in 0in 0pt" class="MsoNormal"> </p>

       

</span><p __designer:dtid="844424930132447" style="margin: 0in 0in 0pt" class="MsoNormal"><span __designer:dtid=”844424930132448” style=”font-size: 10pt; color: green; font-family: ‘Courier New’”>— attach the procedure to the target_queue as an activated procedure</span></p> <p __designer:dtid="844424930132450" style="margin: 0in 0in 0pt" class="MsoNormal">alter queue [target_queue]</p> <p __designer:dtid="844424930132455" style="margin: 0in 0in 0pt" class="MsoNormal">      with activation (</p> <p __designer:dtid="844424930132461" style="margin: 0in 0in 0pt" class="MsoNormal">            status = on,</p> <p __designer:dtid="844424930132469" style="margin: 0in 0in 0pt" class="MsoNormal">            max_queue_readers = 1,</p> <p __designer:dtid="844424930132475" style="margin: 0in 0in 0pt" class="MsoNormal">            procedure_name = [usp_target],</p> <p __designer:dtid="844424930132481" style="margin: 0in 0in 0pt" class="MsoNormal">            execute as owner);</p> <p __designer:dtid="844424930132489" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930132492"> </p> <p __designer:dtid="844424930132493">As you can see, the error handling in the stored procedure is handled by a BEGIN TRY/BEGIN CATCH block, and the error handling consist of ending the conversation with an error, sending back the error number and message to the original sender. Of course, in a real application it would probably make sense to clearly define error numbers and messages reported by the target service, as is not really useful to send back to the sender some internal database engine codes and messages. So now lets go ahead and test our service:</p> <p __designer:dtid="844424930132494" style="margin: 0in 0in 0pt" class="MsoNormal">— send a message to the target service</p> <p __designer:dtid="844424930132497" style="margin: 0in 0in 0pt" class="MsoNormal">declare @handle uniqueidentifier, @payload xml;</p> <p __designer:dtid="844424930132505" style="margin: 0in 0in 0pt" class="MsoNormal">begin transaction;</p> <p __designer:dtid="844424930132511" style="margin: 0in 0in 0pt" class="MsoNormal">begin dialog conversation @handle</p> <p __designer:dtid="844424930132517" style="margin: 0in 0in 0pt" class="MsoNormal">      from service [sender_service]</p> <p __designer:dtid="844424930132523" style="margin: 0in 0in 0pt" class="MsoNormal">      to service <span __designer:dtid=”844424930132528” style=”color: red”>‘target_service’, ‘current database’</span></p> <p __designer:dtid="844424930132532" style="margin: 0in 0in 0pt" class="MsoNormal">      with encryption = off;</p> <p __designer:dtid="844424930132541" style="margin: 0in 0in 0pt" class="MsoNormal">        </p> <p __designer:dtid="844424930132544" style="margin: 0in 0in 0pt" class="MsoNormal">select @payload = N<entry id=”1″ key=”A” value=”B”/>;</p> <p __designer:dtid="844424930132551" style="margin: 0in 0in 0pt" class="MsoNormal">send on conversation @handle (@payload);</p> <p __designer:dtid="844424930132559" style="margin: 0in 0in 0pt" class="MsoNormal">commit;</p> <p __designer:dtid="844424930132563" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930132566"> </p> <p __designer:dtid=”844424930132567”>If we look into the target_table we find the entry we sent (1, A, B). The response that came back from the target service is sitting in the sender_queue and is a mundane EndDialog message, indicating the succesfull completion of the ‘assignment’.</p> <p __designer:dtid=”844424930132568”>Now if we simply run the very same SEND example, we’re gonna trigger an error, because the same values would violate the primary key constraint on the target_table. So go ahead and run this again:</p> <p __designer:dtid="844424930132569" style="margin: 0in 0in 0pt" class="MsoNormal">— send a message to the target service</p> <p __designer:dtid="844424930132572" style="margin: 0in 0in 0pt" class="MsoNormal">declare @handle uniqueidentifier, @payload xml;</p> <p __designer:dtid="844424930132580" style="margin: 0in 0in 0pt" class="MsoNormal">begin transaction;</p> <p __designer:dtid="844424930132586" style="margin: 0in 0in 0pt" class="MsoNormal">begin dialog conversation @handle</p> <p __designer:dtid="844424930132592" style="margin: 0in 0in 0pt" class="MsoNormal">      from service [sender_service]</p> <p __designer:dtid="844424930132598" style="margin: 0in 0in 0pt" class="MsoNormal">      to service <span __designer:dtid=”844424930132603” style=”color: red”>‘target_service’, ‘current database’</span></p> <p __designer:dtid="844424930132607" style="margin: 0in 0in 0pt" class="MsoNormal">      with encryption = off;</p> <p __designer:dtid="844424930132616" style="margin: 0in 0in 0pt" class="MsoNormal">        </p> <p __designer:dtid="844424930132619" style="margin: 0in 0in 0pt" class="MsoNormal">select @payload = N<entry id=”1″ key=”A” value=”B”/>;</p> <p __designer:dtid="844424930132626" style="margin: 0in 0in 0pt" class="MsoNormal">send on conversation @handle (@payload);</p> <p __designer:dtid="844424930132634" style="margin: 0in 0in 0pt" class="MsoNormal">commit;</p> <p __designer:dtid="844424930132638" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930132641"> </p> <p __designer:dtid=”844424930132642”>Now the sender_queue has received an error message:</p> <p __designer:dtid="844424930132643" style="margin: 0in 0in 0pt" class="MsoNormal">«/span>Error xmlns=“http://schemas.microsoft.com/SQL/ServiceBroker/Error”></p> <p __designer:dtid="844424930132649" style="margin: 0in 0in 0pt" class="MsoNormal">  «/span>Code>2627</Code></p> <p __designer:dtid="844424930132657" style="margin: 0in 0in 0pt" class="MsoNormal">  «/span>Description>Violation of PRIMARY KEY constraint <span __designer:dtid=”844424930132667” style=”color: red”>‘PK__target_table__0AD2A005’. Cannot insert duplicate key in object <span __designer:dtid=”844424930132673” style=”color: red”>‘dbo.target_table’.</Description></p> <p __designer:dtid="844424930132678" style="margin: 0in 0in 0pt" class="MsoNormal"></Error></p> <p __designer:dtid="844424930132683">So our error handling works as we expected and the service is handling error conditions fine. So is this the end of the story?</p> <h2 __designer:dtid="844424930132684">Doomed Transactions</h2> <p __designer:dtid="844424930132685"> </p> <p __designer:dtid="844424930132686">As it turns out, we’ve only skimmed the surface and we’re about to open one nasty can of worms. To proove this lets produce the one most common mistake that happens during development: a malformed payload:</p> <p __designer:dtid="844424930132687" style="margin: 0in 0in 0pt" class="MsoNormal">— send a message to the target service</p> <p __designer:dtid="844424930132690" style="margin: 0in 0in 0pt" class="MsoNormal">declare @handle uniqueidentifier, @payload nvarchar(max);</p> <p __designer:dtid="844424930132700" style="margin: 0in 0in 0pt" class="MsoNormal">begin transaction;</p> <p __designer:dtid="844424930132706" style="margin: 0in 0in 0pt" class="MsoNormal">begin dialog conversation @handle</p> <p __designer:dtid="844424930132712" style="margin: 0in 0in 0pt" class="MsoNormal">      from service [sender_service]</p> <p __designer:dtid="844424930132718" style="margin: 0in 0in 0pt" class="MsoNormal">      to service <span __designer:dtid=”844424930132723” style=”color: red”>‘target_service’, ‘current database’</p> <p __designer:dtid="844424930132727" style="margin: 0in 0in 0pt" class="MsoNormal">      with encryption = off;</p> <p __designer:dtid="844424930132736" style="margin: 0in 0in 0pt" class="MsoNormal">        </p> <p __designer:dtid="844424930132739" style="margin: 0in 0in 0pt" class="MsoNormal">select @payload = N<entry id=”1″ key=”A” value=”B”>;</p> <p __designer:dtid="844424930132746" style="margin: 0in 0in 0pt" class="MsoNormal">send on conversation @handle (@payload);</p> <p __designer:dtid="844424930132754" style="margin: 0in 0in 0pt" class="MsoNormal">commit;</p> <p __designer:dtid="844424930132758" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid=”844424930132760”>The payload in this case is incorrect, the tag is not closed. So what happens with our message? Nothing apparently, the message simply stays in the target\_queue and refuses to be processed, as a simple check using  select * from [target_queue] shows! The obvious culprit should be that message failed to be processed 5 times and the poison message detection has intervened and deactivated the target_queue so lets go ahead and check it:</p> <p __designer:dtid="844424930132765" style="margin: 0in 0in 0pt" class="MsoNormal">select [name], is\_receive\_enabled from <span _\_designer:dtid="844424930132770" style="color: green">sys.service\_queues</span></p> <p __designer:dtid="844424930132771">Now I reckon that as I was writing this piece, I had a big surprise to see that the queue is … not disabled! In fact I had to delete a paragraph I had already wrote,  and come back and revisit this behavior. So what happened? It turns out that the activation mechanism was actualy fooled by our erroneous message to put the queue in the NOTIFIED state, and leave it as such, as a check on the relevant DMV shows: select * from <span _\_designer:dtid="844424930132777" style="color: green">sys.dm\_broker\_queue\_monitors. </span>The NOTIFIED state occurs when an activated procedure was launched but the RECEIVE verb has not been run on the activated queue. But our activated procedure was not running, as a quick check on select * from <span _\_designer:dtid="844424930132782" style="color: green">sys.dm\_broker\_activated\_tasks </span>shows. A second check on the ERRORLOG (or the system event viewer) revealed the problem:</p> <blockquote __designer:dtid="844424930132783">

<tt __designer:dtid=”844424930132784”>2007-10-31 16:31:52.57 spid52s The activated proc [dbo].[usp_target] running on queue tempdb.dbo.target_queue output the following: ‘The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.’</tt> </blockquote> <p __designer:dtid="844424930132786">So our procedure wad run, had hit an exception, but our exception handler was not capable of dealing with the problem. The error in the ERRORLOG indicates that we are dealing with an uncommittable transaction issue. So apparently we need to augment our error handling CATCH block to deal with such problems, and the <a __designer:dtid=”844424930132787” href=”http://msdn2.microsoft.com/en-us/library/ms189797.aspx”>XACT_STATE</a> documentation shows how this can be achieved. So we’re gonna modify our CATCH block to deal with doomed transactions and we’re also gonna modify our RECEIVE code slightly not to cast to XML during the RECEIVE statement, since that apparently is fooling the activation machinery which behaves as if the RECEIVE never occured. Here is our modified procedure:</p> <p __designer:dtid="844424930132788" style="margin: 0in 0in 0pt" class="MsoNormal">— Our modified procedure

</span></p> <p __designer:dtid="844424930132791" style="margin: 0in 0in 0pt" class="MsoNormal">alter procedure [usp_target]</p> <p __designer:dtid="844424930132796" style="margin: 0in 0in 0pt" class="MsoNormal">as</p> <p __designer:dtid="844424930132799" style="margin: 0in 0in 0pt" class="MsoNormal">begin</p> <p __designer:dtid="844424930132802" style="margin: 0in 0in 0pt" class="MsoNormal">      set nocount on;</p> <p __designer:dtid="844424930132810" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @handle uniqueidentifier;</p> <p __designer:dtid="844424930132817" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @messageType sysname;</p> <p __designer:dtid="844424930132824" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @messageBody varbinary(max);</p> <p __designer:dtid="844424930132833" style="margin: 0in 0in 0pt" class="MsoNormal">      declare @payload xml;</p> <p __designer:dtid="844424930132840" style="margin: 0in 0in 0pt" class="MsoNormal">      </p> <p __designer:dtid="844424930132844" style="margin: 0in 0in 0pt" class="MsoNormal">      begin try</p> <p __designer:dtid="844424930132850" style="margin: 0in 0in 0pt" class="MsoNormal">            begin transaction;</p> <p __designer:dtid="844424930132857" style="margin: 0in 0in 0pt" class="MsoNormal">            waitfor (receive top (1)</p> <p __designer:dtid="844424930132867" style="margin: 0in 0in 0pt" class="MsoNormal">                  @handle = <span __designer:dtid=”844424930132871” style=”color: blue”>conversation_handle,</span></p> <p __designer:dtid="844424930132874" style="margin: 0in 0in 0pt" class="MsoNormal">                  @messageType = message_type_name,</p> <p __designer:dtid="844424930132880" style="margin: 0in 0in 0pt" class="MsoNormal">                  @messageBody = message_body</p> <p __designer:dtid="844424930132885" style="margin: 0in 0in 0pt" class="MsoNormal">                  from [target_queue]), timeout 1000;</p> <p __designer:dtid="844424930132893" style="margin: 0in 0in 0pt" class="MsoNormal">            if (0 != @@rowcount)</p> <p __designer:dtid="844424930132902" style="margin: 0in 0in 0pt" class="MsoNormal">            begin</p> <p __designer:dtid="844424930132907" style="margin: 0in 0in 0pt" class="MsoNormal">                  if (@messageType = ‘DEFAULT’)</p> <p __designer:dtid="844424930132916" style="margin: 0in 0in 0pt" class="MsoNormal">                  begin</p> <p __designer:dtid="844424930132921" style="margin: 0in 0in 0pt" class="MsoNormal">                        select @payload = cast(@messageBody as xml);</p> <p __designer:dtid="844424930132932" style="margin: 0in 0in 0pt" class="MsoNormal">                        insert into [target_table] (</p> <p __designer:dtid="844424930132939" style="margin: 0in 0in 0pt" class="MsoNormal">                              [id], [key], [value]) </p> <p __designer:dtid="844424930132946" style="margin: 0in 0in 0pt" class="MsoNormal">                              select e.value(‘@id’, ‘int’) as [id],</p> <p __designer:dtid="844424930132959" style="margin: 0in 0in 0pt" class="MsoNormal">                                    e.value(‘@key’, ‘nvarchar(256)’) as [key],</p> <p __designer:dtid="844424930132971" style="margin: 0in 0in 0pt" class="MsoNormal">                                    e.value(‘@value’, ‘nvarchar(256)’) as [value]</p> <p __designer:dtid="844424930132982" style="margin: 0in 0in 0pt" class="MsoNormal">                                    from @payload.nodes(‘//payload/entry’) p(e);</p> <p __designer:dtid="844424930132993" style="margin: 0in 0in 0pt" class="MsoNormal">                  end;</p> <p __designer:dtid="844424930132999" style="margin: 0in 0in 0pt" class="MsoNormal">                  end conversation @handle;</p> <p __designer:dtid="844424930133006" style="margin: 0in 0in 0pt" class="MsoNormal">            end </p> <p __designer:dtid="844424930133011" style="margin: 0in 0in 0pt" class="MsoNormal">            commit;</p> <p __designer:dtid="844424930133017" style="margin: 0in 0in 0pt" class="MsoNormal">      end try</p> <p __designer:dtid="844424930133023" style="margin: 0in 0in 0pt" class="MsoNormal">      begin catch</p> <p __designer:dtid="844424930133029" style="margin: 0in 0in 0pt" class="MsoNormal">            <span __designer:dtid=”844424930133032” style=”color: green”>— Test XACT_STATE for 0, 1, or -1.</span></p> <p __designer:dtid="844424930133034" style="margin: 0in 0in 0pt" class="MsoNormal">            — If 1, the transaction is committable.</p> <p __designer:dtid="844424930133039" style="margin: 0in 0in 0pt" class="MsoNormal">            — If -1, the transaction is uncommittable and should </p> <p __designer:dtid="844424930133044" style="margin: 0in 0in 0pt" class="MsoNormal">                be rolled back.</p> <p __designer:dtid="844424930133050" style="margin: 0in 0in 0pt" class="MsoNormal">  </p> <p __designer:dtid="844424930133053" style="margin: 0in 0in 0pt" class="MsoNormal">            — Test whether the transaction is uncommittable.</p> <p __designer:dtid="844424930133058" style="margin: 0in 0in 0pt" class="MsoNormal">            if (<span __designer:dtid=”844424930133063” style=”color: fuchsia”>XACT_STATE()) = 1</span></p> <p __designer:dtid="844424930133068" style="margin: 0in 0in 0pt" class="MsoNormal">            begin</p> <p __designer:dtid="844424930133073" style="margin: 0in 0in 0pt" class="MsoNormal">                  rollback transaction;</p> <p __designer:dtid="844424930133080" style="margin: 0in 0in 0pt" class="MsoNormal">            end;</p> <p __designer:dtid="844424930133086" style="margin: 0in 0in 0pt" class="MsoNormal">  </p> <p __designer:dtid="844424930133089" style="margin: 0in 0in 0pt" class="MsoNormal">            — Test wether the transaction is active and valid.</p> <p __designer:dtid="844424930133094" style="margin: 0in 0in 0pt" class="MsoNormal">            if (<span __designer:dtid=”844424930133099” style=”color: fuchsia”>XACT_STATE()) = 1</span></p> <p __designer:dtid="844424930133103" style="margin: 0in 0in 0pt" class="MsoNormal">            begin</p> <p __designer:dtid="844424930133108" style="margin: 0in 0in 0pt" class="MsoNormal">                  declare @error int, @message nvarchar(4000);</p> <p __designer:dtid="844424930133119" style="margin: 0in 0in 0pt" class="MsoNormal">                  select @error = <span __designer:dtid=”844424930133124” style=”color: fuchsia”>ERROR_NUMBER(), @message = <span __designer:dtid=”844424930133127” style=”color: fuchsia”>ERROR_MESSAGE</span>();</span></p> <p __designer:dtid="844424930133130" style="margin: 0in 0in 0pt" class="MsoNormal">                  end conversation @handle with error = @error description = @message;</p> <p __designer:dtid="844424930133141" style="margin: 0in 0in 0pt" class="MsoNormal">                  commit;</p> <p __designer:dtid="844424930133147" style="margin: 0in 0in 0pt" class="MsoNormal">            end</p> <p __designer:dtid="844424930133152" style="margin: 0in 0in 0pt" class="MsoNormal">            end catch;</p> <p __designer:dtid="844424930133159" style="margin: 0in 0in 0pt" class="MsoNormal">end</p> <p __designer:dtid="844424930133162" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid=”844424930133164”>We alter the proceure, turn activation back on and… yet again nothig, the message is still sitting in the target_queue. This time though the queue was correctly disabled, we check the select [name], is_receive_enabled from <span __designer:dtid=”844424930133169” style=”color: green”>sys.service_queues </span>view and sure enough the queue is disabled. But wait a minute! I’m sure TRY/CATCH has its place, but this is not quite what we wanted to achieve, a simple common XML formatting error is taking down our service! How come?</p> <h2 __designer:dtid="844424930133170">Error severities</h2> <p __designer:dtid="844424930133171">As it turns out, XML casting and validation errors are raised with severity 16, and this severity will always put the transaction into an uncommittable state. For Service Broker, this is quite bad, as parsing and shreding XML is a day to day operation for most service applications. Unfortunately, there’s simply no workaround for this issue, which has the implication that one cannot use the SQL Server built-in XML methods to validate the received XML programmatically, one has to ensure that the XML is valid upfront. For XML payloads fortunately there is a solution: declare the message validation on the message type and even enforce an XML schema. This will cause validation to occur prior to the message being enqueued and will ensure that the activate dprocedure has to deal only with valid input. In our case, this means to change the contract of the service to use an XML well formatted validation message:</p> <p __designer:dtid="844424930133173" style="margin: 0in 0in 0pt" class="MsoNormal">create message type [valid_payload] validation = <span __designer:dtid=”844424930133179” style=”color: blue”>well_formed_xml;</span></p> <p __designer:dtid="844424930133182" style="margin: 0in 0in 0pt" class="MsoNormal">create contract [validated_contract] ([valid_payload] sent by initiator);</p> <p __designer:dtid="844424930133190" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930133193" style="margin: 0in 0in 0pt" class="MsoNormal">  </p> <p __designer:dtid="844424930133196" style="margin: 0in 0in 0pt" class="MsoNormal">alter service [target_service] (add contract [validated_contract]);</p> <p __designer:dtid="844424930133204"> </p> <p __designer:dtid=”844424930133205”>We also need to change our activated procedure to deal with the new message type name, the ‘valid_payload’ type:</p> <p __designer:dtid="844424930133206">…</p> <p __designer:dtid="844424930133207" style="margin: 0in 0in 0pt" class="MsoNormal">            if (0 != @@rowcount)</p> <p __designer:dtid="844424930133216" style="margin: 0in 0in 0pt" class="MsoNormal">            begin</p> <p __designer:dtid="844424930133221" style="margin: 0in 0in 0pt" class="MsoNormal">                  if (@messageType = N<span __designer:dtid=”844424930133227” style=”color: red”>‘valid_payload’)</span></p> <p __designer:dtid="844424930133230" style="margin: 0in 0in 0pt" class="MsoNormal">                  begin</p> <p __designer:dtid="844424930133235" style="margin: 0in 0in 0pt" class="MsoNormal">                        select @payload = cast(@messageBody as xml);</p> <p __designer:dtid="844424930133246" style="margin: 0in 0in 0pt" class="MsoNormal">                        insert into [target_table] (</p> <p __designer:dtid="844424930133253" style="margin: 0in 0in 0pt" class="MsoNormal">                              [id], [key], [value]) </p> <p __designer:dtid="844424930133260" style="margin: 0in 0in 0pt" class="MsoNormal">…</p> <p __designer:dtid=”844424930133261”>We can now reset the broker in tempdb (to get rif of the erroneous message that would prevent the target_queue from ever becomming enabled) and enable back the target_queue:</p> <p __designer:dtid="844424930133262" style="margin: 0in 0in 0pt" class="MsoNormal">alter database tempdb set <span __designer:dtid=”844424930133267” style=”color: blue”>new_broker with rollback immediate;</span></p> <p __designer:dtid="844424930133272" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930133275" style="margin: 0in 0in 0pt" class="MsoNormal">alter queue target_queue with status = on;</p> <p __designer:dtid="844424930133285" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930133287">So now we can send again the erroneous message, this time using the validated contract and message type:</p> <p __designer:dtid="844424930133288" style="margin: 0in 0in 0pt" class="MsoNormal">— send a message to the target service</p> <p __designer:dtid="844424930133291" style="margin: 0in 0in 0pt" class="MsoNormal">declare @handle uniqueidentifier, @payload nvarchar(max);</p> <p __designer:dtid="844424930133301" style="margin: 0in 0in 0pt" class="MsoNormal">begin transaction;</p> <p __designer:dtid="844424930133307" style="margin: 0in 0in 0pt" class="MsoNormal">begin dialog conversation @handle</p> <p __designer:dtid="844424930133313" style="margin: 0in 0in 0pt" class="MsoNormal">      from service [sender_service]</p> <p __designer:dtid="844424930133319" style="margin: 0in 0in 0pt" class="MsoNormal">      to service <span __designer:dtid=”844424930133324” style=”color: red”>‘target_service’, ‘current database’</span></p> <p __designer:dtid="844424930133328" style="margin: 0in 0in 0pt" class="MsoNormal">      on contract [validated_contract]</p> <p __designer:dtid="844424930133334" style="margin: 0in 0in 0pt" class="MsoNormal">      with encryption = off;</p> <p __designer:dtid="844424930133343" style="margin: 0in 0in 0pt" class="MsoNormal">  </p> <p __designer:dtid="844424930133346" style="margin: 0in 0in 0pt" class="MsoNormal">select @payload = N<entry id=”1″ key=”A” value=”B”>;</p> <p __designer:dtid="844424930133353" style="margin: 0in 0in 0pt" class="MsoNormal">send on conversation @handle message type [valid_payload] (@payload);</p> <p __designer:dtid="844424930133363" style="margin: 0in 0in 0pt" class="MsoNormal">commit;</p> <p __designer:dtid="844424930133367" style="margin: 0in 0in 0pt" class="MsoNormal">go</p> <p __designer:dtid="844424930133369">And, as expected, the Service Broker responds immedeately with an error on our conversation because the message did not pass the XML payload validation:</p> <p __designer:dtid="844424930133370" style="margin: 0in 0in 0pt" class="MsoNormal">«/span>Error xmlns=http://schemas.microsoft.com/SQL/ServiceBroker/Error></p> <p __designer:dtid="844424930133380" style="margin: 0in 0in 0pt" class="MsoNormal">  «/span>Code>-9615</Code></p> <p __designer:dtid="844424930133390" style="margin: 0in 0in 0pt" class="MsoNormal">  «/span>Description><span __designer:dtid=”844424930133395” style=”font-size: 10pt; font-family: ‘Courier New’“>A message of type ‘valid_payload’ failed XML validation on the target service.  XML parsing: line 1, character 51, end tag does not match start tag This occurred in the message with Conversation ID ‘…’, Initiator: 1, and Message sequence number: 0.</Description></p> <p __designer:dtid="844424930133401" style="margin: 0in 0in 0pt" class="MsoNormal"></Error></p> <p __designer:dtid="844424930133405">So we were able to cope with the case of invalid XML, but how about other cases? What if the message is still valid XML, but has some invalid attributes? Ie. the payload <entry id=”foobar” key=”A” value=”B”/>’<span __designer:dtid=”844424930133407” style=”font-size: 12pt; color: #000000; font-family: Times New Roman”>will pass the well_formed_xml test, but will still cause the service to disable itslef, because ‘foobar’ cannot be casted to int. You could raise the bar even further by specifying an XML schema for validation, but not all of us are so confident in our XML schema knowledge to be sure that we covered everything. And besides, there are binary messages too, XML payload is not a requirement for Service Broker. The truth is that the severity of such trivial errors forcing the transaction to rollback is quite a burden on the application, since it means that pretty much any malformed message can disable a service. And there simply isn’t any buletproof defense against it.</p> <h2 __designer:dtid="844424930133409">Post Rollback corrective actions</h2> <p __designer:dtid="844424930133410">Many developers I talked with have been at this point already and realised that they simply cannot cover all the angles on this problem so they tried another approach: what if one rolls back, but then does some corrective action. Surely we could write some code in the catch block that remedies the problem, like dequeue the message and error the conversation immedeately, without trying to process it’s payload. Usually this looks something like the following, int he BEGIN CATCH block:</p> <p __designer:dtid="844424930133411" style="margin: 0in 0in 0pt" class="MsoNormal">            — Test whether the transaction is uncommittable.</p> <p __designer:dtid="844424930133416" style="margin: 0in 0in 0pt" class="MsoNormal">            if (<span __designer:dtid=”844424930133421” style=”color: fuchsia”>XACT_STATE()) = 1</p> <p __designer:dtid="844424930133426" style="margin: 0in 0in 0pt" class="MsoNormal">            begin</p> <p __designer:dtid="844424930133431" style="margin: 0in 0in 0pt" class="MsoNormal">                  rollback transaction;</p> <p __designer:dtid="844424930133438" style="margin: 0in 0in 0pt" class="MsoNormal">                  — take corrective actions</p> <p __designer:dtid="844424930133443" style="margin: 0in 0in 0pt" class="MsoNormal">                  declare @error int, @message nvarchar(4000);</p> <p __designer:dtid="844424930133455" style="margin: 0in 0in 0pt" class="MsoNormal">                  select @error = <span __designer:dtid=”844424930133460” style=”color: fuchsia”>ERROR_NUMBER(), @message = <span __designer:dtid=”844424930133463” style=”color: fuchsia”>ERROR_MESSAGE</span>();</span></p> <p __designer:dtid="844424930133466" style="margin: 0in 0in 0pt" class="MsoNormal">                  begin transaction;</p> <p __designer:dtid="844424930133473" style="margin: 0in 0in 0pt" class="MsoNormal">                  receive top(1) @messageBody = message_body </p> <p __designer:dtid="844424930133482" style="margin: 0in 0in 0pt" class="MsoNormal">                        from [target_queue]</p> <p __designer:dtid="844424930133487" style="margin: 0in 0in 0pt" class="MsoNormal">                        where <span __designer:dtid=”844424930133491” style=”color: blue”>conversation_handle = @handle;</span></p> <p __designer:dtid="844424930133495" style="margin: 0in 0in 0pt" class="MsoNormal">                  end conversation with error = @error description = @message;</p> <p __designer:dtid="844424930133506" style="margin: 0in 0in 0pt" class="MsoNormal">                  commit;</p> <p __designer:dtid="844424930133512" style="margin: 0in 0in 0pt" class="MsoNormal">            end;</p> <p __designer:dtid="844424930133517"> </p> <p __designer:dtid="844424930133518">But this is riddled with problems. The moment the transaction was rollbed back, the code has absolutely no guarantee over the state of this conversation and its messages. Another instance of the activated procedure might had already picked up the very same message and tried to process it. This problem is true for any processing of this kind, it is not specific to Service Broker, but the typical Service Broker environment, with it’s activated procedures running in parallel and trying to grab the next unlocked message it is very likely to happen in production. What’s worst is that is is unlikely to happen in the test development environement, unless the test environment matches the concurrency of the production environment.</p> <h2 __designer:dtid="844424930133520">Conclusions</h2> <p __designer:dtid="844424930133521">So it turns out error handling in Service Broker is a bit trickier than expected and presenting some challenges. Some recommendations stand valid:</p> <ul __designer:dtid="844424930133522"> <li __designer:dtid="844424930133523">Do use the BEGIN TRY/BEGIN CATCH blocks, they are simplifying the code tremendously</li> <li __designer:dtid="844424930133524">Use the Service Broker conversation semantics to represent business transactions, and use the END CONVERSATION … WITH ERROR to signal errors to the other service at the end of a conversation when you catch</li> <li __designer:dtid="844424930133525">Do harden your service broker contract as much as possible using XML validation schemas to catch malformed messages as early as possible, before they enter your application queues</li> </ul> <p __designer:dtid="844424930133526">But other thant this, dealing with errors and the problems caused by uncommittable transactions and its effects on Service Broker queues is by no means trivial, and is a subject I actually still looking for an acceptable answer.</p>