Skip to content

Commit 9e7f250

Browse files
made startup more durable
1 parent db3c551 commit 9e7f250

6 files changed

Lines changed: 95 additions & 12 deletions

File tree

controller/controller.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package controller
33
import (
44
"encoding/json"
55
"errors"
6+
"fmt"
67
"net"
78
"os"
89
"os/exec"
910
"path/filepath"
11+
"runtime"
1012
"strings"
1113
"sync"
1214
"sync/atomic"
@@ -81,7 +83,9 @@ func New(fsm *fsm.StateMachine, c lib.Config, valKey crypto.PrivateKeyI, metrics
8183
controller.loadCheckpointsFile()
8284
// setup plugin if enabled
8385
if c.Plugin != "" {
84-
controller.PluginExecute(c.Plugin)
86+
if err = controller.PluginExecute(c.Plugin); err != nil {
87+
return nil, err
88+
}
8589
controller.PluginConnectSync()
8690
}
8791
// initialize the consensus in the controller, passing a reference to itself
@@ -252,24 +256,25 @@ const socketDir = "/tmp/plugin"
252256
const socketFile = "plugin.sock"
253257

254258
// PluginExecute() executes the plugin control script to start the plugin process
255-
func (c *Controller) PluginExecute(plugin string) {
259+
func (c *Controller) PluginExecute(plugin string) lib.ErrorI {
256260
if plugin == "" || strings.Contains(plugin, "..") || strings.ContainsRune(plugin, os.PathSeparator) {
257-
c.log.Errorf("Invalid plugin name %q", plugin)
258-
return
261+
return lib.NewError(lib.NoCode, lib.MainModule, fmt.Sprintf("invalid plugin name %q", plugin))
262+
}
263+
cmdPath, err := resolvePluginCtlPath(plugin)
264+
if err != nil {
265+
return lib.NewError(lib.NoCode, lib.MainModule, err.Error())
259266
}
260-
// construct the shell command path: plugin/<plugin>/pluginctl.sh start
261-
cmdPath := filepath.Join("plugin", plugin, "pluginctl.sh")
262267
// create the command to execute the plugin control script with 'start' argument
263268
cmd := exec.Command(cmdPath, "start")
264269
// execute the command and capture output
265270
output, err := cmd.CombinedOutput()
266271
// if an error occurred during execution
267272
if err != nil {
268-
// log the error and exit
269-
c.log.Errorf("Failed to execute plugin %s: %v, output: %s", plugin, err, string(output))
273+
return lib.NewError(lib.NoCode, lib.MainModule, fmt.Sprintf("failed to execute plugin %s: %v, output: %s", plugin, err, string(output)))
270274
}
271275
// log successful plugin execution
272276
c.log.Infof("Plugin %s started: %s", plugin, string(output))
277+
return nil
273278
}
274279

275280
// PluginConnectSync() blocking: enables a unix socket file where plugins can interact with the Canopy FSM
@@ -302,6 +307,31 @@ func (c *Controller) PluginConnectSync() {
302307
c.FSM.Plugin, c.Mempool.FSM.Plugin = c.Plugin, c.Plugin
303308
}
304309

310+
func resolvePluginCtlPath(plugin string) (string, error) {
311+
relPath := filepath.Join("plugin", plugin, "pluginctl.sh")
312+
candidates := []string{
313+
relPath,
314+
}
315+
if exePath, err := os.Executable(); err == nil {
316+
exeDir := filepath.Dir(exePath)
317+
candidates = append(candidates,
318+
filepath.Join(exeDir, relPath),
319+
filepath.Join(filepath.Dir(exeDir), relPath),
320+
)
321+
}
322+
if _, sourceFile, _, ok := runtime.Caller(0); ok {
323+
repoRoot := filepath.Dir(filepath.Dir(sourceFile))
324+
candidates = append(candidates, filepath.Join(repoRoot, relPath))
325+
}
326+
for _, candidate := range candidates {
327+
info, err := os.Stat(candidate)
328+
if err == nil && !info.IsDir() {
329+
return candidate, nil
330+
}
331+
}
332+
return "", fmt.Errorf("plugin launcher not found for %q; checked: %s", plugin, strings.Join(candidates, ", "))
333+
}
334+
305335
// INTERNAL CALLS BELOW
306336

307337
// LoadIsOwnRoot() returns if this chain is its own root (base)

controller/controller_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package controller
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestResolvePluginCtlPath(t *testing.T) {
12+
wd, err := os.Getwd()
13+
require.NoError(t, err)
14+
15+
tempDir := t.TempDir()
16+
require.NoError(t, os.Chdir(tempDir))
17+
t.Cleanup(func() {
18+
require.NoError(t, os.Chdir(wd))
19+
})
20+
21+
path, err := resolvePluginCtlPath("go")
22+
require.NoError(t, err)
23+
require.True(t, filepath.IsAbs(path))
24+
require.FileExists(t, path)
25+
require.Equal(t, "pluginctl.sh", filepath.Base(path))
26+
require.Equal(t, "go", filepath.Base(filepath.Dir(path)))
27+
}
28+
29+
func TestResolvePluginCtlPathMissing(t *testing.T) {
30+
_, err := resolvePluginCtlPath("missing-plugin")
31+
require.Error(t, err)
32+
require.ErrorContains(t, err, "plugin launcher not found")
33+
}

plugin/go/pluginctl.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ extract_if_needed() {
5353
return 1
5454
}
5555

56+
get_process_cmd() {
57+
local pid="$1"
58+
ps -p "$pid" -o args= 2>/dev/null || ps -p "$pid" -o command= 2>/dev/null
59+
}
60+
5661
# Check if the process is running based on PID file
5762
is_running() {
5863
# Return 1 if PID file doesn't exist
@@ -68,7 +73,7 @@ is_running() {
6873
# Check if process exists and is the go-plugin binary
6974
if ps -p "$pid" > /dev/null 2>&1; then
7075
# Verify it's actually our binary
71-
if ps -p "$pid" -o cmd= | grep -q "go-plugin"; then
76+
if get_process_cmd "$pid" | grep -q "go-plugin"; then
7277
return 0
7378
fi
7479
fi

plugin/kotlin/pluginctl.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ extract_if_needed() {
3939
return 1
4040
}
4141

42+
get_process_cmd() {
43+
local pid="$1"
44+
ps -p "$pid" -o args= 2>/dev/null || ps -p "$pid" -o command= 2>/dev/null
45+
}
46+
4247
# Check if the process is running based on PID file
4348
is_running() {
4449
# Return 1 if PID file doesn't exist
@@ -54,7 +59,7 @@ is_running() {
5459
# Check if process exists and is the kotlin-plugin
5560
if ps -p "$pid" > /dev/null 2>&1; then
5661
# Verify it's actually our process
57-
if ps -p "$pid" -o cmd= | grep -q "canopy-plugin-kotlin"; then
62+
if get_process_cmd "$pid" | grep -q "canopy-plugin-kotlin"; then
5863
return 0
5964
fi
6065
fi

plugin/python/pluginctl.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ setup_venv_if_needed() {
118118
return $?
119119
}
120120

121+
get_process_cmd() {
122+
local pid="$1"
123+
ps -p "$pid" -o args= 2>/dev/null || ps -p "$pid" -o command= 2>/dev/null
124+
}
125+
121126
# Check if the process is running based on PID file
122127
is_running() {
123128
# Return 1 if PID file doesn't exist
@@ -133,7 +138,7 @@ is_running() {
133138
# Check if process exists and is running our Python script
134139
if ps -p "$pid" > /dev/null 2>&1; then
135140
# Verify it's actually our Python script
136-
if ps -p "$pid" -o cmd= | grep -q "python.*main.py"; then
141+
if get_process_cmd "$pid" | grep -q "python.*main.py"; then
137142
return 0
138143
fi
139144
fi

plugin/typescript/pluginctl.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ extract_if_needed() {
3535
return 1
3636
}
3737

38+
get_process_cmd() {
39+
local pid="$1"
40+
ps -p "$pid" -o args= 2>/dev/null || ps -p "$pid" -o command= 2>/dev/null
41+
}
42+
3843
# Check if the process is running based on PID file
3944
is_running() {
4045
# Return 1 if PID file doesn't exist
@@ -50,7 +55,7 @@ is_running() {
5055
# Check if process exists and is running our Node.js script
5156
if ps -p "$pid" > /dev/null 2>&1; then
5257
# Verify it's actually our Node.js script
53-
if ps -p "$pid" -o cmd= | grep -q "node.*dist/main.js"; then
58+
if get_process_cmd "$pid" | grep -q "node.*dist/main.js"; then
5459
return 0
5560
fi
5661
fi

0 commit comments

Comments
 (0)