fix: improve agent shutdown handling and error reporting
Kim committed
May 22, 2026 at 17:41 UTC
fe2dcc8df9c2662a39d407cbf178cfb4974511fb
2 files changed
+35
-9
cmd/portal-tunnel/agent.go
+16
-7
@@ -242,17 +242,26 @@ func runAgentStopCommand(args []string) error {
242
if stateDir != "" {
243
cfg.Agent.StateDir = stateDir
244
}
245
- ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
245
+ ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
246
defer cancel()
247
248
shutdownErr := agent.Shutdown(ctx, cfg.Agent.StateDir)
249
- if err := service.StopDisable(ctx, cfg.Agent.ServiceName); err != nil {
250
- if shutdownErr == nil {
251
- fmt.Fprintf(os.Stderr, "Warning: agent stopped, but service manager cleanup failed: %v\n", err)
252
- fmt.Fprintln(os.Stdout, "Portal agent stopped.")
253
- return nil
249
+ serviceErr := service.StopDisable(ctx, cfg.Agent.ServiceName)
250
+ if errors.Is(shutdownErr, agent.ErrNotRunning) {
251
+ if serviceErr != nil {
252
+ fmt.Fprintf(os.Stderr, "Warning: agent is not running, but service manager cleanup failed: %v\n", serviceErr)
253
}
255
- return fmt.Errorf("stop portal agent service: %w", err)
254
+ fmt.Fprintln(os.Stdout, "Portal agent is not running.")
255
+ return nil
256
+ }
257
+ if shutdownErr != nil {
258
+ return fmt.Errorf("stop portal agent: %w", errors.Join(shutdownErr, serviceErr))
259
+ }
260
+ if err := waitAgentStopped(ctx, cfg.Agent.StateDir); err != nil {
261
+ return fmt.Errorf("stop portal agent: %w", errors.Join(err, serviceErr))
262
+ }
263
+ if serviceErr != nil {
264
+ fmt.Fprintf(os.Stderr, "Warning: agent stopped, but service manager cleanup failed: %v\n", serviceErr)
265
}
266
fmt.Fprintln(os.Stdout, "Portal agent stopped.")
267
return nil
cmd/portal-tunnel/agent/control.go
+19
-2
@@ -3,8 +3,10 @@ package agent
3
import (
4
"context"
5
"errors"
6
+ "net"
7
"net/http"
8
"net/url"
9
+ "os"
10
"path/filepath"
11
"strings"
12
"time"
@@ -20,7 +22,10 @@ const (
22
agentCookieName = "portal_agent"
23
)
24
23
-var controlHTTPClient = utils.NewHTTPClient(utils.WithHTTPTimeout(5 * time.Second))
25
+var (
26
+ ErrNotRunning = errors.New("portal agent is not running")
27
+ controlHTTPClient = utils.NewHTTPClient(utils.WithHTTPTimeout(5 * time.Second))
28
+)
29
30
type endpoint struct {
31
ControlAddr string `json:"control_addr"`
@@ -330,6 +335,9 @@ func controlRequest(ctx context.Context, stateDir, method, path string, payload
335
}
336
var endpoint endpoint
337
if err := utils.ReadJSONFile(filepath.Join(stateDir, endpointFilename), &endpoint); err != nil {
338
+ if os.IsNotExist(err) {
339
+ return ErrNotRunning
340
+ }
341
return err
342
}
343
if strings.TrimSpace(endpoint.ControlAddr) == "" || strings.TrimSpace(endpoint.Token) == "" {
@@ -340,5 +348,14 @@ func controlRequest(ctx context.Context, stateDir, method, path string, payload
348
return err
349
}
350
headers := http.Header{"Authorization": []string{"Bearer " + endpoint.Token}}
343
- return utils.HTTPDoAPIPath(ctx, controlHTTPClient, baseURL, method, path, payload, headers, out)
351
+ err = utils.HTTPDoAPIPath(ctx, controlHTTPClient, baseURL, method, path, payload, headers, out)
352
+ if controlDialError(err) {
353
+ return ErrNotRunning
354
+ }
355
+ return err
356
+}
357
+
358
+func controlDialError(err error) bool {
359
+ var opErr *net.OpError
360
+ return errors.As(err, &opErr) && opErr.Op == "dial"
361
}