/// <summary>
      /// This is the method that actually receives Service Broker messages.
      /// </summary>
      /// <param name="timeout">Maximum time to wait for a message.  This is passed to the RECIEVE command, not used as a SqlCommandTimeout</param>
      /// <param name="cancelEvent">An event to cancel the wait.  Async ADO.NET is used to enable the thread to wait for either completion or cancel</param>
      /// <returns></returns>
        SqlDataReader GetMessageBatch(TimeSpan timeout, WaitHandle cancelEvent)
        {
            string SQL = string.Format(@"
            waitfor( 
                RECEIVE conversation_handle,service_name,message_type_name,message_body,message_sequence_number 
                FROM [{0}] WHERE conversation_group_id = @cgid
                    ), timeout @timeout", this.serviceInfo.QueueName);
            SqlCommand cmd = new SqlCommand(SQL, this.con);

            SqlParameter pConversation = cmd.Parameters.Add("@cgid", SqlDbType.UniqueIdentifier);
            pConversation.Value = this.cgId;

            SqlParameter pTimeout = cmd.Parameters.Add("@timeout", SqlDbType.Int);

            pTimeout.Value = TimeoutHelper.ToMilliseconds(timeout);

            cmd.CommandTimeout = 0; //honor the RECIEVE timeout, whatever it is.

            
            //Run the command, but abort if the another thread wants to run Close or Abort
            IAsyncResult result = cmd.BeginExecuteReader(CommandBehavior.SequentialAccess);
            int rc = WaitHandle.WaitAny(new WaitHandle[] { result.AsyncWaitHandle, cancelEvent });
            if (rc == 1) //cancel event
            {
                cmd.Cancel();
                TraceHelper.TraceEvent(System.Diagnostics.TraceEventType.Verbose,"Canceling Service Broker wait on ConversationGroupReciever shutdown","GetMessageBatch");
                return null;
            }
            if (rc != 0)
            {
                throw new InvalidOperationException("Unexpected state");
            }
            return cmd.EndExecuteReader(result);
        }
        internal SsbConversationGroupReceiver AcceptConversationGroup(TimeSpan timeout)
        {
            TimeoutHelper helper = new TimeoutHelper(timeout);

            try
            {
                //create a transaction to own the conversation.  Open the SqlConnection while
                //the transaction is current, so all the work on the connection will be enlisted
                //in the transaction.  SqlTransaction doesn't work this way.  It requires every command
                //to be manually enlisted, yuck.
                //As an alternative you could simply issue a new SqlCommand("begin transaction",cn).ExecuteNonQuery()
                //to start a transaction.
                // TODO (dbrowne) Consider whether to allow service-level code to commit this transaction, or, alternatively,
                //commit the transaction after the last conversation message in this session is processed.
                //If service-level code is allowed to commit the transaction then SsbConversationGroupReciver _must_
                //retrieve only a single message in each batch.  Else it could fetch 2 messages and risk the service-level
                //commiting after the first message.


                CommittableTransaction tx           = new CommittableTransaction(TimeSpan.MaxValue);
                Transaction            currentTrans = Transaction.Current;
                Transaction.Current = tx;
                SqlConnection cn = SsbHelper.GetConnection(this.connstring);
                cn.Open();
                Transaction.Current = currentTrans;

                tx.TransactionCompleted += new TransactionCompletedEventHandler(tx_TransactionCompleted);

                //wait for a new message, but if that message is in a conversation group that another SsbChannelListener is waiting on
                //then roll back the conversation group lock and get back in line.  The waiting SsbChannelListener should pick it up.
                string sql = String.Format(@"
                    retry:
                    save transaction swiper_no_swiping
                    declare @cg uniqueidentifier
                    declare @rc int

                    waitfor (get conversation group @cg from [{0}]), TIMEOUT @timeout
                    if @cg is null
                    begin
                      return --timeout
                    end 

                    exec @rc = sp_getapplock @Resource=@cg, @LockMode='Exclusive', @LockTimeout = 0
                    if @rc = -1
                    begin
                     print 'skipping message for locked conversation_group'
                     rollback transaction swiper_no_swiping
                     goto retry
                    end
                    
                    set @conversation_group_id = @cg
                    ", this.serviceInfo.QueueName);

                SqlCommand cmd = new SqlCommand(sql, cn);
                cmd.CommandTimeout = 0;

                cn.InfoMessage += new SqlInfoMessageEventHandler(cn_InfoMessage);
                SqlParameter pTimeout = cmd.Parameters.Add("@timeout", SqlDbType.Int);
                pTimeout.Value = TimeoutHelper.ToMilliseconds(timeout);

                SqlParameter pConversationGroupId = cmd.Parameters.Add("@conversation_group_id", SqlDbType.UniqueIdentifier);
                pConversationGroupId.Direction = ParameterDirection.Output;


                //Run the command, but abort if the another thread runs Close or Abort
                IAsyncResult result = cmd.BeginExecuteNonQuery();
                int          rc     = WaitHandle.WaitAny(new WaitHandle[] { result.AsyncWaitHandle, cancelEvent });
                if (rc == 1) //cancel event
                {
                    cmd.Cancel();
                    TraceHelper.TraceEvent(System.Diagnostics.TraceEventType.Verbose, "Canceling Service Broker wait on SsbChannelListener shutdown", "AcceptConversationGroup");
                    cn.Close();
                    return(null);
                }
                if (rc != 0)
                {
                    throw new InvalidOperationException("Unexpected state");
                }
                cmd.EndExecuteNonQuery(result);

                if (pConversationGroupId.Value == null || pConversationGroupId.Value.GetType() != typeof(Guid))
                {
                    throw new TimeoutException(String.Format("Timed out while waiting for Conversation Group"));
                }

                Guid conversationGroupId = (Guid)pConversationGroupId.Value;

                TraceHelper.TraceEvent(System.Diagnostics.TraceEventType.Verbose, string.Format("Accepted conversation group {0}", conversationGroupId), "AcceptConversationGroup");
                SsbConversationGroupReceiver cg = new SsbConversationGroupReceiver(cn, tx, conversationGroupId, this.serviceInfo, this);
                return(cg);
            }
            catch (SqlException ex)
            {
                if (!helper.IsTimeRemaining)
                {
                    throw new TimeoutException(String.Format("Timed out while waiting for Conversation Group. Timeout value was {0} seconds", timeout.TotalSeconds), ex);
                }
                else
                {
                    throw new ProtocolException("An error occurred while waiting for a Conversation Group", ex);
                }
            }
        }
 public void SetTimer(TimerCallback callback, Object state)
 {
     Timer timer = new Timer(callback, state, TimeoutHelper.ToMilliseconds(this.RemainingTime()), Timeout.Infinite);
 }
 public int RemainingTimeInMilliseconds()
 {
     return(TimeoutHelper.ToMilliseconds(this.RemainingTime()));
 }