-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
Go version
go version go1.25.0 darwin/arm64
Output of go env
in your module/workspace:
AR='ar'
CC='cc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='c++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/ville/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/ville/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/cl/npk3dq855kxf2ns3qth9pv4m0000gn/T/go-build3480984197=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/dev/null'
GOMODCACHE='/Users/ville/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/ville/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.25.0/libexec'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/ville/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.25.0/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.25.0'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
Constructed a template with a very long pipeline to observe parser/runtime behavior. See it on Go playground here.
package main
import (
"fmt"
"runtime"
"strings"
"text/template"
)
func main() {
// Adjust to explore behavior at different sizes.
cmds := 100000
// Build: {{ printf "x" | printf "y" | ... }}
parts := make([]string, cmds)
for i := 0; i < cmds; i++ {
parts[i] = `printf "x"`
}
expr := "{{ " + strings.Join(parts, " | ") + " }}"
fmt.Printf("Input size: %d bytes, commands: %d\n", len(expr), cmds)
var m1, m2 runtime.MemStats
runtime.ReadMemStats(&m1)
_, err := template.New("test").Parse(expr)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
runtime.ReadMemStats(&m2)
allocated := m2.TotalAlloc - m1.TotalAlloc
fmt.Printf("Memory allocated: %d bytes\n", allocated)
if len(expr) > 0 {
fmt.Printf("Amplification factor: %.2fx\n", float64(allocated)/float64(len(expr)))
}
}
What did you see happen?
With an extreme pipeline length of 100k items the output is as follows:
Input size: 1300003 bytes, commands: 100000
Memory allocated: 25314928 bytes
Amplification factor: 19.47x
With a roughly 1 MB of input, Go consumes 24 MB of memory.
For larger command counts, parsing exhibits significant time and memory amplification relative to input size. At very high counts, this can lead to prolonged CPU usage and, in some cases, out-of-memory termination depending on runtime limits.
What did you expect to see?
A reasonable limit on the number of commands allowed in a single pipeline to prevent accidental or adversarial resource exhaustion during parse or execution. Similar to the existing maximum expression nesting depth, a pipeline command cap would bound resource usage for a single expression. See #71201 for more information about the expression nesting depth.