perf: v1.2.2 — core allocation reduction + security + DX#147
Merged
FumingPower3925 merged 11 commits intomainfrom Apr 1, 2026
Merged
perf: v1.2.2 — core allocation reduction + security + DX#147FumingPower3925 merged 11 commits intomainfrom
FumingPower3925 merged 11 commits intomainfrom
Conversation
Initialize respHeaders to use the inline respHdrBuf backing array in the context pool constructor and reset(). This eliminates the heap allocation triggered by the first SetHeader() call via append(). Fix the aliasing hazard in Blob() where respHeaders and respHdrBuf now share the same backing array — copy user headers to a stack temporary before overwriting the buffer with content-type and content-length.
Initialize keys with make(map[string]any, 4) in the pool constructor and use clear() in reset() instead of niling the map. This retains the hash table buckets across requests, eliminating the heap allocation on the first c.Set() call.
Remove eager getBuf() for OutboundBuffer from NewStream(). The buffer is only needed when flow control prevents immediate sends. BufferOutbound() already has a nil guard and now uses getBuf() (pool) instead of new(bytes.Buffer). Saves 1 alloc per NewStream() call — all test contexts and H2 streams that don't need flow-control buffering.
Add hdrBuf [16][2]string to Stream struct and use it as the backing array for Headers in NewStream, NewH1Stream, Release, ResetH1Stream, and ResetH2StreamInline. This eliminates the make([][2]string, 0, N) heap allocation on first pool retrieval and re-anchors to the inline buffer on release (in case append grew beyond 16).
Use base64.StdEncoding.Decode with a [128]byte stack buffer instead of DecodeString which heap-allocates. Convert the auth payload string to []byte via unsafe.Slice(unsafe.StringData(...)) without allocation (read-only, safe for Decode which only reads src). Eliminates the intermediate []byte heap allocation; the two returned strings (username, password) are individually smaller than the full decoded buffer.
Bundle ResponseRecorder and recorderWriter into a single recorderCombo struct managed by sync.Pool. NewContext gets the combo from the pool instead of allocating two heap objects per call. ReleaseContext extracts the combo via the new ctxkit.GetResponseWriter hook and returns it. WriteResponse reuses the Body slice via append(w.rec.Body[:0], body...) instead of allocating a new []byte each time.
Comprehensive allocation elimination across the request lifecycle: - Stream.Data lazy allocation: remove eager getBuf() from NewStream; Data is allocated on first write via GetBuf()/AddData() - Stream.ResetForPool: new function for test recycling without Cancel (avoids race with context.WithTimeout propagation goroutines) - Stream.HasDoneCh: detect derived contexts for safe recycling decisions - Context.handlerBuf [8]HandlerFunc: inline handler chain buffer; SetHandlers uses it for chains <=8 (avoids make([]HandlerFunc)) - ctxkit.GetStream hook: enables celeristest to extract and recycle the stream before context release - celeristest config pooling: sync.Pool with inline headersBuf[4] and handlersBuf[4]; WithHandlers uses inline buffer for <=4 handlers - celeristest headers: append to hdrBuf instead of slice literal - celeristest ReleaseContext: recycles stream to pool (ResetForPool) for streams without derived contexts; Cancel-only for streams with doneCh to avoid goroutine reference races - BasicAuth: single string allocation (decoded[:i], decoded[i+1:]) instead of two separate string() conversions - stdlib.go, tests: s.Data.Write → s.GetBuf().Write for lazy Data Result: 0 allocs/op on 6 of 9 middleware benchmarks (was 10-17).
Performance: - Embedded paramBuf [4]Param on Context for inline route parameter storage - Hoisted stripCRLF/stripCookieUnsafe replacers to package-level vars - Inline fast-path scans in AddHeader/SetHeader (skip non-inlineable calls) - Consolidated three 100MB constants into single maxBodySize - Scheme() fast-path for common "http"/"https" values - Pre-allocated notFound/methodNotAllowed handler chains on routerAdapter - Clear backing arrays (respHdrBuf, paramBuf, hdrBuf, Trailers) in reset for GC hygiene — prevents stale strings from being pinned - SetCookie Builder.Grow(128) pre-allocation Security: - H1 parser: reject duplicate Content-Length with conflicting values (CL-CL request smuggling prevention, RFC 7230 §3.3.3) - FileFromDir: resolve symlinks via filepath.EvalSymlinks + recheck prefix to prevent symlink-based directory traversal - FileFromDir: reject directory paths with IsDir() check DX: - Header(key) auto-lowercases uppercase keys (net/http compat) - FormValueOK canonical name (FormValueOk deprecated alias kept) - QueryBool(key, default) convenience method - QueryInt64(key, default) convenience method - ParamDefault(key, default) convenience method Code quality: - Removed dead internal/timer/ package - Mock ResponseWriter copies headers (prevents clear() aliasing) - Blob header assembly uses len(c.respHdrBuf) instead of magic 8 Tests: - TestQueryBool (16 cases), TestQueryInt64 (8 cases), TestParamDefault - TestWithHandlers (4 tests: chain order, many handlers, error, abort) - TestFormValueOkDeprecated - TestParseRequest_DuplicateContentLength (3 subtests × 2 modes)
base64.StdEncoding.Decode panics (not returns error) when the decoded output exceeds the destination buffer. Add a DecodedLen pre-check before decoding to gracefully return ok=false for credentials exceeding the 128-byte stack buffer, preventing a panic on crafted Authorization headers with long payloads.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Comprehensive v1.2.2 release combining allocation reduction, security hardening, developer experience improvements, and code quality cleanup.
Performance (v1.2.1 → v1.2.2)
Celeris beats Fiber v3 on all 9 middleware benchmarks.
Security
FileFromDirresolves symlinks viafilepath.EvalSymlinks+ prefix recheckFileFromDirrejects directory paths withIsDir()checkDecodedLenpre-check prevents panic on oversized credentialsCore Optimizations
respHdrBuf/paramBuf/handlerBuf/hdrBufbacking arrayskeysmap, lazyData/OutboundBufferinNewStreamceleristestviaResetForPoolceleristestclear()on backing arrays in reset for GC hygieneDX
Header(key)auto-lowercases uppercase keysFormValueOK(renamed fromFormValueOk, deprecated alias kept)QueryBool,QueryInt64,ParamDefaultconvenience methodsCode Quality
internal/timer/packageSetCookieBuilder.Grow(128) pre-allocationTest plan
go test -race ./...passes (all packages except pre-existing h2spec)go vet ./...clean