Skip to content

Commit 4685dd8

Browse files
✨ Add support for gracefully killing processes with specific interrupts (#685)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description <!-- Please add any detail or context that would be useful to a reviewer. --> Add support for gracefully killing processes with specific interrupts ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent c7842a6 commit 4685dd8

File tree

3 files changed

+54
-12
lines changed

3 files changed

+54
-12
lines changed

changes/20250821120026.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `proc` Add support for gracefully killing processes with specific interrupts

utils/proc/interrupt.go

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,29 @@ func InterruptProcess(ctx context.Context, pid int, signal InterruptType) (err e
4747
return
4848
}
4949

50-
// TerminateGracefullyWithChildren follows the pattern set by [kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) and terminates processes gracefully by first sending a SIGTERM and then a SIGKILL after the grace period has elapsed.
50+
// TerminateGracefullyWithChildren follows the pattern set by [kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) and terminates processes gracefully by first sending a SIGINT+SIGTERM and then a SIGKILL after the grace period has elapsed.
5151
// It does not attempt to terminate the process group. If you wish to terminate the process group directly then send -pgid to TerminateGracefully but
5252
// this does not guarantee that the group will be terminated gracefully.
5353
// Instead, this function lists each child and attempts to kill them gracefully concurrently. It will then attempt to gracefully terminate itself.
5454
// Due to the multi-stage process and the fact that the full grace period must pass for each stage specified above, the total maximum length of this
5555
// function will be 2*gracePeriod not gracePeriod.
5656
func TerminateGracefullyWithChildren(ctx context.Context, pid int, gracePeriod time.Duration) (err error) {
57+
return terminateGracefullyWithChildren(ctx, pid, gracePeriod, TerminateGracefully)
58+
}
59+
60+
// TerminateGracefullyWithChildrenWithSpecificInterrupts follows the pattern set by [kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) and terminates processes gracefully by first sending the specified interrupts and then a SIGKILL after the grace period has elapsed.
61+
// It does not attempt to terminate the process group. If you wish to terminate the process group directly then send -pgid to TerminateGracefully but
62+
// this does not guarantee that the group will be terminated gracefully.
63+
// Instead, this function lists each child and attempts to kill them gracefully concurrently. It will then attempt to gracefully terminate itself.
64+
// Due to the multi-stage process and the fact that the full grace period must pass for each stage specified above, the total maximum length of this
65+
// function will be 2*gracePeriod not gracePeriod.
66+
func TerminateGracefullyWithChildrenWithSpecificInterrupts(ctx context.Context, pid int, gracePeriod time.Duration, interrupts ...InterruptType) (err error) {
67+
return terminateGracefullyWithChildren(ctx, pid, gracePeriod, func(ctx context.Context, pid int, gracePeriod time.Duration) error {
68+
return TerminateGracefullyWithSpecificInterrupts(ctx, pid, gracePeriod, interrupts...)
69+
})
70+
}
71+
72+
func terminateGracefullyWithChildren(ctx context.Context, pid int, gracePeriod time.Duration, terminate func(context.Context, int, time.Duration) error) (err error) {
5773
err = parallelisation.DetermineContextError(ctx)
5874
if err != nil {
5975
return
@@ -77,34 +93,39 @@ func TerminateGracefullyWithChildren(ctx context.Context, pid int, gracePeriod t
7793
}
7894

7995
if len(children) == 0 {
80-
err = TerminateGracefully(ctx, pid, gracePeriod)
96+
err = terminate(ctx, pid, gracePeriod)
8197
return
8298
}
8399

84100
childGroup, terminateCtx := errgroup.WithContext(ctx)
85101
childGroup.SetLimit(len(children))
86102
for _, child := range children {
87103
if child.IsRunning() {
88-
childGroup.Go(func() error { return TerminateGracefullyWithChildren(terminateCtx, child.Pid(), gracePeriod) })
104+
childGroup.Go(func() error {
105+
return terminateGracefullyWithChildren(terminateCtx, child.Pid(), gracePeriod, terminate)
106+
})
89107
}
90108
}
91109
err = childGroup.Wait()
92110
if err != nil {
93111
return
94112
}
95113

96-
err = TerminateGracefully(ctx, pid, gracePeriod)
114+
err = terminate(ctx, pid, gracePeriod)
97115
return
98116
}
99117

100-
func terminateGracefully(ctx context.Context, pid int, gracePeriod time.Duration) (err error) {
101-
err = InterruptProcess(ctx, pid, SigInt)
102-
if err != nil {
118+
func terminateGracefully(ctx context.Context, pid int, gracePeriod time.Duration, interrupts ...InterruptType) (err error) {
119+
if len(interrupts) == 0 {
120+
err = commonerrors.New(commonerrors.ErrInvalid, "at least one interrupt must be provided")
103121
return
104122
}
105-
err = InterruptProcess(ctx, pid, SigTerm)
106-
if err != nil {
107-
return
123+
124+
for _, interrupt := range interrupts {
125+
err = InterruptProcess(ctx, pid, interrupt)
126+
if err != nil {
127+
return
128+
}
108129
}
109130

110131
return parallelisation.RunActionWithParallelCheck(ctx,
@@ -119,10 +140,18 @@ func terminateGracefully(ctx context.Context, pid int, gracePeriod time.Duration
119140
}, 200*time.Millisecond)
120141
}
121142

122-
// TerminateGracefully follows the pattern set by [kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) and terminates processes gracefully by first sending a SIGTERM and then a SIGKILL after the grace period has elapsed.
143+
// TerminateGracefully follows the pattern set by [kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) and terminates processes gracefully by first sending a SIGINT+SIGTERM and then a SIGKILL after the grace period has elapsed.
123144
func TerminateGracefully(ctx context.Context, pid int, gracePeriod time.Duration) (err error) {
124145
defer func() { _ = InterruptProcess(context.Background(), pid, SigKill) }()
125-
_ = terminateGracefully(ctx, pid, gracePeriod)
146+
_ = terminateGracefully(ctx, pid, gracePeriod, SigInt, SigTerm)
147+
err = InterruptProcess(ctx, pid, SigKill)
148+
return
149+
}
150+
151+
// TerminateGracefullyWithSpecificInterrupts follows the pattern set by [kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) and terminates processes gracefully by first sending the specified interrupts and then a SIGKILL after the grace period has elapsed.
152+
func TerminateGracefullyWithSpecificInterrupts(ctx context.Context, pid int, gracePeriod time.Duration, interrupts ...InterruptType) (err error) {
153+
defer func() { _ = InterruptProcess(context.Background(), pid, SigKill) }()
154+
_ = terminateGracefully(ctx, pid, gracePeriod, interrupts...)
126155
err = InterruptProcess(ctx, pid, SigKill)
127156
return
128157
}

utils/proc/interrupt_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,22 @@ func TestTerminateGracefully(t *testing.T) {
2626
name: "TerminateGracefully",
2727
testFunc: TerminateGracefully,
2828
},
29+
{
30+
name: "TerminateGracefullyWithSpecificInterrupts",
31+
testFunc: func(ctx context.Context, pid int, gracePeriod time.Duration) error {
32+
return TerminateGracefullyWithSpecificInterrupts(ctx, pid, gracePeriod, SigInt)
33+
},
34+
},
2935
{
3036
name: "TerminateGracefullyWithChildren",
3137
testFunc: TerminateGracefullyWithChildren,
3238
},
39+
{
40+
name: "TerminateGracefullyWithChildrenWithSpecificInterrupts",
41+
testFunc: func(ctx context.Context, pid int, gracePeriod time.Duration) error {
42+
return TerminateGracefullyWithChildrenWithSpecificInterrupts(ctx, pid, gracePeriod, SigInt)
43+
},
44+
},
3345
} {
3446
t.Run(test.name, func(t *testing.T) {
3547
defer goleak.VerifyNone(t)

0 commit comments

Comments
 (0)