Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions go/pkg/basecamp/eventfeed/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ const (
// iteration cannot decline. The live buffer grows to its capacity lazily
// and pays only for the events it admits; it carries the same ceiling
// because the two are one published contract, not because it allocates up
// front. #900 refused the value at fixture load, after a scenario asking
// for 2,147,483,647 took the test process down. The options are the same
// request on a path the loader does not cover.
// front. That New itself spends nothing on either capacity is proven
// rather than asserted here: TestNewAllocationSizeDoesNotVaryWithCapacity
// measures it. #900 refused the value at fixture load, after a scenario
// asking for 2,147,483,647 took the test process down. The options are
// the same request on a path the loader does not cover.
MaxCapacity = 1_000_000

// handshakeDeadline (EVENT_FEED_HANDSHAKE_DEADLINE, 10s) spans
Expand Down
75 changes: 75 additions & 0 deletions go/pkg/basecamp/eventfeed/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package eventfeed_test
import (
"context"
"errors"
"math"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -167,6 +168,80 @@ func TestNewAcceptsCapacitiesAtTheCeiling(t *testing.T) {
}
}

// TestNewAllocationSizeDoesNotVaryWithCapacity is the MaxCapacity comment's
// cost claim, written as an assertion. That comment justifies the ceiling by
// naming where a configured capacity is actually spent — newDedupe sizes its
// index map by it eagerly, on the first Events iteration — and that account
// holds only while New itself spends nothing on it. An earlier wording said
// both capacities were allocated the moment the connector was constructed;
// nothing in the suite disagreed, and a reader caught it instead.
//
// Bytes rather than testing.AllocsPerRun's count, because the count cannot
// carry this claim: sizing anything by the capacity — make([]byte, capacity)
// in New — is ONE allocation whether the capacity is 1 or a million, so the
// count is identical in exactly the case that must fail.
//
// The assertion is the invariance, not the size: every configuration is
// compared against the smallest one, so an unrelated allocation added to New
// moves them all together and stays green while a New that began sizing
// anything by a capacity does not. The two capacities move independently,
// including in both orderings, so an allocation taken from their relationship
// rather than from either alone is caught too.
func TestNewAllocationSizeDoesNotVaryWithCapacity(t *testing.T) {
smallest := bytesAllocatedByNew(t, 1, 1)
if smallest == 0 {
t.Fatal("New allocated no measurable bytes: the construction was optimized away, so this test can no longer observe it")
}

for _, tc := range []struct {
name string
dedupe, liveBuffer int
}{
{"both capacities at the ceiling", eventfeed.MaxCapacity, eventfeed.MaxCapacity},
{"dedupe capacity alone at the ceiling", eventfeed.MaxCapacity, 1},
{"live buffer capacity alone at the ceiling", 1, eventfeed.MaxCapacity},
} {
t.Run(tc.name, func(t *testing.T) {
got := bytesAllocatedByNew(t, tc.dedupe, tc.liveBuffer)
if got != smallest {
t.Fatalf("New allocated %d bytes at dedupe %d / live buffer %d and %d at 1/1: construction now sizes something by a capacity, which only the run is supposed to do", got, tc.dedupe, tc.liveBuffer, smallest)
}
})
}
}

// newSink keeps each constructed connector reachable, so the allocation being
// measured cannot be optimized away.
var newSink *eventfeed.Connector

// bytesAllocatedByNew measures one New at one pair of capacities. TotalAlloc
// is a process-wide counter and this package runs connectors on their own
// goroutines throughout its suite, so an unrelated allocation landing between
// the two reads can only ADD to a measurement. The lowest of several is
// therefore the floor, and the floors are what the configurations are compared
// at.
func bytesAllocatedByNew(t *testing.T, dedupe, liveBuffer int) uint64 {
t.Helper()

minter := feedtest.NewMinter()
polls := feedtest.NewPolls()
lowest := uint64(math.MaxUint64)
for range 5 {
var before, after runtime.MemStats
runtime.ReadMemStats(&before)
c, err := eventfeed.New(testOrigin, "1", minter, polls,
eventfeed.WithDedupeCapacity(dedupe),
eventfeed.WithLiveBufferCapacity(liveBuffer))
runtime.ReadMemStats(&after)
if err != nil {
t.Fatalf("New at dedupe %d / live buffer %d: %v", dedupe, liveBuffer, err)
}
newSink = c
lowest = min(lowest, after.TotalAlloc-before.TotalAlloc)
}
return lowest
}

func TestNewValidConfigurations(t *testing.T) {
minter := feedtest.NewMinter()
polls := feedtest.NewPolls()
Expand Down
Loading