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 ¶
- Variables
- type Generation
- type Publisher
- func (p *Publisher[W]) Acquire() *Generation[W]
- func (p *Publisher[W]) Close()
- func (p *Publisher[W]) Current() *Generation[W]
- func (p *Publisher[W]) Publish(c *csr.CSR[W]) (*Generation[W], error)
- func (p *Publisher[W]) PublishWithDrain(c *csr.CSR[W], timeout time.Duration) (*Generation[W], error)
- func (p *Publisher[W]) Release(g *Generation[W])
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrClosed = errors.New("generation: publisher closed")
ErrClosed is returned by Publish when the Publisher has been closed.
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 (*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.