chore: remove project conventions from sources/

These have been promoted to standalone repos:
- rodin/cockroachdb-conventions
- rodin/prometheus-conventions
- rodin/temporal-conventions
This commit is contained in:
Rodin
2026-04-30 11:45:45 -07:00
parent 498a3e9b27
commit fe74d5d47c
3 changed files with 0 additions and 684 deletions
-179
View File
@@ -1,179 +0,0 @@
# Patterns Extracted from cockroachdb/cockroach
## Pattern: Stopper for Goroutine Lifecycle
**Source:** `pkg/util/stop/stopper.go`
**Category:** concurrency
**What:** A dedicated struct that manages the lifecycle of
all goroutines in a component: tracks active tasks, refuses
new work during shutdown (quiesce), waits for completion,
then runs closers.
**Why:** In distributed systems, clean shutdown is critical.
You need to: (1) stop accepting new work, (2) finish
in-flight work, (3) release resources in order. The Stopper
centralizes this instead of scattering shutdown logic across
every goroutine.
**Example:**
```go
type Stopper struct {
quiescer chan struct{} // closed when quiescing
stopped chan struct{} // closed when fully stopped
mu struct {
syncutil.RWMutex
_numTasks int32
quiescing, stopping bool
closers []Closer
}
}
// RunAsyncTask refuses new work during quiesce
func (s *Stopper) RunAsyncTask(ctx context.Context,
taskName string, f func(context.Context)) error {
if !s.addTask() {
return ErrUnavailable
}
go func() {
defer s.decTask()
f(ctx)
}()
return nil
}
```
**When to use:** Any server or subsystem that spawns
goroutines and needs graceful shutdown. Especially in
long-running services where leaked goroutines cause
resource exhaustion.
**When NOT to use:** Simple programs with a single main
goroutine. Or when `errgroup` with context cancellation
suffices for the shutdown coordination.
---
## Pattern: Tracked Lifecycle with Leak Detection
**Source:** `pkg/util/stop/stopper.go`
**Category:** testing
**What:** Register every Stopper instance in a global
tracker. In tests, call `PrintLeakedStoppers(t)` to detect
any Stopper that was created but never stopped — indicating
a resource leak.
**Why:** Distributed systems have complex lifecycle graphs.
A forgot-to-stop bug silently leaks goroutines and
connections. The tracker makes leaks fail-loud in tests
without requiring careful manual cleanup.
**Example:**
```go
var trackedStoppers struct {
syncutil.Mutex
stoppers []stopperWithStack
}
func register(s *Stopper) {
trackedStoppers.Lock()
trackedStoppers.stoppers = append(...)
trackedStoppers.Unlock()
}
func PrintLeakedStoppers(t testing.TB) {
for _, tracked := range trackedStoppers.stoppers {
t.Errorf("leaked stopper, created at:\n%s",
tracked.createdAt)
}
}
```
**When to use:** Any resource that must be explicitly
closed/stopped and where forgetting to do so causes silent
degradation.
**When NOT to use:** Resources with finalizers or GC-safe
cleanup. Adds global state — only for testing.
---
## Pattern: Quiesce Then Stop (Two-Phase Shutdown)
**Source:** `pkg/util/stop/stopper.go`
**Category:** concurrency
**What:** Shutdown has two explicit phases: (1) Quiesce —
refuse new work, wait for in-flight to finish; (2) Stop —
run closers, signal done. Components observe
`ShouldQuiesce` channel alongside context.
**Why:** One-phase shutdown (just cancel context) loses
in-flight work. Two-phase gives running tasks time to
complete while preventing new work from starting. The
explicit channel (vs just context) lets components
distinguish "winding down" from "dead."
**Example:**
```go
func worker(s *Stopper, ctx context.Context) {
for {
select {
case <-s.ShouldQuiesce():
return // graceful: finish current, exit
case <-ctx.Done():
return // hard cancel
case work := <-workChan:
process(work)
}
}
}
```
**When to use:** Servers handling requests where you want
zero-downtime deploys (drain then stop). Load balancers,
RPC servers, queue consumers.
**When NOT to use:** Batch jobs or CLIs where immediate
exit is fine.
---
## Pattern: CloserFn Adapter
**Source:** `pkg/util/stop/stopper.go`
**Category:** concurrency
**What:** Define a `Closer` interface with one method
(`Close()`), plus a `CloserFn` type that adapts any
function into a Closer.
**Why:** The adapter pattern (like `http.HandlerFunc`)
avoids forcing users to define a struct just to implement
a one-method interface. Cleanup functions can be registered
directly.
**Example:**
```go
type Closer interface { Close() }
type CloserFn func()
func (f CloserFn) Close() { f() }
// Usage:
stopper.AddCloser(stop.CloserFn(func() {
conn.Close()
}))
```
**When to use:** Any one-method interface where callers
often have a simple function they want to register.
**When NOT to use:** Interfaces with >1 method, or when
the implementation needs state beyond a closure.
<!-- PATTERN_COMPLETE -->
-182
View File
@@ -1,182 +0,0 @@
# Patterns Extracted from prometheus/prometheus
## Pattern: Atomic File Operations with Suffix Convention
**Source:** `tsdb/db.go`
**Category:** storage
**What:** Use directory suffixes (`.tmp-for-creation`,
`.tmp-for-deletion`) to make multi-step file operations
crash-safe. On startup, clean up any dirs with these
suffixes (they represent incomplete operations).
**Why:** Database storage needs atomicity. If the process
crashes between creating a block and finalizing it, you
need to know the block is incomplete. The suffix convention
makes incomplete state visible at the filesystem level
without requiring a separate journal.
**Example:**
```go
const (
tmpForDeletionBlockDirSuffix = ".tmp-for-deletion"
tmpForCreationBlockDirSuffix = ".tmp-for-creation"
)
// On startup: remove any .tmp-* dirs (incomplete ops)
// On create: write to dir.tmp-for-creation, then rename
// On delete: rename to dir.tmp-for-deletion, then remove
```
**When to use:** Any system that manages files/directories
and needs crash consistency without a full WAL. Simpler
than a write-ahead log for coarse-grained operations.
**When NOT to use:** When you already have a WAL or
transaction log. Or for fine-grained operations where
rename semantics are insufficient.
---
## Pattern: DefaultOptions() Function
**Source:** `tsdb/db.go`
**Category:** configuration
**What:** Provide a `DefaultOptions()` function returning a
fully-populated config struct. Users copy and override only
what they need. No nil-means-default ambiguity.
**Why:** Large config structs (20+ fields) are unwieldy.
By providing sane defaults as a function (not a
package-level var), you avoid mutation bugs and make it
clear what "normal" looks like. Users only specify
deviations.
**Example:**
```go
func DefaultOptions() *Options {
return &Options{
WALSegmentSize: wlog.DefaultSegmentSize,
RetentionDuration: int64(15*24*time.Hour / ...),
MinBlockDuration: DefaultBlockDuration,
MaxBlockDuration: DefaultBlockDuration,
SamplesPerChunk: DefaultSamplesPerChunk,
// ... 20 more fields with sane defaults
}
}
// Usage:
opts := tsdb.DefaultOptions()
opts.RetentionDuration = 30 * 24 * time.Hour
db, err := tsdb.Open(dir, nil, nil, opts, nil)
```
**When to use:** Config structs with many fields where most
users want defaults. Especially when zero-value semantics
would be confusing (e.g., 0 retention = infinite? or off?).
**When NOT to use:** Small configs (3-4 fields) where
struct literal with zero-means-default is clear enough.
---
## Pattern: Scrape Loop with Aligned Timestamps
**Source:** `scrape/scrape.go`
**Category:** concurrency
**What:** Periodic scrape loops that align timestamps to
intervals with a small tolerance, enabling better storage
compression downstream.
**Why:** Time-series databases compress better when
timestamps are regular. A 2ms tolerance on alignment
means scraped data aligns to the expected grid while
accommodating real-world jitter.
**Example:**
```go
var ScrapeTimestampTolerance = 2 * time.Millisecond
var AlignScrapeTimestamps = true
// In scrape loop: if scrape finishes within tolerance
// of expected timestamp, snap to the grid
```
**When to use:** Any periodic data collection where
downstream storage benefits from timestamp regularity.
Metrics, heartbeats, polling loops.
**When NOT to use:** Event-driven data where timestamps
must reflect actual occurrence time. Audit logs, user
actions, financial transactions.
---
## Pattern: Sentinel Errors with Interface Check
**Source:** `tsdb/db.go`
**Category:** error-handling
**What:** Define package-level sentinel errors with
`errors.New()` and use compile-time interface assertions
to verify implementations satisfy storage interfaces.
**Why:** `ErrNotReady` as a sentinel lets callers use
`errors.Is` for retry logic. The pattern ensures error
identity is stable across versions (not string-matched).
**Example:**
```go
var ErrNotReady = errors.New("TSDB not ready")
// Callers can reliably detect this:
if errors.Is(err, tsdb.ErrNotReady) {
// Retry later — DB is still initializing
}
```
**When to use:** Any error that callers need to handle
programmatically (retry, fallback, special UI). Make it a
named sentinel, not a string comparison.
**When NOT to use:** Errors that are always terminal or
always logged-and-discarded. Not every error needs a name.
---
## Pattern: Compile-Time Interface Satisfaction
**Source:** `scrape/scrape.go`
**Category:** organization
**What:** Use `var _ Interface = (*Type)(nil)` to verify at
compile time that a type satisfies an interface, even if
the type is only used dynamically.
**Why:** Without this, you discover missing methods only
when the type is actually used — which might be in a
rarely-exercised code path or only in production. The
compile-time check catches it immediately.
**Example:**
```go
var _ FailureLogger = (*logging.JSONFileLogger)(nil)
// Fails at compile time if JSONFileLogger doesn't
// implement FailureLogger
```
**When to use:** Any type that implements an interface
consumed dynamically (registered in a map, stored as
interface value, passed to framework code).
**When NOT to use:** Types whose interface satisfaction is
already enforced by direct usage in the same package.
<!-- PATTERN_COMPLETE -->
-323
View File
@@ -1,323 +0,0 @@
# Patterns from Temporal (temporalio/temporal)
Source: github.com/temporalio/temporal
Analyzed: 2026-04-30
Commits: 8,958 | Contributors: 290
---
## 1. Effect Buffer (Transactional Side Effects)
**Location:** `common/effect/buffer.go`
Buffer side effects during a transaction, apply after
commit, rollback (in reverse order) after failure.
```go
type Buffer struct {
effects []func(context.Context)
cancels []func(context.Context)
}
func (b *Buffer) OnAfterCommit(effect func(ctx)) {
b.effects = append(b.effects, effect)
}
func (b *Buffer) OnAfterRollback(effect func(ctx)) {
b.cancels = append(b.cancels, effect)
}
func (b *Buffer) Apply(ctx context.Context) bool {
b.cancels = nil
for _, effect := range b.effects {
effect(ctx) // FIFO order
}
b.effects = nil
return true
}
func (b *Buffer) Cancel(ctx context.Context) bool {
b.effects = nil
for i := len(b.cancels) - 1; i >= 0; i-- {
b.cancels[i](ctx) // LIFO (reverse) order
}
b.cancels = nil
return true
}
```
**When to use:** Any operation that has side effects
(notifications, cache invalidation, external calls)
that should only execute if the primary transaction
succeeds.
**When NOT to use:** Simple operations where failure
leaves no cleanup needed. Over-engineering for
functions with a single side effect.
---
## 2. Soft Assertions (Log, Don't Crash)
**Location:** `common/softassert/softassert.go`
Assert invariants in production code without panicking.
Log the violation so tests catch it but production
continues serving.
```go
func That(logger log.Logger, condition bool,
staticMessage string, tags ...tag.Tag) bool {
if !condition {
logger.Error("failed assertion: "+staticMessage,
append([]tag.Tag{tag.FailedAssertion},
tags...)...)
}
return condition
}
```
Usage at call sites:
```go
if !softassert.That(logger, obj.State == "ready",
"object not ready before dispatch") {
return // or take recovery action
}
```
**The philosophy (from PR #7411):** "Why not panic?
Maybe in the future. For now, we're happy with finding
these failed assertions in functional tests."
**When to use:** Invariants that should always hold
but where crashing production is worse than logging.
Distributed systems where one node's invariant
violation shouldn't cascade.
**When NOT to use:** Safety-critical paths where
corruption would be worse than downtime. Better to
crash than serve corrupt data.
---
## 3. Type-Safe State Transitions
**Location:** `service/history/hsm/sm.go`
State machines with compile-time source state
validation and typed events.
```go
type Transition[S comparable, SM StateMachine[S], E any] struct {
Sources []S
Destination S
apply func(SM, E) (TransitionOutput, error)
}
func (t Transition[S, SM, E]) Apply(sm SM, event E) (TransitionOutput, error) {
if !slices.Contains(t.Sources, sm.State()) {
return TransitionOutput{},
fmt.Errorf("%w from %v: %v",
ErrInvalidTransition, sm.State(), event)
}
sm.SetState(t.Destination)
return t.apply(sm, event)
}
```
**When to use:** Any system with defined state
lifecycles — workflow engines, order processing,
connection state, protocol implementations.
**When NOT to use:** Simple boolean flags, systems
where state changes are free-form or user-defined.
---
## 4. Mutable vs Immutable Context (Type-Level Access Control)
**Location:** `chasm/context.go`
Separate `Context` (read-only) from `MutableContext`
(read-write) at the type level. Functions that only
read take `Context`; functions that mutate take
`MutableContext`.
```go
// Read-only operations
func (h *Handler) Check(ctx chasm.Context, c Component) (bool, error)
// Mutation operations
func (h *Handler) Execute(ctx chasm.MutableContext, c Component) error
```
**From PR discussion (Sushisource):** "I think I prefer
them separate, because what happens if you mutate
something and then say 'not ready'? That would be some
weird violation that shouldn't be possible, and separate
contexts enforces that at the type level."
**When to use:** Any system where read vs write access
matters — databases, caches, state machines, APIs with
both query and mutation.
**When NOT to use:** Simple CRUD where every operation
is both a read and a write.
---
## 5. Goroutine Handle (Safe Lifecycle)
**Location:** `common/goro/goro.go`
A handle to a goroutine that supports safe multi-stop,
context cancellation, and error collection.
```go
h := goro.NewHandle(ctx)
h.Go(func(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case task := <-ch:
process(task)
}
}
})
// Safe to call multiple times:
h.Cancel()
err := h.Wait()
```
**Born from:** PR #1892, fixing a double-close panic in
the task writer. The old code used a raw `chan struct{}`
for shutdown, which panics if closed twice.
**When to use:** Any long-lived goroutine that needs
clean shutdown, error reporting, or may be stopped from
multiple places.
**When NOT to use:** Fire-and-forget goroutines, or
goroutines managed by higher-level frameworks
(errgroup, worker pools).
---
## 6. Dynamic Config with Type-Safe Generics
**Location:** `common/dynamicconfig/`
566 settings, each declared as a typed constant with
default value, description, and precedence resolution.
```go
var WorkflowMaxTimeout = NewNamespaceDurationSetting(
"limit.maxWorkflowTimeout",
24*365*time.Hour,
`Maximum timeout for a workflow execution.`,
)
// Consumer side:
timeout := dc.GetDurationProperty(
dynamicconfig.WorkflowMaxTimeout,
namespace,
)()
```
Resolution order: task queue → namespace → global.
Uses `weak.Pointer` cache for GC-friendly memoization.
**When to use:** Large systems with many operational
knobs that need to change without restart. Multi-tenant
systems where different tenants need different limits.
**When NOT to use:** Simple services with <10 config
values. Static configuration is simpler and more
predictable.
---
## 7. Composable Predicates (Filter Algebra)
**Location:** `common/predicates/`
Generic predicate interface with And/Or/Not composition
and automatic flattening.
```go
type Predicate[T any] interface {
Test(T) bool
Equals(Predicate[T]) bool
Size() int
}
// Usage:
filter := predicates.And(
predicates.Not(isExpired),
predicates.Or(isHighPriority, isRetryable),
)
```
Flattening: `And(And(a, b), c)``And(a, b, c)`.
Short-circuit: `And(Empty, anything)``Empty`.
**When to use:** Task queues, query builders, access
control rules — anywhere filters compose.
**When NOT to use:** Single predicate checks, simple
if-statements.
---
## 8. Persistence Plugin Registration (init pattern)
**Location:** `common/persistence/sql/`
```go
func init() {
sql.RegisterPlugin("postgres12", &plugin{
driver: &driver.PQDriver{},
})
}
```
Import-driven registration. The main binary imports the
plugins it wants; each plugin's init() registers it.
**When to use:** Database drivers, encoding formats,
protocol implementations — anything with a fixed set
of implementations selected at compile time.
**When NOT to use:** When you need runtime plugin
discovery (use a factory pattern instead). When the
number of implementations is small and stable (just
use a switch).
---
## 9. ShutdownOnce (CAS-Based Safe Close)
**Location:** `common/channel/shutdown_once.go`
```go
func (c *ShutdownOnceImpl) Shutdown() {
if atomic.CompareAndSwapInt32(
&c.status, statusOpen, statusClosed) {
close(c.channel)
}
}
```
Wraps the "close of closed channel" panic with a CAS
guard. Safe to call from multiple goroutines.
**When to use:** Any shared shutdown signal where
multiple goroutines might initiate shutdown. Replaces
`sync.Once` when you also need a channel for select.
**When NOT to use:** When only one goroutine ever
triggers shutdown. When `context.WithCancel` suffices.
<!-- PATTERN_COMPLETE -->