{"record":{"id":"5f7fb6c8360dbf46","repo":"dagger/dagger","slug":"closing-link-w","errorCode":null,"errorMessage":"closing link: %w","messagePattern":"closing link: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"engine/ebpf/filetracer/tracer.go","lineNumber":313,"sourceCode":"\t\t}\n\t\tt.logEvent(&event)\n\t\treturn nil\n\t})\n}\n\n// Close releases all resources held by the tracer.\nfunc (t *Tracer) Close() error {\n\tvar errs []error\n\n\tif t.reader != nil {\n\t\tif err := t.reader.Close(); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"closing ring buffer: %w\", err))\n\t\t}\n\t}\n\n\tfor _, l := range t.links {\n\t\tif err := l.Close(); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"closing link: %w\", err))\n\t\t}\n\t}\n\n\tt.objs.Close()\n\n\tif len(errs) > 0 {\n\t\treturn errors.Join(errs...)\n\t}\n\treturn nil\n}\n\n// opName returns the human-readable name for an operation.\nfunc opName(op uint32, flags uint32) string {\n\tswitch op {\n\tcase opMount:\n\t\treturn \"MOUNT\"\n\tcase opUmount:\n\t\treturn \"UMOUNT\"","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/dagger/dagger/blob/82ba2681dbe30d3547a1dc50ea495900ab5b6047/engine/ebpf/filetracer/tracer.go#L295-L331","documentation":"Tracer.Close iterates all attached eBPF links and aggregates any Close() failures into a multi-error wrapped with \"closing link: %w\". A link Close failure means the kernel refused to detach one of the loaded BPF programs from its hook. This is rarely fatal since process exit cleans up links anyway, but signals a kernel/program lifecycle anomaly.","triggerScenarios":"Calling Close() on an ovltracer/filetracer Tracer when link.Close() (cilium/ebpf link.Link) returns an error, e.g. the link was already closed or the kernel rejected the detach.","commonSituations":"Double-Close of the same Tracer from concurrent code paths or defer plus explicit Close; kernel versions with buggy detach for kprobes/tracepoints; links whose underlying program object was already released via t.objs.Close() ordering issues.","solutions":["Ensure Close() is called exactly once (use sync.Once or check a closed flag).","Close the tracer before releasing the eBPF objects so links are still valid at detach time.","Log and continue: at process exit the kernel auto-detaches links, so this can often be treated as non-fatal.","Upgrade the kernel/cilium/ebpf if detach consistently fails on a specific kernel version."],"exampleFix":"// before\ntracer.Close()\ntracer.Close() // second call hits already-closed links\n\n// after\nvar closeOnce sync.Once\ncloseOnce.Do(func() { _ = tracer.Close() })","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := tracer.Close(); err != nil {\n\tvar multi interface{ Unwrap() []error }\n\tif errors.As(err, &multi) {\n\t\tfor _, e := range errors.Unwrap(err).([]error) { log.Warn(\"cleanup\", \"err\", e) }\n\t} else {\n\t\tlog.Warn(\"tracer close\", \"err\", err)\n\t}\n}","preventionTips":["Close the tracer exactly once (sync.Once).","Call Close before releasing the eBPF objects.","Treat close errors at process exit as warnings, not fatal."],"tags":["ebpf","cleanup","resource-lifecycle"],"backgroundTag":"bpf-link-detach-failed","analyzedSha":"82ba2681dbe30d3547a1dc50ea495900ab5b6047","analyzedAt":"2026-09-05T07:21:37.930Z","contentChangedAt":"2026-09-05T07:21:37.930Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}