Skip to content

Commit eb5a51e

Browse files
authored
Merge pull request moby#3658 from coryb/gateway-exec-cancel
fix gateway exec tty cleanup on context.Canceled
2 parents 2c6ea13 + aa827f5 commit eb5a51e

File tree

2 files changed

+156
-2
lines changed

2 files changed

+156
-2
lines changed

client/build_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ func TestClientGatewayIntegration(t *testing.T) {
4545
testClientGatewayContainerPID1Exit,
4646
testClientGatewayContainerMounts,
4747
testClientGatewayContainerPID1Tty,
48+
testClientGatewayContainerCancelPID1Tty,
4849
testClientGatewayContainerExecTty,
50+
testClientGatewayContainerCancelExecTty,
4951
testClientSlowCacheRootfsRef,
5052
testClientGatewayContainerPlatformPATH,
5153
testClientGatewayExecError,
@@ -923,6 +925,77 @@ func testClientGatewayContainerPID1Tty(t *testing.T, sb integration.Sandbox) {
923925
checkAllReleasable(t, c, sb, true)
924926
}
925927

928+
// testClientGatewayContainerCancelPID1Tty is testing that the tty will cleanly
929+
// shutdown on context cancel
930+
func testClientGatewayContainerCancelPID1Tty(t *testing.T, sb integration.Sandbox) {
931+
requiresLinux(t)
932+
ctx := sb.Context()
933+
934+
c, err := New(ctx, sb.Address())
935+
require.NoError(t, err)
936+
defer c.Close()
937+
938+
product := "buildkit_test"
939+
940+
inputR, inputW := io.Pipe()
941+
output := bytes.NewBuffer(nil)
942+
943+
b := func(ctx context.Context, c client.Client) (*client.Result, error) {
944+
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
945+
defer cancel()
946+
947+
st := llb.Image("busybox:latest")
948+
949+
def, err := st.Marshal(ctx)
950+
if err != nil {
951+
return nil, errors.Wrap(err, "failed to marshal state")
952+
}
953+
954+
r, err := c.Solve(ctx, client.SolveRequest{
955+
Definition: def.ToPB(),
956+
})
957+
if err != nil {
958+
return nil, errors.Wrap(err, "failed to solve")
959+
}
960+
961+
ctr, err := c.NewContainer(ctx, client.NewContainerRequest{
962+
Mounts: []client.Mount{{
963+
Dest: "/",
964+
MountType: pb.MountType_BIND,
965+
Ref: r.Ref,
966+
}},
967+
})
968+
require.NoError(t, err)
969+
defer ctr.Release(ctx)
970+
971+
prompt := newTestPrompt(ctx, t, inputW, output)
972+
pid1, err := ctr.Start(ctx, client.StartRequest{
973+
Args: []string{"sh"},
974+
Tty: true,
975+
Stdin: inputR,
976+
Stdout: &nopCloser{output},
977+
Stderr: &nopCloser{output},
978+
Env: []string{fmt.Sprintf("PS1=%s", prompt.String())},
979+
})
980+
require.NoError(t, err)
981+
prompt.SendExpect("echo hi", "hi")
982+
cancel()
983+
984+
err = pid1.Wait()
985+
require.ErrorIs(t, err, context.Canceled)
986+
987+
return &client.Result{}, err
988+
}
989+
990+
_, err = c.Build(ctx, SolveOpt{}, product, b, nil)
991+
require.Error(t, err)
992+
993+
inputW.Close()
994+
inputR.Close()
995+
996+
checkAllReleasable(t, c, sb, true)
997+
}
998+
926999
type testPrompt struct {
9271000
ctx context.Context
9281001
t *testing.T
@@ -1071,6 +1144,87 @@ func testClientGatewayContainerExecTty(t *testing.T, sb integration.Sandbox) {
10711144
checkAllReleasable(t, c, sb, true)
10721145
}
10731146

1147+
// testClientGatewayContainerExecTty is testing the tty shuts down cleanly
1148+
// on context.Cancel
1149+
func testClientGatewayContainerCancelExecTty(t *testing.T, sb integration.Sandbox) {
1150+
requiresLinux(t)
1151+
ctx := sb.Context()
1152+
1153+
c, err := New(ctx, sb.Address())
1154+
require.NoError(t, err)
1155+
defer c.Close()
1156+
1157+
product := "buildkit_test"
1158+
1159+
inputR, inputW := io.Pipe()
1160+
output := bytes.NewBuffer(nil)
1161+
b := func(ctx context.Context, c client.Client) (*client.Result, error) {
1162+
ctx, timeout := context.WithTimeout(ctx, 10*time.Second)
1163+
defer timeout()
1164+
st := llb.Image("busybox:latest")
1165+
1166+
def, err := st.Marshal(ctx)
1167+
if err != nil {
1168+
return nil, errors.Wrap(err, "failed to marshal state")
1169+
}
1170+
1171+
r, err := c.Solve(ctx, client.SolveRequest{
1172+
Definition: def.ToPB(),
1173+
})
1174+
if err != nil {
1175+
return nil, errors.Wrap(err, "failed to solve")
1176+
}
1177+
1178+
ctr, err := c.NewContainer(ctx, client.NewContainerRequest{
1179+
Mounts: []client.Mount{{
1180+
Dest: "/",
1181+
MountType: pb.MountType_BIND,
1182+
Ref: r.Ref,
1183+
}},
1184+
})
1185+
require.NoError(t, err)
1186+
1187+
pid1, err := ctr.Start(ctx, client.StartRequest{
1188+
Args: []string{"sleep", "10"},
1189+
})
1190+
require.NoError(t, err)
1191+
1192+
defer pid1.Wait()
1193+
defer ctr.Release(ctx)
1194+
1195+
execCtx, cancel := context.WithCancel(ctx)
1196+
defer cancel()
1197+
1198+
prompt := newTestPrompt(execCtx, t, inputW, output)
1199+
pid2, err := ctr.Start(execCtx, client.StartRequest{
1200+
Args: []string{"sh"},
1201+
Tty: true,
1202+
Stdin: inputR,
1203+
Stdout: &nopCloser{output},
1204+
Stderr: &nopCloser{output},
1205+
Env: []string{fmt.Sprintf("PS1=%s", prompt.String())},
1206+
})
1207+
require.NoError(t, err)
1208+
1209+
prompt.SendExpect("echo hi", "hi")
1210+
cancel()
1211+
1212+
err = pid2.Wait()
1213+
require.ErrorIs(t, err, context.Canceled)
1214+
1215+
return &client.Result{}, err
1216+
}
1217+
1218+
_, err = c.Build(ctx, SolveOpt{}, product, b, nil)
1219+
require.Error(t, err)
1220+
require.Contains(t, err.Error(), context.Canceled.Error())
1221+
1222+
inputW.Close()
1223+
inputR.Close()
1224+
1225+
checkAllReleasable(t, c, sb, true)
1226+
}
1227+
10741228
func testClientSlowCacheRootfsRef(t *testing.T, sb integration.Sandbox) {
10751229
requiresLinux(t)
10761230

frontend/gateway/grpcclient/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -928,11 +928,11 @@ func (ctr *container) Start(ctx context.Context, req client.StartRequest) (clien
928928

929929
if msg == nil {
930930
// empty message from ctx cancel, so just start shutting down
931-
// input, but continue processing more exit/done messages
931+
// input
932932
closeDoneOnce.Do(func() {
933933
close(done)
934934
})
935-
continue
935+
return ctx.Err()
936936
}
937937

938938
if file := msg.GetFile(); file != nil {

0 commit comments

Comments
 (0)