sqlite: pool the FunctionContext handed to UDF and aggregate callbacks

Updates #226 (closed).

Problem

After !114 (merged) pooled the []driver.Value slice, the &FunctionContext{} passed to Scalar, Step, WindowInverse, WindowValue and Final was the last driver-side heap allocation per invocation: a 16-byte object escaping to the heap on every call, about 12% of the allocations left in the #226 (closed) reproducer, the same magnitude !114 (merged) saved.

Change

  • Fold the context into the pooled per-call object next to the args slice (udfCall), so one sync.Pool Get/Put serves both. The five UDF trampolines hand the callback &call.ctx.
  • Populate the context's tls and sqlite3_context fields per call (they were always zero at these sites before), so accessor methods can be added to FunctionContext later without touching the trampolines. makeAggregate already did this for the by-value context given to MakeAggregate.
  • The vtab Filter and Update paths use the same pooled object with a zero context; what they hand to modules is unchanged.
  • Document on FunctionContext and on the callbacks that, like the argument slice, the context is valid only for the duration of the call and must not be retained. Nothing observable changes for existing callers: the type has no exported fields or methods, so the only way to notice the reuse is comparing pointers across calls.

Numbers

Same machine and flags for both columns (linux/amd64, go1.27, -count 3):

Benchmark master this MR
BenchmarkUDFArgsAllocation 5756 allocs/op, 70489 B/op, ~572 us/op 4756 allocs/op, 54476 B/op, ~561 us/op
BenchmarkUDFArgsAllocationVolatile 3756 allocs/op, 62481 B/op, ~512 us/op 2756 allocs/op, 46464 B/op, ~505 us/op
#226 (closed) reporter's main_test.go 25.33M allocs/op, 553 MB/op, ~169 GCs 22.35M allocs/op, 505 MB/op, ~154 GCs

Exactly 1000 allocations per 1000 UDF calls removed, as expected; ns/op unchanged within noise.

Tests

  • New TestFunctionContextPerCall checks that every callback (scalar, Step, WindowInverse, WindowValue, Final) receives a non-nil, populated context, through a scalar, an aggregate and a window query.
  • The UDF, aggregate, window, vtab, volatile-args and hook tests pass with and without -race; go vet reports the same set of pre-existing notes as master; cross-builds for windows/386 and freebsd/arm pass; -gcflags=-m shows no heap escape at the callback call sites.

CHANGELOG

A pending v1.59.0 section is included per HACKING.md.

Merge request reports

Loading
Loading