summaryrefslogtreecommitdiff
path: root/internal/eventloop_state.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-18 13:13:25 +0300
committerPaul Buetow <paul@buetow.org>2026-04-18 13:13:25 +0300
commit550f064f95113e072677b871b7de30ecf25d62b8 (patch)
tree03e39a1bed71b4fc16ea62e7956a6268982f15a3 /internal/eventloop_state.go
parent808c2549bfb17d22388a197d361a07ce0fc8f54f (diff)
fix task 45: bound pending handle cleanup
Diffstat (limited to 'internal/eventloop_state.go')
-rw-r--r--internal/eventloop_state.go80
1 files changed, 80 insertions, 0 deletions
diff --git a/internal/eventloop_state.go b/internal/eventloop_state.go
index 9622fd1..5cb0a78 100644
--- a/internal/eventloop_state.go
+++ b/internal/eventloop_state.go
@@ -18,6 +18,15 @@ type fdTracker struct {
age uint64 // monotonic counter for LRU ordering
}
+// pendingHandleTracker holds unresolved name_to_handle_at pathnames keyed by
+// TID until the corresponding open_by_handle_at exit consumes them.
+type pendingHandleTracker struct {
+ paths map[uint32]string
+ pathAges map[uint32]uint64
+ maxCacheSize int
+ age uint64
+}
+
func newFDTracker(files map[int32]file.File) *fdTracker {
if files == nil {
files = make(map[int32]file.File)
@@ -29,6 +38,13 @@ func newFDTracker(files map[int32]file.File) *fdTracker {
}
}
+func newPendingHandleTracker() *pendingHandleTracker {
+ return &pendingHandleTracker{
+ paths: make(map[uint32]string),
+ pathAges: make(map[uint32]uint64),
+ }
+}
+
func (t *fdTracker) get(fd int32) (file.File, bool) {
f, ok := t.files[fd]
return f, ok
@@ -135,6 +151,50 @@ func (t *fdTracker) deleteCacheKey(key uint64) {
delete(t.procFdAges, key)
}
+func (t *pendingHandleTracker) set(tid uint32, pathname string) {
+ if t.paths == nil {
+ t.paths = make(map[uint32]string)
+ t.pathAges = make(map[uint32]uint64)
+ }
+ t.age++
+ t.paths[tid] = pathname
+ t.pathAges[tid] = t.age
+ t.prune()
+}
+
+func (t *pendingHandleTracker) consume(tid uint32) (string, bool) {
+ pathname, ok := t.paths[tid]
+ if !ok {
+ return "", false
+ }
+ delete(t.paths, tid)
+ delete(t.pathAges, tid)
+ return pathname, true
+}
+
+func (t *pendingHandleTracker) delete(tid uint32) {
+ delete(t.paths, tid)
+ delete(t.pathAges, tid)
+}
+
+func (t *pendingHandleTracker) prune() {
+ if t.paths == nil {
+ return
+ }
+ limit := t.limit()
+ if len(t.paths) <= limit {
+ return
+ }
+ trimOldestPendingHandles(t.paths, t.pathAges, trimTarget(limit))
+}
+
+func (t *pendingHandleTracker) limit() int {
+ if t.maxCacheSize > 0 {
+ return t.maxCacheSize
+ }
+ return defaultMaxPendingHandleEntries
+}
+
// pairTracker holds the state for matching sys_enter events to their sys_exit
// counterparts and computing inter-syscall durations per TID.
type pairTracker struct {
@@ -257,6 +317,26 @@ func trimOldestProcFdEntries(state map[uint64]*file.FdFile, ages map[uint64]uint
}
}
+func trimOldestPendingHandles(state map[uint32]string, ages map[uint32]uint64, targetSize int) {
+ excess := len(state) - targetSize
+ if excess <= 0 {
+ return
+ }
+ type pendingHandleAge struct {
+ tid uint32
+ age uint64
+ }
+ oldest := make([]pendingHandleAge, 0, len(state))
+ for tid := range state {
+ oldest = append(oldest, pendingHandleAge{tid: tid, age: ages[tid]})
+ }
+ slices.SortFunc(oldest, func(a, b pendingHandleAge) int { return cmp.Compare(a.age, b.age) })
+ for _, entry := range oldest[:excess] {
+ delete(state, entry.tid)
+ delete(ages, entry.tid)
+ }
+}
+
func trimTarget(limit int) int {
target := limit - limit/cacheTrimDivisor
if target < 1 {