accounting: fix memory leak from stats groups on long-running rcd

Before this change, `NewStats` stored the context it was created from on the
`StatsInfo`. Stats groups are never freed -- they are only evicted once there are
`--max-stats-groups` of them -- so each one kept its context, and everything reachable
from it, alive for the life of the process. As the rc creates a group per call,
that included the call's filters and their compiled regexps.

The context was only ever used to get `ci.StatsFileNameLength` from the config.
`StatsInfo` already stores that same `*fs.ConfigInfo`, read from the same
context in `NewStats`.

This change fixes the issue by passing the stored ci to `transferMap.String` and
dropping the context from `StatsInfo`.
This commit is contained in:
nielash
2026-08-11 22:12:51 +02:00
committed by Nick Craig-Wood
parent 71a0932126
commit abae66ee1a
2 changed files with 3 additions and 7 deletions
+2 -4
View File
@@ -33,7 +33,6 @@ var MaxCompletedTransfers = 100
// to correctly count the updated fields
type StatsInfo struct {
mu sync.RWMutex
ctx context.Context
ci *fs.ConfigInfo
bytes int64
errors int64
@@ -85,7 +84,6 @@ type averageValues struct {
func NewStats(ctx context.Context) *StatsInfo {
ci := fs.GetConfig(ctx)
s := &StatsInfo{
ctx: ctx,
ci: ci,
checking: newTransferMap(ci.Checkers, "checking"),
transferring: newTransferMap(ci.Transfers, "transferring"),
@@ -518,10 +516,10 @@ func (s *StatsInfo) String() string {
// Add per transfer stats if required
if !s.ci.StatsOneLine {
if !s.checking.empty() {
_, _ = fmt.Fprintf(buf, "Checking:\n%s\n", s.checking.String(s.ctx, s.inProgress, s.transferring))
_, _ = fmt.Fprintf(buf, "Checking:\n%s\n", s.checking.String(s.ci, s.inProgress, s.transferring))
}
if !s.transferring.empty() {
_, _ = fmt.Fprintf(buf, "Transferring:\n%s\n", s.transferring.String(s.ctx, s.inProgress, nil))
_, _ = fmt.Fprintf(buf, "Transferring:\n%s\n", s.transferring.String(s.ci, s.inProgress, nil))
}
}
+1 -3
View File
@@ -1,7 +1,6 @@
package accounting
import (
"context"
"fmt"
"maps"
"sort"
@@ -91,10 +90,9 @@ func (tm *transferMap) _sortedSlice() []*Transfer {
// String returns string representation of map items excluding any in
// exclude (if set).
func (tm *transferMap) String(ctx context.Context, progress *inProgress, exclude *transferMap) string {
func (tm *transferMap) String(ci *fs.ConfigInfo, progress *inProgress, exclude *transferMap) string {
tm.mu.RLock()
defer tm.mu.RUnlock()
ci := fs.GetConfig(ctx)
stringList := make([]string, 0, len(tm.items))
for _, tr := range tm._sortedSlice() {
var what = tr.what