@@ -71,6 +71,7 @@ const (
71
71
queryFirstDecisionTaskCheckInterval = 200 * time .Millisecond
72
72
replicationTimeout = 30 * time .Second
73
73
contextLockTimeout = 500 * time .Millisecond
74
+ longPollCompletionBuffer = 50 * time .Millisecond
74
75
75
76
// TerminateIfRunningReason reason for terminateIfRunning
76
77
TerminateIfRunningReason = "TerminateIfRunning Policy"
@@ -1005,7 +1006,26 @@ func (e *historyEngineImpl) getMutableStateOrPolling(
1005
1006
if err != nil {
1006
1007
return nil , err
1007
1008
}
1008
- timer := time .NewTimer (e .shard .GetConfig ().LongPollExpirationInterval (domainName ))
1009
+
1010
+ expirationInterval := e .shard .GetConfig ().LongPollExpirationInterval (domainName )
1011
+ if deadline , ok := ctx .Deadline (); ok {
1012
+ remainingTime := deadline .Sub (e .shard .GetTimeSource ().Now ())
1013
+ // Here we return a safeguard error, to ensure that older clients are not stuck in long poll loop until context fully expires.
1014
+ // Otherwise it results in multiple additional requests being made that returns empty responses.
1015
+ // Newer clients will not make request with too small timeout remaining.
1016
+ if remainingTime < longPollCompletionBuffer {
1017
+ return nil , context .DeadlineExceeded
1018
+ }
1019
+ // longPollCompletionBuffer is here to leave some room to finish current request without its timeout.
1020
+ expirationInterval = common .MinDuration (
1021
+ expirationInterval ,
1022
+ remainingTime - longPollCompletionBuffer ,
1023
+ )
1024
+ }
1025
+ if expirationInterval <= 0 {
1026
+ return response , nil
1027
+ }
1028
+ timer := time .NewTimer (expirationInterval )
1009
1029
defer timer .Stop ()
1010
1030
for {
1011
1031
select {
@@ -1026,8 +1046,6 @@ func (e *historyEngineImpl) getMutableStateOrPolling(
1026
1046
}
1027
1047
case <- timer .C :
1028
1048
return response , nil
1029
- case <- ctx .Done ():
1030
- return nil , ctx .Err ()
1031
1049
}
1032
1050
}
1033
1051
}
0 commit comments