diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 78a4b0c..7b96772 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -48,7 +48,7 @@ reports these implemented packages: | `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. | | `internal/validation` | Shared Go validation with optional race, full-vet, and static package-compilation target selections, formatting, workspace-file and filesystem-replacement checks, per-blob size limits, and selected Markdown destination and fragment checking; `internal/candidate` retains capture, callback sequencing, and local-state rechecks. | | `internal/publish` | Publication guards, one validation run, annotated tag creation, push, and recovery state. | -| `internal/runtimeinfo` | Private version lookup used by the runtime facade. | +| `internal/runtimeinfo` | Injected version selection and cached fallback metadata lookup for the runtime facade. | | `internal/versioncontract` | Shared stable-version rules and linker-symbol identity. | The `buildinfo` facade depends only on its small runtime implementation. The diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 8205c90..e9172ce 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Computational Efficiency Implementation Plan -**Status:** Stages 1–9 complete; Stages 10–11 pending. +**Status:** Stages 1–10 complete; Stage 11 pending. ## Purpose And Baseline @@ -539,6 +539,19 @@ Run facade/dependency checks. Re-run version benchmarks after initializing the cache and report initialization separately from repeated access. The runtime facade and its dependency boundary retain their existing public shape. +**Completion:** Complete. `Version` returns the linker-injected value before +touching fallback state, and a private factory wraps the existing pure +selection helper with `sync.OnceValue`. A concurrent counting-reader test +confirms one metadata read and stable results; isolated unavailable and +development cases continue to resolve to `dev`. Runtime, buildinfo, facade +dependency, consumer executable, and focused race tests passed. Three version +benchmark repetitions measured injected selection at 0.75–1.03 ns with no +allocations, fallback initialization at 1.53–1.74 µs with 1,264 B/9 allocations, +and warmed production fallback access at 4.54–4.91 ns with no allocations. +The original uncached metadata call measured 1.438–1.474 µs and 1,184 B/7 +allocations per access. The internal package inventory now records the cached +fallback behavior. + ## Stage 11: Verify The Complete Implementation And Record Results **Owners:** Affected tests and canonical internal documentation; roadmap status. diff --git a/internal/runtimeinfo/performance_test.go b/internal/runtimeinfo/performance_test.go index ac90f24..4f1c7ad 100644 --- a/internal/runtimeinfo/performance_test.go +++ b/internal/runtimeinfo/performance_test.go @@ -18,7 +18,19 @@ func BenchmarkPerformanceVersion(b *testing.B) { } } }) + b.Run("FallbackInitialization", func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + fallback := newFallbackVersion(debug.ReadBuildInfo) + performanceVersionSink = fallback() + if performanceVersionSink == "" { + b.Fatal("fallback returned an empty version") + } + } + }) b.Run("RealMetadataFallback", func(b *testing.B) { + _ = Version() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/internal/runtimeinfo/version.go b/internal/runtimeinfo/version.go index a4e4702..0cc5dae 100644 --- a/internal/runtimeinfo/version.go +++ b/internal/runtimeinfo/version.go @@ -2,14 +2,26 @@ // public buildinfo facade. package runtimeinfo -import "runtime/debug" +import ( + "runtime/debug" + "sync" +) var injectedVersion string // Version returns the injected version or the running executable's main module // version, falling back to "dev" when neither provides a release identifier. func Version() string { - return version(injectedVersion, debug.ReadBuildInfo) + if injectedVersion != "" { + return injectedVersion + } + return fallbackVersion() +} + +var fallbackVersion = newFallbackVersion(debug.ReadBuildInfo) + +func newFallbackVersion(readBuildInfo func() (*debug.BuildInfo, bool)) func() string { + return sync.OnceValue(func() string { return version("", readBuildInfo) }) } func version(injected string, readBuildInfo func() (*debug.BuildInfo, bool)) string { diff --git a/internal/runtimeinfo/version_test.go b/internal/runtimeinfo/version_test.go index 9d53891..5536715 100644 --- a/internal/runtimeinfo/version_test.go +++ b/internal/runtimeinfo/version_test.go @@ -2,6 +2,8 @@ package runtimeinfo import ( "runtime/debug" + "sync" + "sync/atomic" "testing" ) @@ -55,3 +57,54 @@ func TestVersion(t *testing.T) { }) } } + +func TestFallbackVersionReadsMetadataOnceConcurrently(t *testing.T) { + var reads atomic.Int32 + fallback := newFallbackVersion(func() (*debug.BuildInfo, bool) { + reads.Add(1) + return &debug.BuildInfo{Main: debug.Module{Version: "v4.5.6"}}, true + }) + + const callers = 32 + results := make(chan string, callers) + var workers sync.WaitGroup + workers.Add(callers) + for i := 0; i < callers; i++ { + go func() { + defer workers.Done() + results <- fallback() + }() + } + workers.Wait() + close(results) + for result := range results { + if result != "v4.5.6" { + t.Errorf("fallback() = %q, want stable version", result) + } + } + if got := reads.Load(); got != 1 { + t.Fatalf("metadata reader called %d times, want once", got) + } +} + +func TestFallbackVersionCachesUnavailableAndDevelopmentMetadata(t *testing.T) { + for _, test := range []struct { + name string + read func() (*debug.BuildInfo, bool) + }{ + {name: "unavailable", read: func() (*debug.BuildInfo, bool) { return nil, false }}, + {name: "development", read: func() (*debug.BuildInfo, bool) { + return &debug.BuildInfo{Main: debug.Module{Version: "(devel)"}}, true + }}, + } { + t.Run(test.name, func(t *testing.T) { + fallback := newFallbackVersion(test.read) + if got := fallback(); got != "dev" { + t.Fatalf("fallback() = %q, want dev", got) + } + if got := fallback(); got != "dev" { + t.Fatalf("repeated fallback() = %q, want dev", got) + } + }) + } +}