Welcome to MSDN Blogs Sign in | Join | Help

A new address for this blog

This is the last entry on this blog. Since starting on 10/31 I will no longer be employed with Microsoft, I have opened a new blog at http://rusanu.com/blog where I will continue to post Service Broker, and other, articles.
Posted by RemusRusanu | 0 Comments
Filed under:

SQL Server 2005 SP2 CU4 has shipped

The Cumulative Update No.4 for SQL Server 2005 has shipped: http://support.microsoft.com/kb/941450/en-us. This contains two fixes related to Service Broker:

50001608 940286 (http://support.microsoft.com/kb/940286/) FIX: A Service Broker endpoint stops passing messages in a database mirroring session of SQL Server 2005

This is a very specific condition that can appear if you are using mirrored routes and leads to a live latch deadlock (undetectable by the deadlock monitor). The symptomps are explained in the KB940286.

50001802  Error message when you send a message in a Service Broker application that has message forwarding enabled in Microsoft SQL Server 2005: “err 11290 state 3 because of incorrect timeout start.”

This issue is the one originally reported on our discussion forums at http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2093238&SiteID=1. This problem appears on slow networks when sending large messages. If you are seeing messages dropped with error 11290 state 3, apply this CU4 to resolve the problem.

Posted by RemusRusanu | 0 Comments
Filed under:

When it rains, it pours

I’ve seen a number of customers reporting problems about ERRORLOG growing out of control (tens of GBs) because of error like following:

2007-10-12 11:18:32.44 spid25s     The query notification dialog on conversation handle '{EC54573A-9978-DC11-961C-00188B111155}.' closed due to the following error: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service &apos;SqlQueryNotificationService-4869b411-fa1c-4d8a-ab37-5bf5762eb98b&apos; because it does not exist.</Description></Error>'.

2007-10-12 11:37:20.69 spid51s     The activated proc [dbo].[ SqlQueryNotificationStoredProcedure-633a0c13-66e4-410e-8bd8-744146d2258d] running on queue tempdb.dbo. SqlQueryNotificationService-633a0c13-66e4-410e-8bd8-744146d2258d output the following:  'Could not find stored procedure 'dbo. SqlQueryNotificationStoredProcedure-633a0c13-66e4-410e-8bd8-744146d2258d'

2007-10-12 10:59:39.32 spid51      Error: 28054, Severity: 11, State: 1.
2007-10-12 10:59:39.32 spid51      Service Broker needs to access the master key in the database 'tempdb'. Error code:25. The master key has to exist and the service master key encryption is required.

All these messages are related one way or another to the ADO.Net component SqlDependency.  I’ll present each one how it is caused and how to avoid id.

First, lets review in brief how the SqlDependency works. The application is supposed to invoke the static method SqlDependency.Start at startup to deploy the necessary infrastructure, then use instances of SqlDependency object associated with a SqlCommand to receive callbacks when the query executed is notified (data has changed), and finally call SqlDependency.Stop when the application shuts down to tear down the infrastructure deployed at startup. I have explained before how the server side Query Notifications feature works to detect the changes and to notify the subscriptions, see http://blogs.msdn.com/remusrusanu/archive/2006/06/17/635608.aspx

The three errors above are all a result of the way how SqlDependency deploys and cleans up it’s infrastructure. The first error happens in the following scenario:

1)      SqlDependency.Start () is invoked by an application. At this moment a service, a queue and a procedure are created.

2)      SqlDependency is used to subscribe to query notifications. Perhaps some queries are notified and re-subscribed, in a normal operations mode

3)      Application exists, SqlDependency.Stop is called and the service, queue and procedure are dropped.  However, there are still subscribed notifications pending on the server.

4)      One or more of the pending subscriptions are notified in the server. This cause a notification message to be sent, but the destination service was dropped, so an error is returned to the sender. The QN receives this error and displays an error message in the ERRORLOG:
2007-10-12 11:18:32.44 spid25s     The query notification dialog on conversation handle '{EC54573A-9978-DC11-961C-00188B111155}.' closed due to the following error: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service &apos;SqlQueryNotificationService-4869b411-fa1c-4d8a-ab37-5bf5762eb98b&apos; because it does not exist.</Description></Error>'

