@@ -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,6 @@ public Connection(IConnectionFactory factory, bool insist, IFrameHandler frameHa
113
122
StartMainLoop ( factory . UseBackgroundThreadsForIO ) ;
114
123
Open ( insist ) ;
115
124
116
- StartHeartbeatTimer ( ) ;
117
125
AppDomain . CurrentDomain . DomainUnload += HandleDomainUnload ;
118
126
}
119
127
@@ -233,10 +241,10 @@ public ushort Heartbeat
233
241
set
234
242
{
235
243
m_heartbeat = value ;
236
- // timers fire at half the interval to avoid race
244
+ // timers fire at slightly below half the interval to avoid race
237
245
// conditions
238
- m_heartbeatTimeSpan = TimeSpan . FromMilliseconds ( ( value * 1000 ) / 2.0 ) ;
239
- m_frameHandler . Timeout = ( value * 1000 ) / 2 ;
246
+ m_heartbeatTimeSpan = TimeSpan . FromMilliseconds ( ( value * 1000 ) / 4 ) ;
247
+ m_frameHandler . Timeout = value * 1000 * 2 ;
240
248
}
241
249
}
242
250
@@ -465,8 +473,10 @@ public void EnsureIsOpen()
465
473
public void FinishClose ( )
466
474
{
467
475
// Notify hearbeat loops that they can leave
476
+ m_heartbeatRead . Set ( ) ;
477
+ m_heartbeatWrite . Set ( ) ;
468
478
m_closed = true ;
469
- StopHeartbeatTimer ( ) ;
479
+ MaybeStopHeartbeatTimers ( ) ;
470
480
471
481
m_frameHandler . Close ( ) ;
472
482
m_model0 . SetCloseReason ( m_closeReason ) ;
@@ -522,33 +532,6 @@ public bool HardProtocolExceptionHandler(HardProtocolException hpe)
522
532
return false ;
523
533
}
524
534
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
535
public void InternalClose ( ShutdownEventArgs reason )
553
536
{
554
537
if ( ! SetCloseReason ( reason ) )
@@ -639,96 +622,62 @@ public void MainLoop()
639
622
640
623
public void MainLoopIteration ( )
641
624
{
642
- try
643
- {
644
- Frame frame = m_frameHandler . ReadFrame ( ) ;
625
+ Frame frame = m_frameHandler . ReadFrame ( ) ;
645
626
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
- }
627
+ NotifyHeartbeatListener ( ) ;
628
+ // We have received an actual frame.
629
+ if ( frame . Type == Constants . FrameHeartbeat )
630
+ {
631
+ // Ignore it: we've already just reset the heartbeat
632
+ // latch .
633
+ return ;
634
+ }
654
635
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
636
+ if ( frame . Channel == 0 )
637
+ {
638
+ // In theory, we could get non-connection.close-ok
639
+ // frames here while we're quiescing (m_closeReason !=
640
+ // null). In practice, there's a limited number of
641
+ // things the server can ask of us on channel 0 -
642
+ // essentially, just connection.close. That, combined
643
+ // with the restrictions on pipelining, mean that
644
+ // we're OK here to handle channel 0 traffic in a
645
+ // quiescing situation, even though technically we
646
+ // should be ignoring everything except
647
+ // connection.close-ok.
648
+ m_session0 . HandleFrame ( frame ) ;
649
+ }
650
+ else
651
+ {
652
+ // If we're still m_running, but have a m_closeReason,
653
+ // then we must be quiescing, which means any inbound
654
+ // frames for non-zero channels (and any inbound
655
+ // commands on channel zero that aren't
656
+ // Connection.CloseOk) must be discarded.
657
+ if ( m_closeReason == null )
670
658
{
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 )
659
+ // No close reason, not quiescing the
660
+ // connection. Handle the frame. (Of course, the
661
+ // Session itself may be quiescing this particular
662
+ // channel, but that's none of our concern.)
663
+ ISession session = m_sessionManager . Lookup ( frame . Channel ) ;
664
+ if ( session == null )
677
665
{
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
- }
666
+ throw new ChannelErrorException ( frame . Channel ) ;
667
+ }
668
+ else
669
+ {
670
+ session . HandleFrame ( frame ) ;
691
671
}
692
672
}
693
673
}
694
- catch ( SocketException ioe )
695
- {
696
- HandleIOException ( ioe ) ;
697
- }
698
- catch ( IOException ioe )
699
- {
700
- HandleIOException ( ioe ) ;
701
- }
702
674
}
703
675
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 )
676
+ public void NotifyHeartbeatListener ( )
710
677
{
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 )
678
+ if ( m_heartbeat != 0 )
720
679
{
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 ( ) ;
680
+ m_heartbeatRead . Set ( ) ;
732
681
}
733
682
}
734
683
@@ -946,12 +895,15 @@ public bool SetCloseReason(ShutdownEventArgs reason)
946
895
}
947
896
}
948
897
949
- public void StartHeartbeatTimer ( )
898
+ public void MaybeStartHeartbeatTimers ( )
950
899
{
951
900
if ( Heartbeat != 0 )
952
901
{
953
902
_heartbeatWriteTimer = new Timer ( HeartbeatWriteTimerCallback ) ;
954
- _heartbeatWriteTimer . Change ( TimeSpan . FromMilliseconds ( 0 ) , m_heartbeatTimeSpan ) ;
903
+ _heartbeatWriteTimer . Change ( TimeSpan . FromMilliseconds ( 200 ) , m_heartbeatTimeSpan ) ;
904
+
905
+ _heartbeatReadTimer = new Timer ( HeartbeatReadTimerCallback ) ;
906
+ _heartbeatReadTimer . Change ( TimeSpan . FromMilliseconds ( 200 ) , m_heartbeatTimeSpan ) ;
955
907
}
956
908
}
957
909
@@ -963,9 +915,81 @@ public void StartMainLoop(bool useBackgroundThread)
963
915
mainLoopThread . Start ( ) ;
964
916
}
965
917
966
- protected void StopHeartbeatTimer ( )
918
+ public void HeartbeatReadTimerCallback ( object state )
919
+ {
920
+ bool shouldTerminate = false ;
921
+ if ( ! m_closed )
922
+ {
923
+ if ( ! m_heartbeatRead . WaitOne ( 0 , false ) )
924
+ {
925
+ m_missedHeartbeats ++ ;
926
+ }
927
+ else
928
+ {
929
+ m_missedHeartbeats = 0 ;
930
+ }
931
+
932
+ // We check against 8 = 2 * 4 because we need to wait for at
933
+ // least two complete heartbeat setting intervals before
934
+ // complaining, and we've set the socket timeout to a quarter
935
+ // of the heartbeat setting in setHeartbeat above.
936
+ if ( m_missedHeartbeats > 2 * 4 )
937
+ {
938
+ String description = String . Format ( "Heartbeat missing with heartbeat == {0} seconds" , m_heartbeat ) ;
939
+ var eose = new EndOfStreamException ( description ) ;
940
+ m_shutdownReport . Add ( new ShutdownReportEntry ( description , eose ) ) ;
941
+ HandleMainLoopException (
942
+ new ShutdownEventArgs ( ShutdownInitiator . Library , 0 , "End of stream" , eose ) ) ;
943
+ shouldTerminate = true ;
944
+ }
945
+ }
946
+
947
+ if ( shouldTerminate )
948
+ {
949
+ TerminateMainloop ( ) ;
950
+ FinishClose ( ) ;
951
+ }
952
+ else
953
+ {
954
+ _heartbeatReadTimer . Change ( Heartbeat * 1000 , Timeout . Infinite ) ;
955
+ }
956
+ }
957
+
958
+ public void HeartbeatWriteTimerCallback ( object state )
967
959
{
968
- if ( _heartbeatWriteTimer != null )
960
+ bool shouldTerminate = false ;
961
+ try
962
+ {
963
+ if ( ! m_closed )
964
+ {
965
+ WriteFrame ( m_heartbeatFrame ) ;
966
+ m_frameHandler . Flush ( ) ;
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
+ }
985
+
986
+ protected void MaybeStopHeartbeatTimers ( )
987
+ {
988
+ if ( _heartbeatReadTimer != null )
989
+ {
990
+ _heartbeatReadTimer . Dispose ( ) ;
991
+ }
992
+ if ( _heartbeatWriteTimer != null )
969
993
{
970
994
_heartbeatWriteTimer . Dispose ( ) ;
971
995
}
@@ -976,7 +1000,7 @@ protected void StopHeartbeatTimer()
976
1000
///</remarks>
977
1001
public void TerminateMainloop ( )
978
1002
{
979
- StopHeartbeatTimer ( ) ;
1003
+ MaybeStopHeartbeatTimers ( ) ;
980
1004
m_running = false ;
981
1005
}
982
1006
@@ -988,6 +1012,7 @@ public override string ToString()
988
1012
public void WriteFrame ( Frame f )
989
1013
{
990
1014
m_frameHandler . WriteFrame ( f ) ;
1015
+ m_heartbeatWrite . Set ( ) ;
991
1016
}
992
1017
993
1018
///<summary>API-side invocation of connection abort.</summary>
@@ -1060,7 +1085,7 @@ public void HandleConnectionUnblocked()
1060
1085
1061
1086
void IDisposable . Dispose ( )
1062
1087
{
1063
- StopHeartbeatTimer ( ) ;
1088
+ MaybeStopHeartbeatTimers ( ) ;
1064
1089
Abort ( ) ;
1065
1090
if ( ShutdownReport . Count > 0 )
1066
1091
{
@@ -1190,6 +1215,8 @@ protected void StartAndTune()
1190
1215
heartbeat ) ;
1191
1216
1192
1217
m_inConnectionNegotiation = false ;
1218
+ // now we can start heartbeat timers
1219
+ MaybeStartHeartbeatTimers ( ) ;
1193
1220
}
1194
1221
1195
1222
private static uint NegotiatedMaxValue ( uint clientValue , uint serverValue )
0 commit comments