Skip to content

Commit 8e9cc32

Browse files
committedSep 24, 2024·
feat: add extism.Pool
1 parent 1aa7887 commit 8e9cc32

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed
 

‎pool.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package extism
2+
3+
import (
4+
"context"
5+
"errors"
6+
"time"
7+
8+
"sync"
9+
)
10+
11+
type PluginFunc = func(ctx context.Context) (*Plugin, error)
12+
13+
type pluginInstance struct {
14+
lock sync.Mutex
15+
Plugin *Plugin
16+
}
17+
18+
func (p *pluginInstance) Done() {
19+
p.lock.Unlock()
20+
}
21+
22+
type Pool struct {
23+
maxInstances int
24+
plugins map[string]PluginFunc
25+
instances map[string][]*pluginInstance
26+
lock sync.Mutex
27+
}
28+
29+
func NewPool(maxInstances int) *Pool {
30+
return &Pool{
31+
maxInstances: maxInstances,
32+
plugins: map[string]PluginFunc{},
33+
instances: map[string][]*pluginInstance{},
34+
}
35+
}
36+
37+
func (pool *Pool) Add(key string, f PluginFunc) {
38+
pool.lock.Lock()
39+
defer pool.lock.Unlock()
40+
pool.plugins[key] = f
41+
pool.instances[key] = []*pluginInstance{}
42+
}
43+
44+
func (pool *Pool) Count(key string) int {
45+
pool.lock.Lock()
46+
defer pool.lock.Unlock()
47+
x, ok := pool.instances[key]
48+
if !ok {
49+
return 0
50+
}
51+
52+
return len(x)
53+
}
54+
55+
func (pool *Pool) findAvailable(key string) *pluginInstance {
56+
for _, p := range pool.instances[key] {
57+
if p.lock.TryLock() {
58+
return p
59+
}
60+
}
61+
62+
return nil
63+
}
64+
65+
func (pool *Pool) Get(ctx context.Context, key string, timeout time.Duration) (*pluginInstance, error) {
66+
end := time.After(timeout)
67+
pool.lock.Lock()
68+
defer pool.lock.Unlock()
69+
70+
if p := pool.findAvailable(key); p != nil {
71+
return p, nil
72+
}
73+
74+
if len(pool.instances[key]) < pool.maxInstances {
75+
f := pool.plugins[key]
76+
plugin, err := f(ctx)
77+
if err != nil {
78+
return nil, err
79+
}
80+
instance := &pluginInstance{Plugin: plugin}
81+
instance.lock.Lock()
82+
pool.instances[key] = append(pool.instances[key], instance)
83+
return instance, err
84+
}
85+
86+
for {
87+
select {
88+
case <-end:
89+
return nil, errors.New("Timed out getting instance for key: " + key)
90+
default:
91+
p := pool.findAvailable(key)
92+
if p != nil {
93+
return p, nil
94+
}
95+
}
96+
}
97+
}
98+
99+
func (pool *Pool) WithPlugin(ctx context.Context, key string, timeout time.Duration, f func(*Plugin) error) error {
100+
p, err := pool.Get(ctx, key, timeout)
101+
if err != nil {
102+
return err
103+
}
104+
defer p.Done()
105+
return f(p.Plugin)
106+
}

‎pool_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package extism
2+
3+
import (
4+
"context"
5+
"log"
6+
"sync"
7+
"testing"
8+
"time"
9+
)
10+
11+
func runTest(wg *sync.WaitGroup, pool *Pool, n int) {
12+
defer wg.Done()
13+
time.Sleep(time.Millisecond * time.Duration(n))
14+
var data string
15+
pool.WithPlugin(context.Background(), "test", time.Second, func(plugin *Plugin) error {
16+
_, x, err := plugin.Call("count_vowels", []byte("aaa"))
17+
data = string(x)
18+
return err
19+
})
20+
log.Println(string(data))
21+
}
22+
23+
func TestPluginPool(t *testing.T) {
24+
pool := NewPool(2)
25+
manifest := Manifest{
26+
Wasm: []Wasm{
27+
WasmFile{
28+
Path: "../code.wasm",
29+
},
30+
},
31+
}
32+
33+
pool.Add("test", func(ctx context.Context) (*Plugin, error) {
34+
return NewPlugin(ctx, manifest, PluginConfig{}, []HostFunction{})
35+
})
36+
37+
wg := &sync.WaitGroup{}
38+
wg.Add(10)
39+
go runTest(wg, pool, 1000)
40+
go runTest(wg, pool, 1000)
41+
go runTest(wg, pool, 1000)
42+
go runTest(wg, pool, 1000)
43+
go runTest(wg, pool, 1000)
44+
go runTest(wg, pool, 500)
45+
go runTest(wg, pool, 500)
46+
go runTest(wg, pool, 500)
47+
go runTest(wg, pool, 500)
48+
go runTest(wg, pool, 500)
49+
wg.Wait()
50+
}

0 commit comments

Comments
 (0)
Please sign in to comment.