--------------------------------------------------------------------
-- Script for fast data push sample.
--
-- This file is part of the Microsoft SQL Server Code Samples.
-- Copyright (C) Microsoft Corporation. All Rights reserved.
-- This source code is intended only as a supplement to Microsoft
-- Development Tools and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
-- ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
--------------------------------------------------------------------
----------------------------------------------------
-- Common setup for fast data push.
-- Before running, replace the configuration-dependent
-- domain_name and partner_host names.
----------------------------------------------------
USE master;
GO
---------------------------------------------------------------------
-- Create the broker endpoint using Windows authentication.
-- On a secure network, encryption may be disabled to improve speed:
-- (AUTHENTICATION = Windows, ENCRYPTION = DISABLED)
-- This step can be skipped if services are in the same database instance.
---------------------------------------------------------------------
IF EXISTS (SELECT * FROM sys.endpoints WHERE name = 'service_broker_endpoint')
DROP ENDPOINT service_broker_endpoint;
GO
CREATE ENDPOINT service_broker_endpoint
STATE = STARTED
AS TCP (LISTENER_PORT = 4022)
FOR SERVICE_BROKER (AUTHENTICATION = Windows);
GO
-- A procedure to create a Windows login and grant it endpoint connection permission.
IF EXISTS (SELECT name FROM tempdb.sys.procedures WHERE name LIKE '#usp_windows_login_for_broker_endpoint%')
DROP PROCEDURE #usp_windows_login_for_broker_endpoint;
GO
CREATE PROCEDURE #usp_windows_login_for_broker_endpoint (
@domain_name VARCHAR(100),
@login_name VARCHAR(50),
@endpoint_name VARCHAR(50))
AS
BEGIN
SET NOCOUNT ON;
DECLARE @query VARCHAR(1000);
-- Create the login.
SET @query =
'IF EXISTS (SELECT * FROM sys.syslogins WHERE name = ''' + @domain_name + '\' + @login_name + ''')
DROP LOGIN [' + @domain_name + '\' + @login_name + ']';
EXEC (@query);
SET @query = 'CREATE LOGIN [' + @domain_name + '\' + @login_name + '] FROM Windows';
EXEC (@query);
-- Grant the login connection access to the endpoint.
SET @query = 'GRANT CONNECT ON ENDPOINT::' + @endpoint_name + ' TO [' + @domain_name + '\' + @login_name + ']';
EXEC (@query);
END;
GO
-- Create a login for the partner machine (partner_host) in the
-- shared domain (domain_name) and grant it endpoint connection permission.
-- This assumes the availability of Kerberos authentication.
-- Note: the '$' is significant.
EXEC #usp_windows_login_for_broker_endpoint 'domain_name', 'partner_host$', 'service_broker_endpoint';
GO
---------------------------------------------------------------------
-- Create the data push database.
---------------------------------------------------------------------
IF EXISTS (SELECT * FROM sys.databases WHERE name = 'data_push_database')
DROP DATABASE data_push_database;
GO
CREATE DATABASE data_push_database;
GO
USE data_push_database;
GO
-- Create messages and contract.
CREATE MESSAGE TYPE data_push_message VALIDATION = NONE;
CREATE MESSAGE TYPE end_of_stream;
CREATE CONTRACT data_push_contract
(
data_push_message SENT BY INITIATOR,
end_of_stream SENT BY INITIATOR
);
----------------------------------------------------
-- The data push parameters.
--
-- Application parameters:
-- message_quantity: number of messages sent.
-- message_size: size of message in bytes.
-- message_processing_time: time for target to process a message.
-- Format: 'hh:mm:ss:xxx' hh=hours, mm=minutes, ss=seconds, xxx=milliseconds
--
-- Internal parameters:
-- number_initiator_transactions: number of initiator transactions used.
-- Notes: 1. Fewer is more efficient since each transaction entails an overhead.
-- 2. Messages are actually sent when transaction commits, so sending a large
-- number of messages in a transaction can result in increased latency.
-- initiator_transaction_delay: delay time between initiator transactions.
-- Format: 'hh:mm:ss:xxx' hh=hours, mm=minutes, ss=seconds, xxx=milliseconds
-- Notes: 1. A transaction can be thought of as a burst of message_quantity /
-- number_initiator_transactions messages. This delay specifies a time
-- to wait before the next transaction is run.
-- 2. This parameter can be used to simulate message traffic distributed
-- over time.
-- number_dialogs: number of dialogs used to send messages.
-- Notes: 1. Message ordering only guaranteed with a dialog.
-- 2. Multiple dialogs allows concurrent processing on target.
-- 3. Dialog creation is expensive; dialog reuse is employed here.
-- dialog_recycle_max_messages: maximum number messages sent on a dialog before
-- recycling the dialog. Recycling is defined as ending the old dialog and
-- beginning a new one. A value of -1 indicates no recycling.
-- Notes: 1. Larger is more efficient since is minimizes the overhead of
-- creating dialogs.
-- 2. Larger can complicate dialog error processing.
-- number_target_procedures: number of activated target procedures to receive messages.
-- Notes: 1. A target proc locks all messages in a dialog when it receives first message
-- for a dialog, blocking other procs from processing these messages.
-- 2. Thus more dialogs yields increased concurrent processing. However, unless
-- dialog recycling is used, this should be set to number_dialogs, which
-- can utilize a target proc for each dialog.
-- max_messages_per_receive: maximum number of messages per target receive call.
-- Notes: 1. Larger is more efficient, but can complicate transaction error processing.
-- 2. The maximum value can be set to message_quantity / number_dialogs.
--
-- General note: for simplicity, @message_quantity should be evenly divisible
-- by @number_initiator_transactions x @number_dialogs, since this allows a
-- constant number of messages to be sent per dialog per transaction. "Remainder"
-- messages will not be sent to the target.
--
-- Example:
--
-- I want to send 100000 messages in sets of 10000 with a delay of 10 seconds between
-- each set. This calls for 10 transactions. Each message is 100 bytes and the target
-- message processing time is 10 ms. The messages are independent of each other, so use
-- 5 dialogs and target procedures to get some concurrent processing on the target. Allow
-- each target proc to receive 2000 messages at a time. Do not recycle dialogs.
--
-- INSERT INTO data_push_parameters
-- VALUES
-- (
-- 100000,
-- 10000,
-- '00:00:00:010',
-- 10,
-- '00:00:10:000',
-- 5,
-- -1,
-- 5,
-- 2000
-- );
--
--
CREATE TABLE data_push_parameters (
message_quantity BIGINT NOT NULL,
message_size INT NOT NULL,
message_processing_time CHAR(12) NOT NULL,
number_initiator_transactions INT NOT NULL,
initiator_transaction_delay CHAR(12) NOT NULL,
number_dialogs INT NOT NULL,
dialog_recycle_max_messages BIGINT NOT NULL,
number_target_procedures INT NOT NULL,
max_messages_per_receive BIGINT NOT NULL);
GO
-- Insert parameter values.
TRUNCATE TABLE data_push_parameters;
INSERT INTO data_push_parameters
(
message_quantity,
message_size,
message_processing_time,
number_initiator_transactions,
initiator_transaction_delay,
number_dialogs,
dialog_recycle_max_messages,
number_target_procedures,
max_messages_per_receive
)
VALUES
(
10000,
1000,
'00:00:00:000',
1,
'00:00:00:000',
1,
-1,
1,
1000
);
GO
-- Check parameters.
DECLARE @message_quantity BIGINT;
DECLARE @number_initiator_transactions INT;
DECLARE @number_dialogs INT;
DECLARE @i BIGINT;
DECLARE @string VARCHAR(50);
SET @message_quantity = (SELECT message_quantity FROM data_push_parameters);
SET @number_initiator_transactions = (SELECT number_initiator_transactions FROM data_push_parameters);
SET @number_dialogs = (SELECT number_dialogs FROM data_push_parameters);
SET @i = @message_quantity / (@number_dialogs * @number_initiator_transactions);
SET @i = @i * @number_dialogs * @number_initiator_transactions;
IF @message_quantity > @i
BEGIN
SET @i = @message_quantity - @i;
SET @string = (SELECT CAST( @i AS VARCHAR(50)));
PRINT 'Warning: @message_quantity is not evenly divisible by @number_dialogs * @number_initiator_transactions';
PRINT @string + ' messages will not be sent to the target';
END;
GO
--------------------------------------------------------------------
-- Script for fast data push sample.
--
-- This file is part of the Microsoft SQL Server Code Samples.
-- Copyright (C) Microsoft Corporation. All Rights reserved.
-- This source code is intended only as a supplement to Microsoft
-- Development Tools and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
-- ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
--------------------------------------------------------------------
----------------------------------------------------
-- Initiator setup for fast data push.
-- Before running, customize the configuration-dependent
-- routing to the target service.
----------------------------------------------------
USE data_push_database;
GO
-- The data push procedure: send messages to target.
CREATE PROCEDURE usp_data_push
AS
BEGIN
SET NOCOUNT ON;
-- Get initiator parameters.
DECLARE @message_quantity BIGINT;
DECLARE @message_size INT;
DECLARE @number_initiator_transactions INT;
DECLARE @initiator_transaction_delay CHAR(12);
DECLARE @number_dialogs INT;
DECLARE @dialog_recycle_max_messages BIGINT;
SET @message_quantity = (SELECT message_quantity FROM data_push_parameters);
SET @message_size = (SELECT message_size FROM data_push_parameters);
SET @number_initiator_transactions = (SELECT number_initiator_transactions FROM data_push_parameters);
SET @initiator_transaction_delay = (SELECT initiator_transaction_delay FROM data_push_parameters);
SET @number_dialogs = (SELECT number_dialogs FROM data_push_parameters);
SET @dialog_recycle_max_messages = (SELECT dialog_recycle_max_messages FROM data_push_parameters);
-- Create a message payload of the requested size.
DECLARE @payload VARCHAR(MAX);
DECLARE @char VARCHAR(MAX);
SET @char = '0';
SELECT @payload = REPLICATE(@char, @message_size);
-- Loop controls.
DECLARE @messages_per_transaction BIGINT;
DECLARE @messages_per_dialog_transaction BIGINT;
DECLARE @transaction_counter INT;
DECLARE @message_counter BIGINT;
-- Compute messages to send per dialog per transaction:
-- @message_quantity / (@number_initiator_transactions x @number_dialogs)
-- Note that integer arithmetic may result in "remainder" messages that will not
-- be sent.
SET @messages_per_transaction = @message_quantity / @number_initiator_transactions;
SET @messages_per_dialog_transaction = @messages_per_transaction / @number_dialogs;
-- Error variables.
DECLARE @error_number INT;
DECLARE @error_message VARCHAR(4000);
-- Show start time.
SELECT GETDATE() AS 'Start sending';
-- Create a table containing requested number of dialogs.
DECLARE @dialogs TABLE (idx INT, handle UNIQUEIDENTIFIER, recycle_counter BIGINT);
DECLARE @idx INT;
DECLARE @handle UNIQUEIDENTIFIER;
DECLARE @recycle_counter BIGINT;
SET @idx = 0;
WHILE @idx < @number_dialogs
BEGIN
BEGIN DIALOG CONVERSATION @handle
FROM SERVICE initiator_service
TO SERVICE 'target_service'
ON CONTRACT data_push_contract
WITH ENCRYPTION = OFF;
INSERT INTO @dialogs (idx, handle, recycle_counter) VALUES (@idx, @handle, 0);
SET @idx = @idx + 1;
END
-- Loop through transactions.
SET @transaction_counter = 0;
WHILE @transaction_counter < @number_initiator_transactions
BEGIN
BEGIN TRANSACTION;
-- Loop through dialogs.
SET @idx = 0;
WHILE @idx < @number_dialogs
BEGIN
-- Send a batch of messages for dialog.
SET @handle = (SELECT handle FROM @dialogs WHERE idx = @idx);
SET @recycle_counter = (SELECT recycle_counter FROM @dialogs WHERE idx = @idx);
SET @message_counter = 0;
WHILE @message_counter < @messages_per_dialog_transaction
BEGIN
-- Time to recycle dialog?
IF @dialog_recycle_max_messages <> -1 AND
@recycle_counter = @dialog_recycle_max_messages
BEGIN
-- Inform target to end dialog.
SEND ON CONVERSATION @handle MESSAGE TYPE end_of_stream;
-- Replace the current dialog.
BEGIN DIALOG CONVERSATION @handle
FROM SERVICE initiator_service
TO SERVICE 'target_service'
ON CONTRACT data_push_contract
WITH ENCRYPTION = OFF;
UPDATE @dialogs SET handle = @handle WHERE idx = @idx;
SET @recycle_counter = 0;
END
-- Send a message.
BEGIN TRY
BEGIN
SEND ON CONVERSATION @handle MESSAGE TYPE data_push_message (@payload);
END
IF @dialog_recycle_max_messages <> -1
BEGIN
SET @recycle_counter = @recycle_counter + 1;
END
SET @message_counter = @message_counter + 1;
END TRY
BEGIN CATCH
SET @error_number = ERROR_NUMBER();
SET @error_message = ERROR_MESSAGE();
-- Dialog is faulty?
DECLARE @dialog_error INT;
SET @dialog_error = 1;
DECLARE @dialog_state VARCHAR(2);
SET @dialog_state = (SELECT state FROM sys.conversation_endpoints
WHERE conversation_handle = @handle);
IF @@ROWCOUNT = 1
BEGIN
-- Good dialog is starting or conversing.
IF @dialog_state = 'SO' OR @dialog_state = 'CO'
BEGIN
SET @dialog_error = 0;
END
END
IF @dialog_error = 1
BEGIN
-- Record the error.
INSERT INTO initiator_processing_errors VALUES(@handle, @error_number,
@error_message, NULL, NULL, NULL, NULL, 0);
-- Replace dialog and continue sending.
BEGIN DIALOG CONVERSATION @handle
FROM SERVICE initiator_service
TO SERVICE 'target_service'
ON CONTRACT data_push_contract
WITH ENCRYPTION = OFF;
UPDATE @dialogs SET handle = @handle WHERE idx = @idx;
SET @recycle_counter = 0;
END
ELSE
BEGIN
-- Record the error and return error.
INSERT INTO initiator_processing_errors VALUES(@handle, @error_number,
@error_message, NULL, NULL, NULL, NULL, 0);
RETURN 1;
END
END CATCH
END
UPDATE @dialogs SET recycle_counter = @recycle_counter WHERE idx = @idx;
SET @idx = @idx + 1;
END
COMMIT;
SET @transaction_counter = @transaction_counter + 1;
-- Wait for next transaction.
IF @transaction_counter < @number_initiator_transactions
BEGIN
WAITFOR DELAY @initiator_transaction_delay;
END
END
-- Gracefully end dialogs by informing target.
BEGIN TRANSACTION;
SET @idx = 0;
WHILE @idx < @number_dialogs
BEGIN
SET @handle = (SELECT handle FROM @dialogs WHERE idx = @idx);
BEGIN
SEND ON CONVERSATION @handle MESSAGE TYPE end_of_stream;
END
SET @idx = @idx + 1;
END
COMMIT;
-- Show end time.
SELECT GETDATE() AS 'End sending';
RETURN 0;
END;
GO
-- Resends all pending messages in sys.transmission_queue
-- belonging to an old colversation on a new conversation.
CREATE PROCEDURE usp_resend_pending (@old_handle UNIQUEIDENTIFIER)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @message_type_name SYSNAME;
DECLARE @message_body VARCHAR(MAX);
-- Get a new dialog.
DECLARE @handle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION @handle
FROM SERVICE initiator_service
TO SERVICE 'target_service'
ON CONTRACT data_push_contract
WITH ENCRYPTION = OFF;
-- Declare a cursor to iterate over all the pending messages.
-- It is important to keep the message order and to keep the original message type.
DECLARE cursor_pending CURSOR LOCAL FORWARD_ONLY READ_ONLY
FOR SELECT message_type_name, message_body
FROM sys.transmission_queue
WHERE conversation_handle = @old_handle
ORDER BY message_sequence_number;
OPEN cursorPending;
FETCH NEXT FROM cursor_pending INTO @message_type_name, @message_body;
WHILE (@@FETCH_STATUS = 0)
BEGIN
-- Resend the message on the new conversation
SEND ON CONVERSATION @handle MESSAGE TYPE @message_type_name (@message_body);
FETCH NEXT FROM cursor_pending INTO @message_type_name, @message_body;
END
CLOSE cursor_pending;
DEALLOCATE cursor_pending;
-- Signal end of stream to target.
SEND ON CONVERSATION @handle MESSAGE TYPE end_of_stream;
END;
GO
-- Activated store proc for the initiator to receive messages.
-- Dialogs are gracefully ended by the target after receiving
-- an end_of_stream message from the initiator; the end dialog
-- message is then processed here. This method is recommended
-- to avoid "fire and forget" message loss. One message per
-- invocation is OK here for expected low-volume load.
CREATE PROCEDURE initiator_queue_activated_procedure
AS
BEGIN
SET NOCOUNT ON;
DECLARE @conversation_handle UNIQUEIDENTIFIER,
@message_type_name SYSNAME,
@message_body VARCHAR(MAX);
-- Error variables.
DECLARE @error_number INT;
DECLARE @error_message VARCHAR(4000);
DECLARE @error_severity INT;
DECLARE @error_state INT;
DECLARE @error_procedure SYSNAME;
DECLARE @error_line INT;
BEGIN TRY
BEGIN TRANSACTION;
-- Wait 5 seconds for a message.
WAITFOR (
RECEIVE TOP(1)
@conversation_handle = conversation_handle,
@message_type_name = message_type_name,
@message_body = message_body
FROM initiator_queue), TIMEOUT 5000;
IF @@ROWCOUNT = 1
BEGIN
IF @message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
BEGIN
-- Target is ending dialog normally.
END CONVERSATION @conversation_handle;
END
ELSE IF @message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
BEGIN
-- Record the error.
WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQL/ServiceBroker/Error' AS ssb)
SELECT
@error_number = CAST(@message_body AS XML).value('(//ssb:Error/ssb:Code)[1]', 'INT'),
@error_message = CAST(@message_body AS XML).value('(//ssb:Error/ssb:Description)[1]', 'VARCHAR(4000)');
INSERT INTO initiator_processing_errors VALUES(@conversation_handle, @error_number,
@error_message, NULL, NULL, NULL, NULL, 0);
-- Can messages be resent?
IF (@error_number IN (-8489, -8462, -9719, -28052))
BEGIN
-- Resend the messages on a new dialog.
EXEC usp_resend_pending @conversation_handle;
END
ELSE
BEGIN
-- Save the messages in a side table to be processed later.
INSERT INTO unsent_messages
SELECT message_type_name, message_body FROM sys.transmission_queue
WHERE conversation_handle = @conversation_handle;
END
-- End the conversation.
END CONVERSATION @conversation_handle;
END
END
COMMIT;
END TRY
BEGIN CATCH
SET @error_number = ERROR_NUMBER();
SET @error_message = ERROR_MESSAGE();
SET @error_severity = ERROR_SEVERITY();
SET @error_state = ERROR_STATE();
SET @error_procedure = ERROR_PROCEDURE();
SET @error_line = ERROR_LINE();
IF XACT_STATE() = -1
BEGIN
-- The transaction is doomed. Only rollback possible.
-- Note: 5 consecutive rollbacks will disable the queue!
ROLLBACK TRANSACTION;
-- Record the error.
BEGIN TRANSACTION;
INSERT INTO initiator_processing_errors VALUES(NULL, @error_number, @error_message,
@error_severity, @error_state, @error_procedure, @error_line, 1);
COMMIT;
END
ELSE IF XACT_STATE() = 1
BEGIN
-- Record error and commit transaction.
INSERT INTO initiator_processing_errors VALUES(NULL, @error_number, @error_message,
@error_severity, @error_state, @error_procedure, @error_line, 0);
COMMIT;
END
END CATCH
END;
GO
-- Create the initiator queue with activated procedure.
CREATE QUEUE initiator_queue
WITH ACTIVATION (
STATUS = ON,
MAX_QUEUE_READERS = 1,
PROCEDURE_NAME = initiator_queue_activated_procedure,
EXECUTE AS OWNER);
GO
-- Create initiator service.
CREATE SERVICE initiator_service ON QUEUE initiator_queue (data_push_contract);
GO
-- Any user can send on the service.
GRANT SEND ON SERVICE::initiator_service TO PUBLIC;
GO
-- This table stores unsent messages.
IF EXISTS (SELECT name FROM sys.tables WHERE name = 'unsent_messages')
DROP TABLE unsent_messages;
GO
CREATE TABLE unsent_messages ( message_type_name SYSNAME, message_body VARCHAR(MAX) );
GO
-- Table to store processing errors.
IF EXISTS (SELECT name FROM sys.tables WHERE name = 'initiator_processing_errors')
DROP TABLE initiator_processing_errors;
GO
CREATE TABLE initiator_processing_errors (error_conversation UNIQUEIDENTIFIER, error_number INT,
error_message VARCHAR(4000), error_severity INT, error_state INT, error_procedure SYSNAME NULL,
error_line INT, doomed_transaction TINYINT)
GO
---------------------------------------------------------------------
-- Routing.
-- Skip the following if services are in the same database instance.
---------------------------------------------------------------------
-- Create a route to the target service.
CREATE ROUTE target_route
WITH SERVICE_NAME = 'target_service',
ADDRESS = 'tcp://target_host:4022';
GO
-- In msdb, create an incoming route to the initiator service.
USE msdb;
GO
CREATE ROUTE initiator_route
WITH SERVICE_NAME = 'initiator_service',
ADDRESS = 'local';
GO
USE data_push_database;
GO
--------------------------------------------------------------------
-- Script for fast data push sample.
--
-- This file is part of the Microsoft SQL Server Code Samples.
-- Copyright (C) Microsoft Corporation. All Rights reserved.
-- This source code is intended only as a supplement to Microsoft
-- Development Tools and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
-- ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
--------------------------------------------------------------------
----------------------------------------------------
-- Target setup for fast data push.
-- Before running, customize the configuration-dependent
-- routing to the initiator service.
----------------------------------------------------
--------------------------------------------------------------------
-- Poison message processing:
--
-- Although this should not occur here because messages are not
-- processed in any significant way, there are times that
-- an application might find itself temporarily unable to process
-- a message. The temptation is then to roll back the receive
-- transaction and try again. The danger of doing this is that
-- 5 consecutive roll backs on a queue will disable it.
-- If a queue does become disabled, possibly due to a doomed
-- transaction, a BROKER_QUEUE_DISABLED event notification can
-- be used as a recovery mechanism. You can also use the TRY-CATCH
-- construct to process transaction errors as shown below.
--------------------------------------------------------------------
USE data_push_database;
GO
-- Activated store proc for the target to receive messages.
CREATE PROCEDURE target_queue_activated_procedure
AS
BEGIN
SET NOCOUNT ON;
-- Variable table for received messages.
DECLARE @receive_table TABLE(
queuing_order BIGINT,
conversation_handle UNIQUEIDENTIFIER,
message_type_name SYSNAME,
message_body VARCHAR(MAX));
-- Cursor for received message table.
DECLARE message_cursor CURSOR LOCAL FORWARD_ONLY READ_ONLY
FOR SELECT
conversation_handle,
message_type_name,
message_body
FROM @receive_table ORDER BY queuing_order;
DECLARE @conversation_handle UNIQUEIDENTIFIER,
@message_type_name SYSNAME,
@message_body VARCHAR(MAX);
-- Count processed messages.
DECLARE @message_counter BIGINT;
SET @message_counter = 0;
-- Error variables.
DECLARE @error_number INT;
DECLARE @error_message VARCHAR(4000);
DECLARE @error_severity INT;
DECLARE @error_state INT;
DECLARE @error_procedure SYSNAME;
DECLARE @error_line INT;
-- Get target parameters.
DECLARE @message_processing_time CHAR(12);
SET @message_processing_time = (SELECT message_processing_time FROM data_push_parameters);
DECLARE @max_messages_per_receive BIGINT;
SET @max_messages_per_receive = (SELECT max_messages_per_receive FROM data_push_parameters);
-- Receive messages for available conversation groups.
BEGIN TRY
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
-- Receive max available messages into the table.
-- Wait 5 seconds for messages.
WAITFOR (
RECEIVE TOP(@max_messages_per_receive)
queuing_order,
conversation_handle,
message_type_name,
message_body
FROM target_queue
INTO @receive_table
), TIMEOUT 5000;
IF @@ROWCOUNT = 0
BEGIN
COMMIT;
BREAK;
END
-- Process the messages.
OPEN message_cursor;
WHILE (1=1)
BEGIN
FETCH NEXT FROM message_cursor
INTO @conversation_handle,
@message_type_name,
@message_body;
IF (@@FETCH_STATUS != 0) BREAK;
-- Process a message.
-- If an exception occurs, catch and attempt to recover.
BEGIN TRY
IF @message_type_name = 'data_push_message'
BEGIN
-- Process the message for the specified amount of time.
WAITFOR DELAY @message_processing_time;
SET @message_counter = @message_counter + 1;
END
ELSE IF @message_type_name = 'end_of_stream'
BEGIN
-- Initiator is signaling end of message stream: end the dialog.
END CONVERSATION @conversation_handle;
END
ELSE IF @message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
BEGIN
-- If the message_type_name indicates that the message is an error,
-- record the error and end the conversation.
WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQL/ServiceBroker/Error' AS ssb)
SELECT
@error_number = CAST(@message_body AS XML).value('(//ssb:Error/ssb:Code)[1]', 'INT'),
@error_message = CAST(@message_body AS XML).value('(//ssb:Error/ssb:Description)[1]', 'VARCHAR(4000)');
INSERT INTO target_processing_errors VALUES(@conversation_handle, @error_number,
@error_message, NULL, NULL, NULL, NULL, 0);
END CONVERSATION @conversation_handle;
END
END TRY
BEGIN CATCH
SET @error_number = ERROR_NUMBER();
SET @error_message = ERROR_MESSAGE();
SET @error_severity = ERROR_SEVERITY();
SET @error_state = ERROR_STATE();
SET @error_procedure = ERROR_PROCEDURE();
SET @error_line = ERROR_LINE();
IF XACT_STATE() = -1
BEGIN
-- The transaction is doomed. Only rollback possible.
-- This could disable the queue if done 5 times consecutively!
ROLLBACK TRANSACTION;
-- Record the error.
BEGIN TRANSACTION;
INSERT INTO target_processing_errors VALUES(NULL, @error_number, @error_message,
@error_severity, @error_state, @error_procedure, @error_line, 1);
COMMIT;
-- For this level of error, it is best to exit the proc
-- and give the queue monitor control.
-- Breaking to the outer catch will accomplish this.
RAISERROR ('Message processing error', 16, 1);
END
ELSE IF XACT_STATE() = 1
BEGIN
-- Record error and continue processing messages.
-- Failing message could also be put aside for later processing here.
-- Otherwise it will be discarded.
INSERT INTO target_processing_errors VALUES(NULL, @error_number, @error_message,
@error_severity, @error_state, @error_procedure, @error_line, 0);
END
END CATCH
END
CLOSE message_cursor;
DELETE @receive_table;
COMMIT;
END
END TRY
BEGIN CATCH
-- Process the error and exit the proc to give the queue monitor control
SET @error_number = ERROR_NUMBER();
SET @error_message = ERROR_MESSAGE();
SET @error_severity = ERROR_SEVERITY();
SET @error_state = ERROR_STATE();
SET @error_procedure = ERROR_PROCEDURE();
SET @error_line = ERROR_LINE();
IF XACT_STATE() = -1
BEGIN
-- The transaction is doomed. Only rollback possible.
-- This could disable the queue if done 5 times consecutively!
ROLLBACK TRANSACTION;
-- Record the error.
BEGIN TRANSACTION;
INSERT INTO target_processing_errors VALUES(NULL, @error_number, @error_message,
@error_severity, @error_state, @error_procedure, @error_line, 1);
COMMIT;
END
ELSE IF XACT_STATE() = 1
BEGIN
-- Record error and commit transaction.
-- Here you could also save anything else you want before exiting.
INSERT INTO target_processing_errors VALUES(NULL, @error_number, @error_message,
@error_severity, @error_state, @error_procedure, @error_line, 0);
COMMIT;
END
END CATCH
-- Increment processed message counter.
BEGIN TRANSACTION;
DECLARE @counter BIGINT;
SET @counter = (SELECT TOP(1) counter FROM target_message_counter);
SET @counter = @counter + @message_counter;
UPDATE target_message_counter SET counter = @counter;
COMMIT;
END;
GO
-- Get number of activated target procedures parameter.
DECLARE @number_target_procedures INT;
SET @number_target_procedures = (SELECT number_target_procedures FROM data_push_parameters);
-- Create the target queue with specified number of activated procedures.
DECLARE @query VARCHAR(500);
DECLARE @string VARCHAR(50);
SET @string = (SELECT CAST( @number_target_procedures AS VARCHAR(50)));
SET @query = 'CREATE QUEUE target_queue
WITH ACTIVATION (
STATUS = ON,
MAX_QUEUE_READERS = ' + @string + ',
PROCEDURE_NAME = target_queue_activated_procedure,
EXECUTE AS OWNER)';
EXEC (@query);
GO
-- Create target service.
CREATE SERVICE target_service ON QUEUE target_queue (data_push_contract);
GO
-- Any user can send on the service.
GRANT SEND ON SERVICE::target_service TO PUBLIC;
GO
-- Table to count processed messages.
IF EXISTS (SELECT name FROM sys.tables WHERE name = 'target_message_counter')
DROP TABLE message_counter;
GO
CREATE TABLE target_message_counter (counter BIGINT NOT NULL);
GO
INSERT INTO target_message_counter VALUES (0);
GO
-- Table to store processing errors.
IF EXISTS (SELECT name FROM sys.tables WHERE name = 'target_processing_errors')
DROP TABLE target_processing_errors;
GO
CREATE TABLE target_processing_errors (error_conversation UNIQUEIDENTIFIER, error_number INT,
error_message VARCHAR(4000), error_severity INT, error_state INT, error_procedure SYSNAME NULL,
error_line INT, doomed_transaction TINYINT)
GO
---------------------------------------------------------------------
-- Get size of a message queue.
-- Method used is faster than SQL count operator.
---------------------------------------------------------------------
CREATE PROCEDURE usp_get_queue_size ( @queue_name VARCHAR(50) )
AS
BEGIN
SELECT p.rows
FROM sys.objects AS o
JOIN sys.partitions AS p ON p.object_id = o.object_id
JOIN sys.objects AS q ON o.parent_object_id = q.object_id
WHERE q.name = @queue_name
AND p.index_id = 1;
END;
GO
---------------------------------------------------------------------
-- Routing.
-- Skip the following if services are in the same database instance.
---------------------------------------------------------------------
-- Create a route to the initiator service.
CREATE ROUTE initiator_route
WITH SERVICE_NAME = 'initiator_service',
ADDRESS = 'tcp://initiator_host:4022';
GO
-- In msdb, create an incoming route to the target service.
USE msdb;
GO
CREATE ROUTE target_route
WITH SERVICE_NAME = 'target_service',
ADDRESS = 'local';
GO
USE data_push_database;
GO
--------------------------------------------------------------------
-- Script for fast data push sample.
--
-- This file is part of the Microsoft SQL Server Code Samples.
-- Copyright (C) Microsoft Corporation. All Rights reserved.
-- This source code is intended only as a supplement to Microsoft
-- Development Tools and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
-- ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
--------------------------------------------------------------------
---------------------------------------------------------------------
-- Initiator data push.
--
-- This script can also be run on multiple connections to achieve transactional
-- concurrency. Since each connection will send message_quantity messages
-- with number_initiator_transactions transactions and number_dialogs
-- dialogs, these parameter quantities should be divided by the number
-- of connections to get the same total quantities.
---------------------------------------------------------------------
USE data_push_database;
GO
-- Send the messages. Output sending time.
EXEC usp_data_push;
GO
---------------------------------------------------------------------
-- Wait for transmission queue to be empty, signifying the
-- reception and acknowledgement of all messages by the target.
-- Use this efficient checking method every 5 seconds.
---------------------------------------------------------------------
DECLARE @count BIGINT;
WHILE (1=1)
BEGIN
SET @count =
(SELECT p.rows
FROM sys.objects AS o
JOIN sys.partitions AS p ON p.object_id = o.object_id
WHERE o.name = 'sysxmitqueue');
IF (@count = 0) BREAK;
WAITFOR DELAY '00:00:05:000';
END;
SELECT GETDATE() AS 'End transmission';
GO
-- View processing errors.
SELECT * FROM initiator_processing_errors;
GO
-- View unsent messages.
SELECT * FROM unsent_messages;
GO
--------------------------------------------------------------------
-- Script for fast data push sample.
--
-- This file is part of the Microsoft SQL Server Code Samples.
-- Copyright (C) Microsoft Corporation. All Rights reserved.
-- This source code is intended only as a supplement to Microsoft
-- Development Tools and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
-- ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
--------------------------------------------------------------------
----------------------------------------------------
-- Monitor target.
--
-- A count of processed messages is kept in the
-- target_message_counter table.
-- The usp_get_queue_size procedure can also be used to
-- monitor the size of the target queue.
----------------------------------------------------
USE data_push_database;
GO
-- Wait for message_quantity messages to be processed.
DECLARE @message_quantity BIGINT;
SET @message_quantity = (SELECT message_quantity FROM data_push_parameters);
DECLARE @count BIGINT;
WHILE (1=1)
BEGIN
SET @count = (SELECT TOP(1) counter FROM target_message_counter);
IF (@count >= @message_quantity) BREAK;
WAITFOR DELAY '00:00:05:000';
END;
SELECT GETDATE() AS 'Messages processed';
GO
-- View message processing errors.
SELECT * FROM target_processing_errors;
GO
-- Clear processed message counter for next run.
UPDATE target_message_counter SET counter = 0;
GO
--------------------------------------------------------------------
-- Script for data push sample.
--
-- This file is part of the Microsoft SQL Server Code Samples.
-- Copyright (C) Microsoft Corporation. All Rights reserved.
-- This source code is intended only as a supplement to Microsoft
-- Development Tools and/or on-line documentation. See these other
-- materials for detailed information regarding Microsoft code samples.
--
-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
-- ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
-- PARTICULAR PURPOSE.
--------------------------------------------------------------------
----------------------------------------------------
-- Cleanup for fast data push.
-- Before running, replace the configuration-dependent
-- domain_name and partner_host names.
----------------------------------------------------
USE master;
GO
IF EXISTS (SELECT * FROM sys.databases WHERE name = 'data_push_database')
DROP DATABASE data_push_database;
GO
USE msdb;
GO
IF EXISTS (SELECT * FROM sys.routes WHERE name = 'initiator_route')
DROP ROUTE initiator_route;
GO
IF EXISTS (SELECT * FROM sys.routes WHERE name = 'target_route')
DROP ROUTE target_route;
GO
USE master;
IF EXISTS (SELECT * FROM sys.endpoints WHERE name = 'service_broker_endpoint')
DROP ENDPOINT service_broker_endpoint;
GO
IF EXISTS (SELECT * FROM sys.syslogins WHERE name = 'domain_name\partner_host$')
DROP LOGIN [domain_name\partner_host$];
GO