1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package plugin
6
7import (
8 "context"
9 "fmt"
10 "path/filepath"
11 "sync"
12
13 "github.com/samber/oops"
14 "github.com/tetratelabs/wazero"
15 "github.com/tetratelabs/wazero/api"
16 "github.com/tetratelabs/wazero/experimental"
17 pluginLib "gitroot.dev/libs/golang/plugin/model"
18 "gitroot.dev/server/logger"
19)
20
21type runtimes struct {
22 lockRuntime sync.Mutex
23 manager *Manager
24 runtime *runtime
25 log *logger.Logger
26 start chan runtimeInputs
27}
28
29func newRuntimes(manager *Manager, log *logger.Logger) *runtimes {
30 chanStart := make(chan runtimeInputs)
31 compileCache, _ := wazero.NewCompilationCacheWithDir(filepath.Join(manager.conf.PathCache(), "wazero"))
32 runtimeConfig := wazero.NewRuntimeConfig().WithCompilationCache(compileCache).WithCoreFeatures(api.CoreFeaturesV2 | experimental.CoreFeaturesThreads)
33 r, err := newRuntime(context.Background(), manager, log, runtimeConfig)
34 if err != nil {
35 log.Error("can't create runtime", err)
36 }
37 go func() {
38 r.listen(chanStart)
39 }()
40 return &runtimes{
41 manager: manager,
42 runtime: r,
43 log: log,
44 start: chanStart,
45 }
46}
47
48func (r *runtimes) Compile(ctx context.Context, binary []byte) (wazero.CompiledModule, error) {
49 r.lockRuntime.Lock()
50 defer r.lockRuntime.Unlock()
51 return r.runtime.wazRun.CompileModule(ctx, binary)
52}
53
54func (r *runtimes) Start(ctx context.Context, repoName string, plugins []Plugin, kind runtimeInputsKind, commands []CommandForDiff, then func(newCmds []CommandForDiff)) {
55 r.lockRuntime.Lock()
56 wg := sync.WaitGroup{}
57 for _, command := range commands {
58 wg.Add(1)
59 r.start <- runtimeInputs{
60 ctx: ctx,
61 repoName: repoName,
62 kind: kind,
63 plugins: plugins,
64 command: command,
65 close: func(toRunLater []CommandForDiff) {
66 wg.Done()
67 if then != nil {
68 then(toRunLater)
69 }
70 },
71 }
72 }
73 wg.Wait()
74 r.lockRuntime.Unlock()
75}
76
77func (r *runtimes) Conf(ctx context.Context, plugin Plugin) (*pluginLib.ConfPlugin, error) {
78 timerStop := r.log.Time(fmt.Sprintf("Timer conf plugin %s", plugin.Log()))
79 defer timerStop()
80 r.lockRuntime.Lock()
81 defer r.lockRuntime.Unlock()
82 return r.runtime.conf(ctx, plugin)
83}
84
85func (r *runtimes) Schedule(ctx context.Context, repoName string, pluginPurl string, pluginRun *PluginRun) error {
86 r.log.Debug("start schedule", logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", pluginPurl))
87 plugins, err := r.manager.usableFromDefaultBranch(ctx, repoName)
88 if err != nil {
89 return err
90 }
91 var plugin Plugin
92 found := false
93 for _, p := range plugins {
94 if p.PURL.String() == pluginPurl {
95 plugin = p
96 found = true
97 break
98 }
99 }
100 if !found {
101 return oops.With("repo", repoName, "pluginPurl", pluginPurl).Errorf("plugin not found")
102 }
103 timerStop := r.log.Time(fmt.Sprintf("Timer schedule plugin %s", plugin.Log()))
104 defer timerStop()
105 r.lockRuntime.Lock()
106 defer r.lockRuntime.Unlock()
107 return r.runtime.schedule(ctx, repoName, plugin, *pluginRun)
108}