Summary
Syscall events never call Release() on their eBPF data, causing a memory leak.
Details
In pkg/containerwatcher/v2/container_watcher.go:162-163, syscall events are explicitly skipped from the Release() call:
if enrichedEvent.Event.GetEventType() != utils.SyscallEventType {
enrichedEvent.Event.Release() // at this time we should not need the event anymore
}
This was intentional because the syscall tracer (in pkg/containerwatcher/v2/tracers/syscall.go:125-138) creates multiple DatasourceEvents that share the same underlying eBPF Data pointer:
func (st *SyscallTracer) callback(event *utils.DatasourceEvent) {
syscallsBuffer := event.GetSyscalls()
for _, syscall := range decodeSyscalls(syscallsBuffer) {
st.eventCallback(&utils.DatasourceEvent{
Data: event.Data, // WARNING we pass the original data here, not a DeepCopy
Datasource: event.Datasource,
EventType: event.EventType,
Syscall: syscall,
}, containerID, processID)
}
}
The comment on line 32 explicitly warns: "WARNING we pass the original data here, not a DeepCopy"
The intent was to avoid calling Release() on one sub-event and freeing data for all sibling events. However, this means the copied eBPF data created at line 112 IS NEVER RELEASED, causing a leak.
Call Chain
syscall.go:112 - Initial copy: source.DeepCopy(data) (never released)
syscall.go:132 - Shared across sub-events: Data: event.Data
container_watcher.go:162 - Release skipped for ALL syscall events
- Memory leaks for every syscall capture
Potential Solutions
-
Deep copy each sub-event (simplest, cleanest)
- In syscall callback:
Data: source.DeepCopy(event.Data),
- Remove the skip check at container_watcher.go:162
- Pro: Each event independently owned, consistent with other tracers
- Con: Some memory overhead
-
Add reference counting
- Track number of sub-events sharing each
Data pointer
- Release when count reaches zero
- Pro: Shared memory preserved
- Con: More complex, error-prone
-
Release in syscall callback
- After syscall loop, call
event.Datasource.Release(event.Data)
- Pro: Simple, contained in tracer
- Con: Still violates the "1:1 Release after processing" pattern
Recommendation: Option 1 — it makes each event independently owned like other tracer types, avoiding shared-data complexity.
Summary
Syscall events never call
Release()on their eBPF data, causing a memory leak.Details
In
pkg/containerwatcher/v2/container_watcher.go:162-163, syscall events are explicitly skipped from the Release() call:This was intentional because the syscall tracer (in
pkg/containerwatcher/v2/tracers/syscall.go:125-138) creates multipleDatasourceEventsthat share the same underlying eBPFDatapointer:The comment on line 32 explicitly warns: "WARNING we pass the original data here, not a DeepCopy"
The intent was to avoid calling
Release()on one sub-event and freeing data for all sibling events. However, this means the copied eBPF data created at line 112 IS NEVER RELEASED, causing a leak.Call Chain
syscall.go:112- Initial copy:source.DeepCopy(data)(never released)syscall.go:132- Shared across sub-events:Data: event.Datacontainer_watcher.go:162- Release skipped for ALL syscall eventsPotential Solutions
Deep copy each sub-event (simplest, cleanest)
Data: source.DeepCopy(event.Data),Add reference counting
DatapointerRelease in syscall callback
event.Datasource.Release(event.Data)Recommendation: Option 1 — it makes each event independently owned like other tracer types, avoiding shared-data complexity.