diff --git a/internal/rpc/exec.go b/internal/rpc/exec.go index 0f8210e..3f277af 100644 --- a/internal/rpc/exec.go +++ b/internal/rpc/exec.go @@ -19,6 +19,8 @@ import ( "go.uber.org/zap" "golang.org/x/sync/errgroup" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" ) @@ -199,7 +201,7 @@ func (rpc *RPC) Exec(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse]) return } - if !errors.Is(err, context.Canceled) { + if !errors.Is(err, context.Canceled) && status.Code(err) != codes.Canceled { reportClientError(err) } diff --git a/internal/rpc/exec_grpc_test.go b/internal/rpc/exec_grpc_test.go new file mode 100644 index 0000000..22c637a --- /dev/null +++ b/internal/rpc/exec_grpc_test.go @@ -0,0 +1,134 @@ +//nolint:testpackage +package rpc + +import ( + "context" + "errors" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +func TestExecGRPCBackgroundProcessLifetime(t *testing.T) { + for _, mode := range []string{"normal exit", "cancel after exit", "cancel while running"} { + t.Run(mode, func(t *testing.T) { + client := NewAgentClient(newExecGRPCTestConn(t)) + ctx, cancel := context.WithTimeout(t.Context(), execTestTimeout) + defer cancel() + + stream, err := client.Exec(ctx) + require.NoError(t, err) + + releasePath := filepath.Join(t.TempDir(), "release") + require.NoError(t, stream.Send(&ExecRequest{Type: &ExecRequest_Command_{Command: &ExecRequest_Command{ + Name: execTestShell, + Args: []string{"-c", `nohup sleep 30 >/dev/null 2>&1