generation

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package generation publishes immutable graph snapshots (typically csr.CSR views) under a refcount-protected pointer so readers can observe a consistent generation while a new one is being prepared in the background.

The pattern is the read-mostly equivalent of an MVCC snapshot: every reader Acquires the current generation, uses it, and Releases it; a publisher prepares the next generation in a fresh allocation and atomically swaps the pointer. Old generations are reclaimed only after every outstanding reader has Released them.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrClosed = errors.New("generation: publisher closed")

ErrClosed is returned by Publish when the Publisher has been closed.

View Source
var ErrDrainTimeout = errors.New("generation: drain timeout")

ErrDrainTimeout is returned by Publisher.PublishWithDrain when the old generation still has outstanding readers after the configured timeout.

Functions

This section is empty.

Types

type Generation

type Generation[W any] struct {
	// contains filtered or unexported fields
}

Generation wraps an immutable csr.CSR snapshot with a refcount. Generation is safe for concurrent use; Acquire/Release on the same generation can run from any number of goroutines.

func (*Generation[W]) CSR

func (g *Generation[W]) CSR() *csr.CSR[W]

CSR returns the underlying snapshot. The pointer is valid only while at least one Acquire is outstanding on g.

func (*Generation[W]) Refcount

func (g *Generation[W]) Refcount() int64

Refcount returns the current refcount. Intended for observability; callers must not infer correctness from it because the value can race with concurrent Acquire / Release.

type Publisher

type Publisher[W any] struct {
	// contains filtered or unexported fields
}

Publisher owns the current generation and serialises publication of new ones. It is safe for concurrent reads (Acquire/Release) and for at most one publisher (Publish/PublishWithDrain).

Example

ExamplePublisher shows the read side of the MVCC-style snapshot pattern: a reader Acquires the current generation, uses its immutable CSR, and Releases it. The refcount tracks outstanding readers so an old generation is reclaimed only once every reader has let go.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/csr"
	"github.com/FlavioCFOliveira/GoGraph/graph/generation"
)

// snapshot is a helper that freezes a directed graph with the given
// edges into a CSR view, the immutable unit a Publisher hands out.
func snapshot(edges [][2]string) *csr.CSR[int] {
	g := adjlist.New[string, int](adjlist.Config{Directed: true})
	for _, e := range edges {
		_ = g.AddEdge(e[0], e[1], 1)
	}
	return csr.BuildFromAdjList(g)
}

func main() {
	pub := generation.New(snapshot([][2]string{{"a", "b"}}))
	defer pub.Close()

	gen := pub.Acquire()
	fmt.Println("edges:", gen.CSR().Size())
	fmt.Println("readers while held:", gen.Refcount())

	pub.Release(gen)
	fmt.Println("readers after release:", pub.Current().Refcount())
}
Output:
edges: 1
readers while held: 1
readers after release: 0

func New

func New[W any](initial *csr.CSR[W]) *Publisher[W]

New returns a Publisher seeded with the given CSR as generation 0.

func (*Publisher[W]) Acquire

func (p *Publisher[W]) Acquire() *Generation[W]

Acquire returns the current generation with its refcount incremented. The caller must eventually call Release on the returned generation to allow stale generations to be reclaimed.

func (*Publisher[W]) Close

func (p *Publisher[W]) Close()

Close marks the Publisher as closed, waits for all outstanding acquisitions to drain (with a 30 s safety deadline), and returns. After Close returns, Acquire returns nil and Publish returns ErrClosed.

Close is safe to call from any goroutine. Calling Close more than once is safe; subsequent calls are no-ops.

func (*Publisher[W]) Current

func (p *Publisher[W]) Current() *Generation[W]

Current returns the current generation without incrementing its refcount. Intended for introspection only; never use the returned pointer to access the CSR.

func (*Publisher[W]) Publish

func (p *Publisher[W]) Publish(c *csr.CSR[W]) (*Generation[W], error)

Publish atomically swaps in a fresh generation built from c and returns the new generation. Returns (nil, ErrClosed) when the Publisher has been closed. The previous generation is not reclaimed until its refcount drains to zero (which happens naturally as readers Release).

Example

ExamplePublisher_Publish shows the write side: Publish installs a new immutable generation and makes it the one future Acquire calls see, without disturbing readers still holding the previous generation.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/csr"
	"github.com/FlavioCFOliveira/GoGraph/graph/generation"
)

// snapshot is a helper that freezes a directed graph with the given
// edges into a CSR view, the immutable unit a Publisher hands out.
func snapshot(edges [][2]string) *csr.CSR[int] {
	g := adjlist.New[string, int](adjlist.Config{Directed: true})
	for _, e := range edges {
		_ = g.AddEdge(e[0], e[1], 1)
	}
	return csr.BuildFromAdjList(g)
}

func main() {
	pub := generation.New(snapshot([][2]string{{"a", "b"}}))
	defer pub.Close()

	fmt.Println("before:", pub.Current().CSR().Size())

	next, err := pub.Publish(snapshot([][2]string{{"a", "b"}, {"b", "c"}}))
	if err != nil {
		panic(err) // a healthy (non-closed) publisher never errors here
	}

	fmt.Println("published:", next.CSR().Size())
	fmt.Println("current:", pub.Current().CSR().Size())
}
Output:
before: 1
published: 2
current: 2

func (*Publisher[W]) PublishWithDrain

func (p *Publisher[W]) PublishWithDrain(c *csr.CSR[W], timeout time.Duration) (*Generation[W], error)

PublishWithDrain swaps in a fresh generation and blocks until the previous generation's refcount reaches zero, or returns ErrDrainTimeout when the timeout elapses first. Returns (nil, ErrClosed) when the Publisher has been closed. Callers that need to recycle the previous generation's backing storage (e.g. to unmap a Tier 2 file) should prefer this variant.

A timeout of zero disables the deadline; PublishWithDrain then blocks indefinitely.

func (*Publisher[W]) Release

func (p *Publisher[W]) Release(g *Generation[W])

Release decrements g's refcount and signals any goroutine waiting inside Publisher.PublishWithDrain for the refcount to hit zero.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL