Skip to content

Centralize 30s throttle pattern into internal/throttle utility #421

@xe-nvdk

Description

@xe-nvdk

Context

PR #420 (S3 memory-bloat fix) introduced the third instance of the same 30s-debounce throttle pattern in the codebase. Gemini review on that PR flagged the duplication and recommended centralizing into a shared utility. The PR pushed it out as scope-creep, but the cleanup is real and worth doing.

The three throttle sites

All three follow the same shape: `atomic.Int64` storing nanoseconds-since-process-start, monotonic clock anchor via `processStart = time.Now()`, `last == 0` sentinel for first-call eligibility.

Each repeats the same 8 lines of throttle bookkeeping plus its own anchor variables.

Proposed fix

A new `internal/throttle` package with a `Debouncer` (or similar) type:

```go
type Debouncer struct {
interval time.Duration
processStart time.Time
lastNanos atomic.Int64
}

func New(interval time.Duration) *Debouncer { ... }

// TryAcquire returns true if the caller may proceed; false if throttled.
// Includes the last==0 sentinel so the first call always succeeds.
func (d *Debouncer) TryAcquire() bool { ... }

// Remaining returns how much of the interval is left until the next
// TryAcquire would succeed; useful for HTTP 429 retry-after headers.
func (d *Debouncer) Remaining() time.Duration { ... }
```

Each call site collapses to a one-liner:

```go
var freeOSMemoryDebounce = throttle.New(30 * time.Second)

func freeOSMemoryThrottled() {
if freeOSMemoryDebounce.TryAcquire() {
go debug.FreeOSMemory()
}
}
```

Why a separate PR

The change touches files unrelated to any single feature (`delete.go`, `debug.go`, `memtrim_linux.go`) and benefits from a small focused diff with its own tests. Suggested scope:

  • New `internal/throttle/throttle.go` + `throttle_test.go` (unit tests for the `last==0` first-call sentinel and concurrent CAS contention).
  • Convert all three call sites in one commit.
  • ~80 lines removed, ~60 added; net negative.

Origin

Filed as a follow-up to gemini-code-assist review on #420. The throttle pattern itself is correct in all three places — this issue is a maintainability cleanup, not a bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions