66import io .grpc .Status ;
77import io .grpc .stub .StreamObserver ;
88import java .util .concurrent .ScheduledExecutorService ;
9+ import java .util .concurrent .ScheduledFuture ;
10+ import java .util .concurrent .TimeUnit ;
911import java .util .concurrent .atomic .AtomicInteger ;
1012import java .util .concurrent .atomic .AtomicLong ;
1113import java .util .function .Consumer ;
@@ -20,6 +22,8 @@ public class ReadSession implements AutoCloseable {
2022
2123 private static final Logger logger = LoggerFactory .getLogger (ReadSession .class .getName ());
2224
25+ private static final Long HEARTBEAT_THRESHOLD_NANOS = TimeUnit .SECONDS .toNanos (20 );
26+
2327 final ScheduledExecutorService executor ;
2428 final StreamClient client ;
2529
@@ -28,6 +32,10 @@ public class ReadSession implements AutoCloseable {
2832 final AtomicLong consumedBytes = new AtomicLong (0 );
2933 final AtomicInteger remainingAttempts ;
3034
35+ // Liveness timer.
36+ final AtomicLong lastEvent ;
37+ final ListenableFuture <Void > livenessDaemon ;
38+
3139 final Consumer <ReadOutput > onResponse ;
3240 final Consumer <Throwable > onError ;
3341
@@ -46,6 +54,9 @@ public class ReadSession implements AutoCloseable {
4654 this .request = request ;
4755 this .nextStartSeqNum = new AtomicLong (request .startSeqNum );
4856 this .remainingAttempts = new AtomicInteger (client .config .maxRetries );
57+ this .lastEvent = new AtomicLong (System .nanoTime ());
58+
59+ this .livenessDaemon = request .heartbeats ? livenessDaemon () : Futures .immediateFuture (null );
4960 this .daemon = this .retrying ();
5061 }
5162
@@ -60,7 +71,12 @@ private ListenableFuture<Void> readSessionInner(
6071
6172 @ Override
6273 public void onNext (ReadSessionResponse value ) {
63- innerOnResponse .accept (ReadOutput .fromProto (value .getOutput ()));
74+ lastEvent .set (System .nanoTime ());
75+ if (value .hasOutput ()) {
76+ innerOnResponse .accept (ReadOutput .fromProto (value .getOutput ()));
77+ } else {
78+ logger .trace ("heartbeat" );
79+ }
6480 }
6581
6682 @ Override
@@ -72,12 +88,48 @@ public void onError(Throwable t) {
7288 @ Override
7389 public void onCompleted () {
7490 logger .debug ("Read session inner onCompleted" );
91+ livenessDaemon .cancel (true );
7592 fut .set (null );
7693 }
7794 });
7895 return fut ;
7996 }
8097
98+ private ListenableFuture <Void > livenessDaemon () {
99+ SettableFuture <Void > livenessFuture = SettableFuture .create ();
100+ scheduleLivenessCheck (livenessFuture );
101+ return livenessFuture ;
102+ }
103+
104+ private void scheduleLivenessCheck (SettableFuture <Void > livenessFuture ) {
105+ final long delay = (lastEvent .get () + HEARTBEAT_THRESHOLD_NANOS ) - System .nanoTime ();
106+
107+ logger .trace (
108+ "Checking liveness. Next deadline: {} seconds." ,
109+ TimeUnit .SECONDS .convert (delay , TimeUnit .NANOSECONDS ));
110+ if (delay <= 0 ) {
111+ this .onError .accept (
112+ Status .DEADLINE_EXCEEDED
113+ .withDescription ("ReadSession hit local heartbeat deadline" )
114+ .asRuntimeException ());
115+ this .daemon .cancel (true );
116+ livenessFuture .set (null );
117+ } else {
118+ ScheduledFuture <?> scheduledCheck =
119+ executor .schedule (
120+ () -> {
121+ if (livenessFuture .isDone ()) {
122+ return ;
123+ }
124+ scheduleLivenessCheck (livenessFuture );
125+ },
126+ delay ,
127+ TimeUnit .NANOSECONDS );
128+
129+ livenessFuture .addListener (() -> scheduledCheck .cancel (true ), executor );
130+ }
131+ }
132+
81133 private ListenableFuture <Void > retrying () {
82134
83135 return Futures .catchingAsync (
@@ -107,6 +159,7 @@ private ListenableFuture<Void> retrying() {
107159 } else {
108160 logger .warn ("readSession failed, status={}" , status .getCode ());
109161 onError .accept (t );
162+ this .livenessDaemon .cancel (true );
110163 return Futures .immediateFuture (null );
111164 }
112165 },
@@ -119,6 +172,7 @@ public ListenableFuture<Void> awaitCompletion() {
119172
120173 @ Override
121174 public void close () {
175+ this .livenessDaemon .cancel (true );
122176 this .daemon .cancel (true );
123177 }
124178}
0 commit comments