~james/core

core/spindle/workflow_defs.go -rw-r--r-- 1.7 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 16 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package spindle

import (
	"sync"
	"time"

	"github.com/bluesky-social/indigo/atproto/syntax"
	"github.com/hashicorp/golang-lru/v2/expirable"
	"golang.org/x/time/rate"

	"tangled.org/core/spindle/models"
	"tangled.org/core/spindle/xrpc"
)

const (
	workflowDefRefTTL         = 10 * time.Second
	workflowDefCacheSize      = 512
	workflowDefLoadsPerSecond = 5
	workflowDefLoadBurst      = 20
)

// a miss costs the knot a tree read and a blob per workflow
type workflowDefCache struct {
	once   sync.Once
	mu     sync.Mutex
	byRef  *expirable.LRU[string, []*models.WorkflowDefinition]
	limits *expirable.LRU[syntax.DID, *rate.Limiter]
}

func (c *workflowDefCache) init() {
	c.byRef = expirable.NewLRU[string, []*models.WorkflowDefinition](workflowDefCacheSize, nil, workflowDefRefTTL)
	c.limits = expirable.NewLRU[syntax.DID, *rate.Limiter](workflowDefCacheSize, nil, time.Minute)
}

// only knot reads spend the budget, local clone reads are free
func (c *workflowDefCache) admit(repo syntax.DID) error {
	c.once.Do(c.init)
	c.mu.Lock()
	defer c.mu.Unlock()
	limiter, ok := c.limits.Get(repo)
	if !ok {
		limiter = rate.NewLimiter(workflowDefLoadsPerSecond, workflowDefLoadBurst)
		c.limits.Add(repo, limiter)
	}
	if !limiter.Allow() {
		return xrpc.ErrDefinitionsBusy
	}
	return nil
}

func (c *workflowDefCache) list(repo syntax.DID, ref string, load func() ([]*models.WorkflowDefinition, error)) ([]*models.WorkflowDefinition, error) {
	c.once.Do(c.init)
	return cached(c.byRef, repo.String()+"\x00"+ref, load)
}

func cached[V any](lru *expirable.LRU[string, V], key string, load func() (V, error)) (V, error) {
	if v, ok := lru.Get(key); ok {
		return v, nil
	}
	v, err := load()
	if err == nil {
		lru.Add(key, v)
	}
	return v, err
}