Skip to content

Bug: DefaultDictWithTimeout TTL refresh guard is inverted — expired keys never cleaned up (memory leak) #2260

@karesansui-u

Description

@karesansui-u

Bug description

DefaultDictWithTimeout.__refresh() in pr_agent/servers/utils.py has an inverted guard condition on line 67. The comparison uses > when it should use <:

def __refresh(self):
    if self.__ttl is None:
        return
    request_time = self.__time()
    if request_time - self.__last_refresh > self.__refresh_interval:  # ← inverted
        return
    # cleanup logic below — only reached when NOT enough time has passed

When enough time has passed since the last refresh (i.e., cleanup is needed), the method returns early. The cleanup logic only runs when the interval has NOT elapsed — when there's nothing to clean up.

Impact

_duplicate_push_triggers and _pending_task_duplicate_push_conditions in the GitHub App server accumulate every processed PR URL and never release them. On long-running server instances, this is a memory leak that grows linearly with the number of PRs processed.

Steps to reproduce

from pr_agent.servers.utils import DefaultDictWithTimeout
import time

d = DefaultDictWithTimeout(list, ttl=1, refresh_interval=0.5)
d["key1"].append("val")
time.sleep(2)  # well past TTL
d["key2"]  # triggers __refresh via __getitem__
print("key1" in d)  # True — should be False, key1's TTL expired

Fix

PR #2255 — change > to < on line 67.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions