@@ -71,6 +71,7 @@ const (
7171 queryFirstDecisionTaskCheckInterval = 200 * time .Millisecond
7272 replicationTimeout = 30 * time .Second
7373 contextLockTimeout = 500 * time .Millisecond
74+ longPollCompletionBuffer = 50 * time .Millisecond
7475
7576 // TerminateIfRunningReason reason for terminateIfRunning
7677 TerminateIfRunningReason = "TerminateIfRunning Policy"
@@ -1005,7 +1006,26 @@ func (e *historyEngineImpl) getMutableStateOrPolling(
10051006 if err != nil {
10061007 return nil , err
10071008 }
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 )
10091029 defer timer .Stop ()
10101030 for {
10111031 select {
@@ -1026,8 +1046,6 @@ func (e *historyEngineImpl) getMutableStateOrPolling(
10261046 }
10271047 case <- timer .C :
10281048 return response , nil
1029- case <- ctx .Done ():
1030- return nil , ctx .Err ()
10311049 }
10321050 }
10331051 }
0 commit comments