From 679514d40a3f318746c209f078822bf9b29216ef Mon Sep 17 00:00:00 2001 From: Yunlu Wen Date: Tue, 25 Aug 2026 04:36:46 +0000 Subject: [PATCH] perf(agent): exponential backoff in polling to prevent forking storm (#41222) --- dify-agent-runtime/internal/server/config.go | 3 +++ dify-agent-runtime/internal/server/service.go | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dify-agent-runtime/internal/server/config.go b/dify-agent-runtime/internal/server/config.go index c30ed34df70..bae1c56d93f 100644 --- a/dify-agent-runtime/internal/server/config.go +++ b/dify-agent-runtime/internal/server/config.go @@ -24,6 +24,7 @@ const ( DefaultGCIntervalSeconds = 60.0 DefaultGCFinishedJobRetentionSeconds = 300.0 DefaultPollInterval = 5 * time.Millisecond + DefaultMaxPollInterval = 50 * time.Millisecond DefaultPipeMonitorInterval = 1 * time.Second DefaultPipeReadyTimeout = 10 * time.Second DefaultSQLiteBusyTimeoutMs = 5000 @@ -53,6 +54,7 @@ type Config struct { MaxOutputLimitBytes int DefaultTerminateGraceSeconds float64 PollInterval time.Duration + MaxPollInterval time.Duration PipeMonitorInterval time.Duration PipeReadyTimeout time.Duration SQLiteBusyTimeoutMs int @@ -90,6 +92,7 @@ func DefaultConfig() (*Config, error) { MaxOutputLimitBytes: MaxOutputLimitBytes, DefaultTerminateGraceSeconds: DefaultTerminateGraceSeconds, PollInterval: DefaultPollInterval, + MaxPollInterval: DefaultMaxPollInterval, PipeMonitorInterval: DefaultPipeMonitorInterval, PipeReadyTimeout: DefaultPipeReadyTimeout, SQLiteBusyTimeoutMs: DefaultSQLiteBusyTimeoutMs, diff --git a/dify-agent-runtime/internal/server/service.go b/dify-agent-runtime/internal/server/service.go index 693d3c16fcc..24a21f5ee54 100644 --- a/dify-agent-runtime/internal/server/service.go +++ b/dify-agent-runtime/internal/server/service.go @@ -349,6 +349,11 @@ func (s *Service) WaitJob(jobID string, req *WaitJobRequest) (*JobResult, error) lastGrowthAt = &now } + // Poll with exponential backoff: start tight so short jobs return with low + // latency, then grow the interval up to MaxPollInterval so long/idle waits + // stop spawning a tmux liveness probe (fork) every few milliseconds. + pollInterval := s.config.PollInterval + for { view, err := s.GetJobStatus(jobID) if err != nil { @@ -411,7 +416,8 @@ func (s *Service) WaitJob(jobID string, req *WaitJobRequest) (*JobResult, error) return s.jobResultFromView(view, row, window), nil } - time.Sleep(s.config.PollInterval) + time.Sleep(pollInterval) + pollInterval = min(pollInterval*2, s.config.MaxPollInterval) } }