This is straightforward scenario and the application developer has done no mistake. However, this case should not result in huge ERRORLOG growth, because it will happen only for subscriptions notified after an application had exited. So if an application runs twice for 4 hours a day and subscribes to 10 queries, it should cause at most 20 entries like this a day. Multiplied by a decent size deployment base you will get something like 2000 entries for a 100 users deployment, annoying but not fatal. If you find your ERROLOG swamped by the message above (I’ve heard of thousands of entries added per hour), review your application behavior. Most likely you are calling SqlDepdendency.Stop way too often. Normally it should be called only on AppDomain unload. A possible workaround would probably have to be based on the kill query notification subscription verb, forcing the application to cleanup any pending subscription at shutdown. Good luck figuring out your own subscriptions from the other instances of the same application…

The second error message is a bit trickier. This one happens because the SqlDepndency cleanup attempted to drop the service, queue and procedure. Here is an example of how the cleanup procedure looks like:

CREATE PROCEDURE [SqlQueryNotificationStoredProcedure-633a0c13-66e4-410e-8bd8-744146d2258d]

AS

BEGIN

      BEGIN TRANSACTION;

      RECEIVE TOP(0)

            conversation_handle

            FROM [SqlQueryNotificationService-633a0c13-66e4-410e-8bd8-744146d2258d];

      IF (SELECT COUNT(*) FROM [SqlQueryNotificationService-633a0c13-66e4-410e-8bd8-744146d2258d]

            WHERE message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer') > 0

      BEGIN

            DROP SERVICE [SqlQueryNotificationService-633a0c13-66e4-410e-8bd8-744146d2258d];

            DROP QUEUE [SqlQueryNotificationService-633a0c13-66e4-410e-8bd8-744146d2258d];

            DROP PROCEDURE [SqlQueryNotificationStoredProcedure-633a0c13-66e4-410e-8bd8-744146d2258d];

      END

      COMMIT TRANSACTION;

END

 

It is possible that for whatever reason it fails to drop the service and the queue, but it dropped the procedure. Since there is no referential integrity rule to prevent the drop of a procedure that is attached as activation procedure to a queue, it is possible to drop the procedure and leave the queue declared with activation pointing to a missing procedure. In this case the activation mechanism will trigger the error message every five seconds:

2007-10-12 11:37:20.69 spid51s     The activated proc [dbo].[ SqlQueryNotificationStoredProcedure-633a0c13-66e4-410e-8bd8-744146d2258d] running on queue tempdb.dbo. SqlQueryNotificationService-633a0c13-66e4-410e-8bd8-744146d2258d output the following:  'Could not find stored procedure 'dbo. SqlQueryNotificationStoredProcedure-633a0c13-66e4-410e-8bd8-744146d2258d'

Every five seconds for each queue left behind by SqlDependency, now that will grow the ERRORLOG to fill every last sector available on the disk in no time at all. The queues that have this problem must be manually fixed (ie. DROP QUEUE). One can identify them by looking up sys.service_queues for queues that have an activation procedure that no longer exists. Also one must identify the cause of the problem, why the service and queue could not be dropped, since the application that is using SqlDependency will continue to create new queues with orphaned activation.

Finally the last problem is the message about the lack of a master key in the database:

2007-10-12 10:59:39.32 spid51      Error: 28054, Severity: 11, State: 1.
2007-10-12 10:59:39.32 spid51      Service Broker needs to access the master key in the database 'tempdb'. Error code:25. The master key has to exist and the service master key encryption is required.

This message is usually also caused by SqlDependency because the timer it creates to cleanup the service, queue and procedure needs a conversation (to use BEGIN CONVERSATION TIMER). This conversation is started without specifying the ENCRYPTION = OFF clause, so it will need a database master key to store the generated session keys. Although this conversation never sends any message and session keys are not actually needed, the message is periodically logged into the ERRORLOG. To avoid this message, there is a trivial workaround: simply create a database master key in the database

Posted by RemusRusanu | 1 Comments
Filed under:

Service Broker Tutorials update in MSDN

The new MSDN release online now has a major overhaul of the Service Broker tutorials chapter: http://msdn2.microsoft.com/en-us/library/bb839489.aspx

These contain examples of how to establish a dialog locally within the same database, between two databases in the same SQL Instance and between two SQL Instances. The old examples had some issue and the straight forward 'Hello World' application wouldn't work without a database master key, I like the new examples and tutorial much more. They are also more detailed and easier to follow and understand, a natural trend as the result of the feedback and sugestions you guys sent in.
 

Posted by RemusRusanu | 0 Comments
Filed under:

Service Broker 'leaked' target conversation endpoints fix ships in Cumulative Update for SQL Server 2005 SP2

 

The CU3 of SQL Server 2005 SP2 was just released on the web, http://support.microsoft.com/kb/939537

It contains an update to Service Broker:

50001416 940260 (http://support.microsoft.com/kb/940260/) FIX: Error message when you use Service Broker in SQL Server 2005: "An error occurred while receiving data: '64(The specified network name is no longer available.)'"

 

The title of the fix is derived from the original incident case, but the fix is actualy for the one case that would lead to 'leaked' conversation endpoints on the target in remote scenarios (communication between two different SQL Server instances) was fixed. The case fixed is when the pattern of message exchange is correct:

- Initiator sends one or more messages

- Target issue END CONVERSATION and sends the EndDialog message

- Initiator receives the EndDialog and responds by issuing it's own END CONVERSATION

The defect was that if the initiator issues the END CONVERSATION within 1 (one) seconds of the target sending the EndDialog message, the target endpoint was 'leaked'.

 

Note that this fix does not address local cases (two databases within the same SQL Server instance) nor the case of remote 'incorrect' message exchange pattern, when the initiator ends the conversation first w/o ever receiving any message from the target.

Posted by RemusRusanu | 1 Comments
Filed under:

Dynamic Routing Service

So how does one deploy Service Broker services in a large enterprise? Hundreds of services that change location every now and then (just enough to create a major outage exactly at the wrong moment!), and each database requiring a route to any service it wishes to interact with. Creating hundreds and thousands of routes and maintaining them as each new service is deployed, is retired or is moved is a difficult task and obviously prone to operational mistakes, pretty much a disaster waiting to happen.

One solution to this problem is to use a ‘dynamic routing service’, as described in the Service Broker routing algorithm http://technet.microsoft.com/en-us/library/ms166052.aspx:

4. If a route to a dynamic routing service is present, and no request for a route to the service is pending, mark the conversation delayed and request routing information from that service.

What is a ‘dynamic routing service’? Is just an ordinary Service Broker service, implemented by your enterprise/environment and responsible to provide routing information for all the services in the environment is deployed in. Whenever the Service Broker needs to route message to a service with an unknown location it will request the ‘dynamic routing service’ to find the location of the unknown service and provide the appropriate routing information. The request for routing information is, obviously, a Service Broker message delivered to the ‘dynamic routing service’ queue. The ‘dynamic routing service’ is expected to actually create a route in the database and then ‘respond’ to the request by ending the dialog.

Example

Let’s see how the ‘dynamic routing’ behaves in practice. So we’ll create a ‘dynamic routing service’ and watch it in action. For this example we’ll consider that a newly deployed ‘MyDepartament/MyService’ application/service needs to exchange messages with the ‘MyEnterprise/Accounting’ service. So go ahead and create this ‘application’:

create queue [MyDepartament/MyService/Queue];

create service [MyDepartament/MyService]

      on queue [MyDepartament/MyService/Queue];

go

When this application initiates a dialog with the ‘MyEnterprise/Accounting’ service, messages will just sit in sys.transmission_queue because Service Broker needs to know the location of this service, it needs a route. Let’s have the ‘application’ send a message (for simplicity will use the implicit [DEFAULT] contract and message type):

 

begin transaction;

declare @h uniqueidentifier;

begin dialog conversation @h

      from service [MyDepartament/MyService]

      to service N'MyEnterprise/Accounting'

      with encryption = off;

send on conversation @h (N'<request account="FOO" information="credit limit"></request>');

commit;

go

If we check the sys.transmission_queue, we found the message there delayed because there is no routing information about the location of the service:

select transmission_status from sys.transmission_queue;

transmission_status

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The target service name could not be found. Ensure that the service name is specified correctly and/or the routing information has been supplied.

 

(1 row(s) affected)

Let’s see how things change if we introduce a ‘dynamic routing service’. Note that the service name must be SQL/ServiceBroker/BrokerConfiguration:

create queue [SQL/ServiceBroker/BrokerConfiguration/Queue];

create service [SQL/ServiceBroker/BrokerConfiguration]

      on queue [SQL/ServiceBroker/BrokerConfiguration/Queue]

      ([http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice]);

go

 

This doesn’t change anything so far, because the routing algorithm described at http://technet.microsoft.com/en-us/library/ms166052.aspx says that a route to this service has to exist in order that ‘dynamic routing’ functionality is used. So let’s go ahead and create this route:

create route [SQL/ServiceBroker/BrokerConfiguration]

      with service_name = N'SQL/ServiceBroker/BrokerConfiguration',

      address = N'LOCAL';

 

As soon as we add this route, two messages appear in the ‘dynamic routing service’ queue:

select cast(message_body as xml), message_type_name

      from [SQL/ServiceBroker/BrokerConfiguration/Queue]

                                                                                                                                                                                                                                                                 message_type_name

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------

<MissingRemoteServiceBinding xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice/MissingRemoteServiceBinding"><SERVICE_NAME>MyEnterprise/Accounting</SERVICE_NAME></MissingRemoteServiceBinding>                                     http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice/MissingRemoteServiceBinding

<MissingRoute xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice/MissingRoute"><SERVICE_NAME>MyEnterprise/Accounting</SERVICE_NAME></MissingRoute>                                                                                  http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice/MissingRoute

 

(2 row(s) affected)

We’ll ignore the message type [http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice/MissingRemoteServiceBinding] for the moment, so let’s focus on the ‘MissingRoute’ one:

<MissingRoute xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice/MissingRoute">

  <SERVICE_NAME>MyEnterprise/Accounting</SERVICE_NAME>

</MissingRoute>

So the ‘dynamic routing service’ was requested to provide routing information about the service ‘MyEnterprise/Accounting’. The ‘dynamic routing service’ has to find the location of this service, create a route to it and end the conversation on which this request was sent. We’ll cheat for the moment and create route manually, then end the conversation manually:

create route [MyEnterprise/Accounting]

      with

            service_name = N'MyEnterprise/Accounting',

            address = N'tcp://accountingserver.myenterprisedomain.org:4022';

go

 

begin transaction

declare @h uniqueidentifier;

receive

      @h = conversation_handle

      from [SQL/ServiceBroker/BrokerConfiguration/Queue];

if @h is not null

begin

      end conversation @h;

end

commit;

go

This example shows how the ‘dynamic routing service’ functionality works and how the Service Broker routing algorithm will use this service whenever it needs to find the location of an unknown service.  Of course, the difficult part is actually implementing this service correctly J

Dynamic Routing Service requirements

  1. Has to be bound to the ([http://schemas.microsoft.com/SQL/ServiceBroker/BrokerConfigurationNotice] contract in order to accept the MissingRoute messages
  2. Has to be able to actually find the information about the location of any service in the enterprise
  3. Has to create the ‘missing route’ with the information retrieved at the above step
  4. Needs to end incoming request conversations
  5. Has to adapt to any changes in the location of service as services are moved, retired or new services are added

Although the Service Broker algorithm only looks up a route to the ‘dynamic routing service’ (meaning the service could be located anywhere), the requirement to create the actual ROUTE in the database means that the normal implementation of this service is in the same database where the application is. That is, every deployed database in the enterprise contains a copy of this service. One can think of this service much like an automation script to provide ‘dynamic routing’. This leaves the question on how is the ‘dynamic routing service’ capable to find the location of any service? Usually this implies a central repository where all services are registered and the ‘dynamic routing service’ interrogates this central repository.

As for the requirement to adapt to services being moved and decommissioned, the best approach is simply to rely on a expiration time associated with each request. In fact, the ‘dynamic routing service’ doesn’t even have to implement and track this expiration time; it can simply use the CREATE ROUTE statement itself and provide a lifetime after which the route expires:

create route [MyEnterprise/Accounting]

      with

            service_name = N'MyEnterprise/Accounting',

            address = N'tcp://accountingserver.myenterprisedomain.org:4022',

            LIFETIME = 3600;

When the newly created route expires, a new request will be sent to the ‘dynamic routing service’. The actual expiration lifetime value is dependent on how fast is desired that the system reacts to a service being relocated and how often are such relocation expected to occur. Reasonable lifetimes are in the range of 5-10 minutes up to hours. Days would probably be too long and cause long times of unavailability if an unexpired route keeps sending messages to a service at the old location, sorter times will result in too many lookups on the central repository and cause unnecessary contention.

The Central Repository

So how is the ‘dynamic routing service’ supposed to interrogate the central repository about the location of the ‘unknown’ services? Here are some alternatives:

  1. Use Service Broker. Although it seems like a chicken and egg problem, it is not J If Service Broker is used to interrogate the central repository, then the necessary routes to reach the ‘central repository have to be pre-deployed with each database, because otherwise the ‘dynamic routing service’ will not be able to find the location … of the central repository service.
  2. Use Active Directory. The ‘dynamic routing service’ could use an ADSI linked server to query the Active Directory using LDAP. Of course, this means that your Active Directory environment has to contain new schema objects for Service Broker services. The Windows 2003 Active Directory schema contains objects like ‘SQL Server Instance’ and ‘SQL Server Database’, but it does not contain ‘SQL Server Service Broker Service’. Another approach is to use the Active Directory ‘Service-Connection-Point’ objects and provide the service address as the ‘Service-Binding-Information’ property.
  3. Use a file share, simply lookup a well known share where a each known service contains a describing its location (perhaps a ‘service listing’ created with the Service Listing Manager tool)
  4. Use a Web Service

If I would do such a Central Repository, of course, I would choose option 1 J. The Active Directory option is also interesting, but I have to recon that I could not implement it when I tried. But my knowledge and understanding of AD is quite minimal, and I had to deal with an environment where I was restricted severely in what I could do/modify in the AD (the actual Redmond MS domain). If you implement this successfully, drop me a note about it, I’m actually interested how it’s done.

Another subject of interest is how to keep this Central Repository up to date. One could use Event Notifications for this! Even more, the ‘dynamic routing service’ itself can be extended to accept the [http://schemas.microsoft.com/SQL/Notifications/PostEventNotification] contract and subscribe to the CREATE/ALTER/DROP SERVICE events:

 

create event notification [SQL/ServiceBroker/BrokerConfiguration/Services]

      on database

      for create_service, alter_service, drop_service

      to service N'SQL/ServiceBroker/BrokerConfiguration', 'current database';

This event notification subscription will send a message to our service each time a service is created, altered or dropped in the database. Since the ‘dynamic routing service’ has the means to communicate with the Central Repository in the first place, it can use these means to add, modify and drop service information in the repository. The service would also have to deal with services being moved around, either by databases that are detached/attached to a different instance, or by SQL Server host machines being renamed, but I will not drill further into this topic.

Gotcha!

Whenever a route to the ‘dynamic routing service’ is added, a pretty unexpected thing happens: all of the services in the database become all of the sudden unreachable! This is because, according to the routing algorithm described at http://technet.microsoft.com/en-us/library/ms166052.aspx they are usually reached via the default ‘AutoCreatedLocal’ route at step 5 in the algorithm:

5. Find a route that does not specify either the service name or the service broker identifier

But when a ‘dynamic routing service’ is added, the step 4 takes precedence and thus all local services are suddenly ‘delayed’ expecting an answer from the ‘dynamic routing service’! To prevent this from happening, whenever one adds the route to the ‘dynamic routing service’ it should also add an explicit ‘LOCAL’ route to the built-in services:

-- Because we enabled dynamic routing, all of the existing

-- services in the database are sudenly unreacheable, unless an

-- explicit route is created for them. this includes the built-in services

-- like Event Notifications and Query Notifications, as well as the

-- 'ServiceBroker' service itself.

--

create route [http://schemas.microsoft.com/SQL/Notifications/EventNotificationService]

      with service_name = N'http://schemas.microsoft.com/SQL/Notifications/EventNotificationService',

      address = N'LOCAL';

 

create route [http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService]

      with service_name = N'http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService',

      address = N'LOCAL';

 

create route [http://schemas.microsoft.com/SQL/ServiceBroker/ServiceBroker]

      with service_name = N'http://schemas.microsoft.com/SQL/ServiceBroker/ServiceBroker',

      address = N'LOCAL';

go

Posted by RemusRusanu | 3 Comments
Filed under:

New book released: Pro SQL Server 2005 Service Broker by Klaus Aschenbrenner

This new book that is dedicated to the SQL Service Broker is now avalable. Klaus Aschenbrenner is an two-time C# MVP with significant expertise in messaging. He took an early interest in the Service Broker technology and he definetly 'got it'. I had the priviledge to look over early drafts of the book and if you are a Service Broker fan, this book is a must read. The nearly 500 pages tome covers many advanced topics and is full of examples.

http://www.amazon.com/Pro-Server-2005-Service-Broker/dp/1590598423/ref=pd_bbs_sr_2/105-5258080-9466050

Posted by RemusRusanu | 0 Comments
Filed under:

Recycling Conversations

In my previous post http://blogs.msdn.com/remusrusanu/archive/2007/04/24/reusing-conversations.aspx I promised I’ll follow up with a solution to the question about how to end the conversations that are reused for the data-push scenario (logging, auditing, ETL for DW etc).

First of all, why end them in the first place? The answer is to minimize exposure in case of problems. If a problem happens and a dialog has to be destroyed (i.e. errored), then we want to minimize the potential number of messages that we have to re-process or that are potential lost. But wait a minute, how come we’re talking about message loss, doesn’t Service Broker guarantee delivery? The truth is that no system can ever guarantee delivery under all conditions. Human error can happen resulting in misconfiguration, applications have bugs that can send invalid messages (e.g. fail XML validation); hardware can be catastrophically lost w/o any possibility to restore a database/host and so on and so forth. Reusing a single dialog to send all your messages (or one per @@spid) is like putting all your eggs in one basket. If that dialog was trailing millions of messages behind, all those messages have to be reprocessed or are lost.

Another reason to recycle the dialogs is to allow RETENTION on queues. Enabling RETENTION keeps a copy of every message sent to/from that queue for the duration of the dialog. If a dialog is kept open for unlimited duration, the queue will continuously grow.

Also we want to recycle dialogs to deal with load balancing scenarios, since a dialog is sticky to one service instance we would like to periodically start new dialogs so they ‘stick’ to new instances of from the available target service instances.

So how should we end this dialogs? My recommendation is to end them based on time. Choose a time for how long a dialog should be used, depending on the expected number of messages traffic. Then you can use the Service Broker conversation timers to end the dialog when it’s time is done. I also believe that the timer should be handled by the initiator, but the conversation should be ended by the target. Given that the pattern we’re talking about (one way data push) involves sending only messages from initiator to target, the initiator is not allowed to end the conversation because that will create a fire-and-forget message pattern, a very undesirable message exchange pattern, see http://blogs.msdn.com/remusrusanu/archive/2006/04/06/570578.aspx. My recommendation is to have a special message in the contract used by the two services that informs that target that the initiator is done sending on this dialog. Lets call this message [EndOfStream]. The target would respond to this message by ending its side of the conversation, thus sending an EndDialog message to the initiator. The initiator responds to this message by ending its side (issuing the END CONVERSATION verb).

To implement what I described above we would need to modify the original example from http://blogs.msdn.com/remusrusanu/archive/2007/04/24/reusing-conversations.aspx to create a conversation timer when the dialog is created and to add an activated procedure that handles the timer message, as well as the EndDialog and Error messages. One interesting issue is how to handle the timer message. After sending the [EndOfStream] message, the initiator should not send any more messages on that dialog. This is easily achieved by removing the dialog handle from the [SessionConversations] table. The next invocation of usp_Send will create a new one, just as desired. But there is a race condition there, an usp_Send procedure might be executing already and trying to send a message on that conversation. Even though the dialog was removed from the [SessionConversations] table, the concurrent usp_Send will successfully send on the dialog that just sent an [EndOfStream] message. Since the target responds to the [EndOfstream] message by ending the conversation, the message sent is most likely lost as the target will ignore it! To solve this situation, the usp_Send procedure must guarantee the stability of the conversation it looked up in the [SessionConversations] table. So the SELECT from the [SessionConversations] table has to be REPEATABLE, and this can be achieved with an appropriate query hint (HOLDLOCK).

But by doing this we introduce a new problem: the activated procedure handling the timer message and the usp_Send procedure might deadlock each other! One will hold a lock on the conversation and try to update the [SessionConversations] table, while the other will hold the lock on the [SessionConversations] table and try to acquire the lock on the conversation: guaranteed deadlock. To solve this problem, will solve it as most deadlock problems are solved: acquire the locks in the same order in both procedures. Since the usp_Send must acquire the locks in the order row-in-[SessionConversations]-table-first-conversation-next, the activated procedure must do the same. That is: delete the row from [SessionConversations] first, send the [EndOfStream] message next.

 

So here is the code, the modified usp_Send and the initiator side activated procedur. I highlighted the changes in usp_Send.

 

 

-- This table associates the current connection (@@SPID)

-- with a conversation. The association key also

-- contains the conversation parameteres:

-- from service, to service, contract used

--

CREATE TABLE [SessionConversations] (

      SPID INT NOT NULL,

      FromService SYSNAME NOT NULL,

      ToService SYSNAME NOT NULL,

      OnContract SYSNAME NOT NULL,

      Handle UNIQUEIDENTIFIER NOT NULL,

      PRIMARY KEY (SPID, FromService, ToService, OnContract),

      UNIQUE (Handle));

GO

 

-- SEND procedure. Will lookup to reuse an existing conversation

-- or start a new in case no conversation exists or the conversation

-- cannot be used

--

CREATE PROCEDURE [usp_Send] (

      @fromService SYSNAME,

      @toService SYSNAME,

      @onContract SYSNAME,

      @messageType SYSNAME,

      @messageBody VARBINARY(MAX))

AS

BEGIN

      SET NOCOUNT ON;

      DECLARE @handle UNIQUEIDENTIFIER;

      DECLARE @counter INT;

      DECLARE @error INT;

 

      SELECT @counter = 1;

     

      BEGIN TRANSACTION;

      -- Will need a loop to retry in case the conversation is

      -- in a state that does not allow transmission

      --

      WHILE (1=1)

      BEGIN

            -- Seek an eligible conversation in [SessionConversations]

            --

            SELECT @handle = Handle

                  FROM [SessionConversations] WITH (HOLDLOCK)

                  WHERE SPID = @@SPID

                        AND FromService = @fromService

                        AND ToService = @toService

                        AND OnContract = @OnContract;

            IF @handle IS NULL

            BEGIN

                  -- Need to start a new conversation for the current @@spid

                  --

                  BEGIN DIALOG CONVERSATION @handle

                        FROM SERVICE @fromService

                        TO SERVICE @toService

                        ON CONTRACT @onContract

                        WITH ENCRYPTION = OFF;

                  -- Set an one hour timer on the conversation

                  --

                  BEGIN CONVERSATION TIMER (@handle) TIMEOUT = 3600;

                  INSERT INTO [SessionConversations]

                        (SPID, FromService, ToService, OnContract, Handle)

                        VALUES

                        (@@SPID, @fromService, @toService, @onContract, @handle);

            END;

            -- Attempt to SEND on the associated conversation

            --

            SEND ON CONVERSATION @handle

                  MESSAGE TYPE @messageType

                  (@messageBody);

            SELECT @error = @@ERROR;

            IF @error = 0

            BEGIN

                  -- Successful send, just exit the loop

                  --

                  BREAK;

            END

           

        &n