@@ -77,19 +77,28 @@ public class Connection : IConnection
77
77
public EventHandler < EventArgs > m_connectionUnblocked ;
78
78
public IConnectionFactory m_factory ;
79
79
public IFrameHandler m_frameHandler ;
80
- public ushort m_heartbeat = 0 ;
81
- public TimeSpan m_heartbeatTimeSpan = TimeSpan . FromSeconds ( 0 ) ;
82
80
83
81
public Guid m_id = Guid . NewGuid ( ) ;
84
-
85
- public int m_missedHeartbeats = 0 ;
86
82
public ModelBase m_model0 ;
87
83
public volatile bool m_running = true ;
88
84
public MainSession m_session0 ;
89
85
public SessionManager m_sessionManager ;
90
86
91
87
public IList < ShutdownReportEntry > m_shutdownReport = new SynchronizedList < ShutdownReportEntry > ( new List < ShutdownReportEntry > ( ) ) ;
88
+
89
+ //
90
+ // Heartbeats
91
+ //
92
+
93
+ public ushort m_heartbeat = 0 ;
94
+ public TimeSpan m_heartbeatTimeSpan = TimeSpan . FromSeconds ( 0 ) ;
95
+ public int m_missedHeartbeats = 0 ;
96
+
92
97
private Timer _heartbeatWriteTimer ;
98
+ private Timer _heartbeatReadTimer ;
99
+ public AutoResetEvent m_heartbeatRead = new AutoResetEvent ( false ) ;
100
+ public AutoResetEvent m_heartbeatWrite = new AutoResetEvent ( false ) ;
101
+
93
102
94
103
// true if we haven't finished connection negotiation.
95
104
// In this state socket exceptions are treated as fatal connection
@@ -113,7 +122,7 @@ public Connection(IConnectionFactory factory, bool insist, IFrameHandler frameHa
113
122
StartMainLoop ( factory . UseBackgroundThreadsForIO ) ;
114
123
Open ( insist ) ;
115
124
116
- StartHeartbeatTimer ( ) ;
125
+ StartHeartbeatTimers ( ) ;
117
126
AppDomain . CurrentDomain . DomainUnload += HandleDomainUnload ;
118
127
}
119
128
@@ -465,8 +474,10 @@ public void EnsureIsOpen()
465
474
public void FinishClose ( )
466
475
{
467
476
// Notify hearbeat loops that they can leave
477
+ m_heartbeatRead . Set ( ) ;
478
+ m_heartbeatWrite . Set ( ) ;
468
479
m_closed = true ;
469
- StopHeartbeatTimer ( ) ;
480
+ StopHeartbeatTimers ( ) ;
470
481
471
482
m_frameHandler . Close ( ) ;
472
483
m_model0 . SetCloseReason ( m_closeReason ) ;
@@ -522,33 +533,6 @@ public bool HardProtocolExceptionHandler(HardProtocolException hpe)
522
533
return false ;
523
534
}
524
535
525
- public void HeartbeatWriteTimerCallback ( object state )
526
- {
527
- bool shouldTerminate = false ;
528
- try
529
- {
530
- if ( ! m_closed )
531
- {
532
- WriteFrame ( m_heartbeatFrame ) ;
533
- }
534
- }
535
- catch ( Exception e )
536
- {
537
- HandleMainLoopException ( new ShutdownEventArgs (
538
- ShutdownInitiator . Library ,
539
- 0 ,
540
- "End of stream" ,
541
- e ) ) ;
542
- shouldTerminate = true ;
543
- }
544
-
545
- if ( m_closed || shouldTerminate )
546
- {
547
- TerminateMainloop ( ) ;
548
- FinishClose ( ) ;
549
- }
550
- }
551
-
552
536
public void InternalClose ( ShutdownEventArgs reason )
553
537
{
554
538
if ( ! SetCloseReason ( reason ) )
@@ -639,96 +623,62 @@ public void MainLoop()
639
623
640
624
public void MainLoopIteration ( )
641
625
{
642
- try
643
- {
644
- Frame frame = m_frameHandler . ReadFrame ( ) ;
626
+ Frame frame = m_frameHandler . ReadFrame ( ) ;
645
627
646
- // We have received an actual frame.
647
- m_missedHeartbeats = 0 ;
648
- if ( frame . Type == Constants . FrameHeartbeat )
649
- {
650
- // Ignore it: we've already just reset the heartbeat
651
- // counter .
652
- return ;
653
- }
628
+ NotifyHeartbeatListener ( ) ;
629
+ // We have received an actual frame.
630
+ if ( frame . Type == Constants . FrameHeartbeat )
631
+ {
632
+ // Ignore it: we've already just reset the heartbeat
633
+ // latch .
634
+ return ;
635
+ }
654
636
655
- if ( frame . Channel == 0 )
656
- {
657
- // In theory, we could get non-connection.close-ok
658
- // frames here while we're quiescing (m_closeReason !=
659
- // null). In practice, there's a limited number of
660
- // things the server can ask of us on channel 0 -
661
- // essentially, just connection.close. That, combined
662
- // with the restrictions on pipelining, mean that
663
- // we're OK here to handle channel 0 traffic in a
664
- // quiescing situation, even though technically we
665
- // should be ignoring everything except
666
- // connection.close-ok.
667
- m_session0 . HandleFrame ( frame ) ;
668
- }
669
- else
637
+ if ( frame . Channel == 0 )
638
+ {
639
+ // In theory, we could get non-connection.close-ok
640
+ // frames here while we're quiescing (m_closeReason !=
641
+ // null). In practice, there's a limited number of
642
+ // things the server can ask of us on channel 0 -
643
+ // essentially, just connection.close. That, combined
644
+ // with the restrictions on pipelining, mean that
645
+ // we're OK here to handle channel 0 traffic in a
646
+ // quiescing situation, even though technically we
647
+ // should be ignoring everything except
648
+ // connection.close-ok.
649
+ m_session0 . HandleFrame ( frame ) ;
650
+ }
651
+ else
652
+ {
653
+ // If we're still m_running, but have a m_closeReason,
654
+ // then we must be quiescing, which means any inbound
655
+ // frames for non-zero channels (and any inbound
656
+ // commands on channel zero that aren't
657
+ // Connection.CloseOk) must be discarded.
658
+ if ( m_closeReason == null )
670
659
{
671
- // If we're still m_running, but have a m_closeReason,
672
- // then we must be quiescing, which means any inbound
673
- // frames for non-zero channels (and any inbound
674
- // commands on channel zero that aren't
675
- // Connection.CloseOk) must be discarded.
676
- if ( m_closeReason == null )
660
+ // No close reason, not quiescing the
661
+ // connection. Handle the frame. (Of course, the
662
+ // Session itself may be quiescing this particular
663
+ // channel, but that's none of our concern.)
664
+ ISession session = m_sessionManager . Lookup ( frame . Channel ) ;
665
+ if ( session == null )
677
666
{
678
- // No close reason, not quiescing the
679
- // connection. Handle the frame. (Of course, the
680
- // Session itself may be quiescing this particular
681
- // channel, but that's none of our concern.)
682
- ISession session = m_sessionManager . Lookup ( frame . Channel ) ;
683
- if ( session == null )
684
- {
685
- throw new ChannelErrorException ( frame . Channel ) ;
686
- }
687
- else
688
- {
689
- session . HandleFrame ( frame ) ;
690
- }
667
+ throw new ChannelErrorException ( frame . Channel ) ;
668
+ }
669
+ else
670
+ {
671
+ session . HandleFrame ( frame ) ;
691
672
}
692
673
}
693
674
}
694
- catch ( SocketException ioe )
695
- {
696
- HandleIOException ( ioe ) ;
697
- }
698
- catch ( IOException ioe )
699
- {
700
- HandleIOException ( ioe ) ;
701
- }
702
675
}
703
676
704
- // socket receive timeout is configured to be 1/2 of the heartbeat timeout
705
- // and the peer must be considered dead after two subsequent missed heartbeats:
706
- // terminate after 4 socket timeouts
707
- private const int SOCKET_TIMEOUTS_TO_CONSIDER_PEER_UNRESPONSIVE = 4 ;
708
-
709
- protected void HandleIOException ( Exception e )
677
+ public void NotifyHeartbeatListener ( )
710
678
{
711
- // socket error when in negotiation, throw BrokerUnreachableException
712
- // immediately
713
- if ( m_inConnectionNegotiation )
714
- {
715
- var cfe = new ConnectFailureException ( "I/O error before connection negotiation was completed" , e ) ;
716
- throw new BrokerUnreachableException ( cfe ) ;
717
- }
718
-
719
- if ( ++ m_missedHeartbeats >= SOCKET_TIMEOUTS_TO_CONSIDER_PEER_UNRESPONSIVE )
679
+ if ( m_heartbeat != 0 )
720
680
{
721
- var description =
722
- String . Format ( "Peer missed 2 heartbeats with heartbeat timeout set to {0} seconds" ,
723
- m_heartbeat ) ;
724
- var eose = new EndOfStreamException ( description ) ;
725
- m_shutdownReport . Add ( new ShutdownReportEntry ( description , eose ) ) ;
726
- HandleMainLoopException ( new ShutdownEventArgs ( ShutdownInitiator . Library ,
727
- 0 ,
728
- "End of stream" ,
729
- eose ) ) ;
730
- TerminateMainloop ( ) ;
731
- FinishClose ( ) ;
681
+ m_heartbeatRead . Set ( ) ;
732
682
}
733
683
}
734
684
@@ -946,12 +896,15 @@ public bool SetCloseReason(ShutdownEventArgs reason)
946
896
}
947
897
}
948
898
949
- public void StartHeartbeatTimer ( )
899
+ public void StartHeartbeatTimers ( )
950
900
{
951
901
if ( Heartbeat != 0 )
952
902
{
953
903
_heartbeatWriteTimer = new Timer ( HeartbeatWriteTimerCallback ) ;
954
- _heartbeatWriteTimer . Change ( TimeSpan . FromMilliseconds ( 0 ) , m_heartbeatTimeSpan ) ;
904
+ _heartbeatWriteTimer . Change ( TimeSpan . FromMilliseconds ( 0 ) , TimeSpan . FromMilliseconds ( - 1 ) ) ;
905
+
906
+ _heartbeatReadTimer = new Timer ( HeartbeatReadTimerCallback ) ;
907
+ _heartbeatReadTimer . Change ( TimeSpan . FromMilliseconds ( 0 ) , TimeSpan . FromMilliseconds ( - 1 ) ) ;
955
908
}
956
909
}
957
910
@@ -963,9 +916,84 @@ public void StartMainLoop(bool useBackgroundThread)
963
916
mainLoopThread . Start ( ) ;
964
917
}
965
918
966
- protected void StopHeartbeatTimer ( )
919
+ public void HeartbeatReadTimerCallback ( object state )
920
+ {
921
+ bool shouldTerminate = false ;
922
+ if ( ! m_closed )
923
+ {
924
+ if ( ! m_heartbeatRead . WaitOne ( 0 , false ) )
925
+ {
926
+ m_missedHeartbeats ++ ;
927
+ }
928
+ else
929
+ {
930
+ m_missedHeartbeats = 0 ;
931
+ }
932
+
933
+ // Has to miss two full heartbeats to force socket close
934
+ if ( m_missedHeartbeats > 1 )
935
+ {
936
+ String description = String . Format ( "Heartbeat missing with heartbeat == {0} seconds" , m_heartbeat ) ;
937
+ var eose = new EndOfStreamException ( description ) ;
938
+ m_shutdownReport . Add ( new ShutdownReportEntry ( description , eose ) ) ;
939
+ HandleMainLoopException (
940
+ new ShutdownEventArgs ( ShutdownInitiator . Library , 0 , "End of stream" , eose ) ) ;
941
+ shouldTerminate = true ;
942
+ }
943
+ }
944
+
945
+ if ( shouldTerminate )
946
+ {
947
+ TerminateMainloop ( ) ;
948
+ FinishClose ( ) ;
949
+ }
950
+ else
951
+ {
952
+ _heartbeatReadTimer . Change ( Heartbeat * 1000 , Timeout . Infinite ) ;
953
+ }
954
+ }
955
+
956
+ public void HeartbeatWriteTimerCallback ( object state )
967
957
{
968
- if ( _heartbeatWriteTimer != null )
958
+ bool shouldTerminate = false ;
959
+ try
960
+ {
961
+ if ( ! m_closed )
962
+ {
963
+ if ( ! m_heartbeatWrite . WaitOne ( 0 , false ) )
964
+ {
965
+ WriteFrame ( m_heartbeatFrame ) ;
966
+ }
967
+ }
968
+ }
969
+ catch ( Exception e )
970
+ {
971
+ HandleMainLoopException ( new ShutdownEventArgs (
972
+ ShutdownInitiator . Library ,
973
+ 0 ,
974
+ "End of stream" ,
975
+ e ) ) ;
976
+ shouldTerminate = true ;
977
+ }
978
+
979
+ if ( m_closed || shouldTerminate )
980
+ {
981
+ TerminateMainloop ( ) ;
982
+ FinishClose ( ) ;
983
+ }
984
+ else
985
+ {
986
+ _heartbeatWriteTimer . Change ( Heartbeat * 1000 , Timeout . Infinite ) ;
987
+ }
988
+ }
989
+
990
+ protected void StopHeartbeatTimers ( )
991
+ {
992
+ if ( _heartbeatReadTimer != null )
993
+ {
994
+ _heartbeatReadTimer . Dispose ( ) ;
995
+ }
996
+ if ( _heartbeatWriteTimer != null )
969
997
{
970
998
_heartbeatWriteTimer . Dispose ( ) ;
971
999
}
@@ -976,7 +1004,7 @@ protected void StopHeartbeatTimer()
976
1004
///</remarks>
977
1005
public void TerminateMainloop ( )
978
1006
{
979
- StopHeartbeatTimer ( ) ;
1007
+ StopHeartbeatTimers ( ) ;
980
1008
m_running = false ;
981
1009
}
982
1010
@@ -988,6 +1016,7 @@ public override string ToString()
988
1016
public void WriteFrame ( Frame f )
989
1017
{
990
1018
m_frameHandler . WriteFrame ( f ) ;
1019
+ m_heartbeatWrite . Set ( ) ;
991
1020
}
992
1021
993
1022
///<summary>API-side invocation of connection abort.</summary>
@@ -1060,7 +1089,7 @@ public void HandleConnectionUnblocked()
1060
1089
1061
1090
void IDisposable . Dispose ( )
1062
1091
{
1063
- StopHeartbeatTimer ( ) ;
1092
+ StopHeartbeatTimers ( ) ;
1064
1093
Abort ( ) ;
1065
1094
if ( ShutdownReport . Count > 0 )
1066
1095
{
0 commit comments