Skip to content

Optimize: TTL cleanup should be on-demand only #53

@dapi

Description

@dapi

Problem

Currently RemoveExpired() is called on EVERY port-selector run (line 176 in main.go), even when there are plenty of free ports available. This is inefficient and unnecessary.

The TTL mechanism should only be used as a last resort when the port range is exhausted.

Current Behavior

Every time a user runs port-selector:

  1. All allocations are scanned for expiration
  2. Expired allocations are removed (wasting cycles)
  3. Then search for free port proceeds

Example: With 1000 ports and only 100 allocated, we still scan all for TTL on every run.

Desired Behavior

Lazy cleanup approach:

  1. Try to find a free port in the configured range (normal allocation)
  2. If all ports are busy/frozen:
    • Check for expired allocations (last resort)
    • Find the oldest (by LastUsedAt or AssignedAt)
    • Verify it's actually free with port.IsPortFree()
    • Remove and immediately allocate to current directory
  3. If after cleanup still no free ports: return error

Benefits

  • ✅ Faster normal operation (skip TTL scan)
  • ✅ Safer cleanup (only remove when necessary, verify port is free)
  • ✅ More predictable behavior
  • ✅ Prevent race conditions (remove + allocate atomically within WithStore)

Related Code

  • main.go:176 - unconditional RemoveExpired() call
  • main.go:195-220 - port finding logic
  • allocations.go:439-464 - RemoveExpired() function
  • internal/port/checker.go - IsPortFree() function

Implementation Strategy

  1. Move RemoveExpired() call from line 176 into the port finding loop
  2. Only call when no free port found in range:
    // Find free port (excluding frozen/locked)
    freePort, err := port.FindFreePortWithExclusions(...)
    if err != nil {
        if errors.Is(err, port.ErrAllPortsBusy) {
            // Last resort: try cleanup
            removed := store.RemoveExpired(ttl)
            if removed > 0 {
                // Retry finding free port
                freePort, err = port.FindFreePortWithExclusions(...)
            }
        }
        if err != nil {
            return fmt.Errorf("all ports busy or frozen")
        }
    }
  3. Optionally: add GetOldestAllocation() method to prefer oldest allocations for removal
  4. Consider: log when cleanup is triggered (warning/info level)

Testing

  • Unit test: verify cleanup is NOT called when ports available
  • Integration test: verify cleanup IS called when all ports busy
  • Integration test: verify removed port can be immediately reallocated

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions