Skip to content

Commit cd43d96

Browse files
lvan100lianghuan
authored andcommitted
feat(goutil): Add CancelMode to control context cancellation
1 parent d884912 commit cd43d96

4 files changed

Lines changed: 410 additions & 21 deletions

File tree

goutil/goutil.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,22 @@ var OnPanic = func(ctx context.Context, info PanicInfo) {
5050
fmt.Printf("[PANIC] %v\n%s\n", info.Panic, info.Stack)
5151
}
5252

53-
// Status represents the lifecycle of a goroutine launched by Go.
53+
// CancelMode controls how the context passed to a goroutine handles
54+
// cancellation relative to its parent context.
55+
type CancelMode int
56+
57+
const (
58+
// InheritCancel means the goroutine receives the original context
59+
// and therefore inherits its cancellation and deadline.
60+
InheritCancel CancelMode = iota
61+
62+
// DetachCancel means the goroutine receives a context created with
63+
// context.WithoutCancel, so cancellation of the parent context does
64+
// not propagate to the goroutine.
65+
DetachCancel
66+
)
67+
68+
// Status represents the lifecycle of a goroutine launched by the Go function.
5469
// It provides a synchronization point to wait for the goroutine to finish.
5570
type Status struct {
5671
ch chan struct{}
@@ -80,10 +95,11 @@ func (s *Status) Wait() {
8095
// is cooperative: the goroutine will NOT stop automatically when ctx is
8196
// canceled. The function f must observe ctx.Done() and return explicitly.
8297
//
83-
// If withoutCancel is true, f receives a context that is detached from
84-
// cancellation of the parent context.
85-
func Go(ctx context.Context, f func(ctx context.Context), withoutCancel bool) *Status {
86-
if withoutCancel {
98+
// If mode is DetachCancel, f receives a context derived using
99+
// context.WithoutCancel. In that case, cancellation and deadlines of the
100+
// parent context will not propagate to the goroutine.
101+
func Go(ctx context.Context, f func(ctx context.Context), mode CancelMode) *Status {
102+
if mode == DetachCancel {
87103
ctx = context.WithoutCancel(ctx)
88104
}
89105
s := newStatus()
@@ -141,10 +157,11 @@ type GoValueFunc[T any] func(ctx context.Context) (T, error)
141157
// As with Go, context cancellation is cooperative: f must observe ctx.Done()
142158
// if early termination is required.
143159
//
144-
// If withoutCancel is true, f receives a context that is detached from
145-
// cancellation of the parent context.
146-
func GoValue[T any](ctx context.Context, f GoValueFunc[T], withoutCancel bool) *ValueStatus[T] {
147-
if withoutCancel {
160+
// If mode is DetachCancel, f receives a context derived using
161+
// context.WithoutCancel. In that case, cancellation and deadlines of the
162+
// parent context will not propagate to the goroutine.
163+
func GoValue[T any](ctx context.Context, f GoValueFunc[T], mode CancelMode) *ValueStatus[T] {
164+
if mode == DetachCancel {
148165
ctx = context.WithoutCancel(ctx)
149166
}
150167
s := newValueStatus[T]()

goutil/goutil_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ func TestGo(t *testing.T) {
3131
var s string
3232
goutil.Go(t.Context(), func(ctx context.Context) {
3333
panic("something is wrong")
34-
}, false).Wait()
34+
}, goutil.InheritCancel).Wait()
3535
assert.That(t, s).Equal("")
3636
})
3737

3838
t.Run("normal execution", func(t *testing.T) {
3939
var s string
4040
goutil.Go(t.Context(), func(ctx context.Context) {
4141
s = "hello world!"
42-
}, false).Wait()
42+
}, goutil.InheritCancel).Wait()
4343
assert.That(t, s).Equal("hello world!")
4444
})
4545

@@ -55,7 +55,7 @@ func TestGo(t *testing.T) {
5555
default:
5656
resultCh <- "context was not cancelled"
5757
}
58-
}, false) // withoutCancel=false
58+
}, goutil.InheritCancel)
5959

6060
time.Sleep(5 * time.Millisecond)
6161
cancel()
@@ -78,7 +78,7 @@ func TestGo(t *testing.T) {
7878
case <-ctx.Done():
7979
resultCh <- "context was cancelled (unexpected with withoutCancel=true)"
8080
}
81-
}, true).Wait() // withoutCancel=true
81+
}, goutil.DetachCancel).Wait()
8282

8383
result := <-resultCh
8484
assert.That(t, result).Equal("context was not cancelled (as expected with withoutCancel=true)")
@@ -89,23 +89,23 @@ func TestGoValue(t *testing.T) {
8989
t.Run("panic recovery", func(t *testing.T) {
9090
s, err := goutil.GoValue(t.Context(), func(ctx context.Context) (string, error) {
9191
panic("something is wrong")
92-
}, false).Wait()
92+
}, goutil.InheritCancel).Wait()
9393
assert.That(t, s).Equal("")
9494
assert.Error(t, err).Matches("panic recovered: .*")
9595
})
9696

9797
t.Run("successful execution with int", func(t *testing.T) {
9898
i, err := goutil.GoValue(t.Context(), func(ctx context.Context) (int, error) {
9999
return 42, nil
100-
}, false).Wait()
100+
}, goutil.InheritCancel).Wait()
101101
assert.That(t, err).Nil()
102102
assert.That(t, i).Equal(42)
103103
})
104104

105105
t.Run("successful execution with string", func(t *testing.T) {
106106
s, err := goutil.GoValue(t.Context(), func(ctx context.Context) (string, error) {
107107
return "hello world!", nil
108-
}, false).Wait()
108+
}, goutil.InheritCancel).Wait()
109109
assert.That(t, err).Nil()
110110
assert.That(t, s).Equal("hello world!")
111111
})
@@ -115,7 +115,7 @@ func TestGoValue(t *testing.T) {
115115
for i := range 3 {
116116
arr = append(arr, goutil.GoValue(t.Context(), func(ctx context.Context) (int, error) {
117117
return i, nil
118-
}, false))
118+
}, goutil.InheritCancel))
119119
}
120120
for i, g := range arr {
121121
v, err := g.Wait()
@@ -128,7 +128,7 @@ func TestGoValue(t *testing.T) {
128128
expectedErr := errors.New("expected error")
129129
_, err := goutil.GoValue(t.Context(), func(ctx context.Context) (string, error) {
130130
return "", expectedErr
131-
}, false).Wait()
131+
}, goutil.InheritCancel).Wait()
132132
assert.That(t, err).Equal(expectedErr)
133133
})
134134

@@ -143,7 +143,7 @@ func TestGoValue(t *testing.T) {
143143
default:
144144
return "context was not cancelled", nil
145145
}
146-
}, false) // withoutCancel=false
146+
}, goutil.InheritCancel)
147147

148148
time.Sleep(5 * time.Millisecond)
149149
cancel()
@@ -164,7 +164,7 @@ func TestGoValue(t *testing.T) {
164164
case <-ctx.Done():
165165
return "context was cancelled (unexpected with withoutCancel=true)", nil
166166
}
167-
}, true).Wait() // withoutCancel=true
167+
}, goutil.DetachCancel).Wait()
168168

169169
assert.That(t, err).Nil()
170170
assert.That(t, result).Equal("context was not cancelled (as expected with withoutCancel=true)")
@@ -187,7 +187,7 @@ func TestGoValue(t *testing.T) {
187187
return "value: " + retrievedValueStr, nil
188188
}
189189
return "value not string", nil
190-
}, true).Wait() // withoutCancel=true
190+
}, goutil.DetachCancel).Wait()
191191

192192
assert.That(t, err).Nil()
193193
assert.That(t, result).Equal("value: test_value")

listutil/list.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2025 The Go-Spring Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package listutil
18+
19+
import (
20+
"container/list"
21+
)
22+
23+
// Element is an element of a linked list.
24+
type Element[T any] struct {
25+
*list.Element
26+
}
27+
28+
// Valid returns true if e is a valid element of list l.
29+
func (e Element[T]) Valid() bool {
30+
return e.Element != nil
31+
}
32+
33+
// Value returns the value of element e.
34+
func (e Element[T]) Value() T {
35+
return e.Element.Value.(T)
36+
}
37+
38+
// Next returns the next element of list l or nil if e is the last element.
39+
func (e Element[T]) Next() Element[T] {
40+
return Element[T]{e.Element.Next()}
41+
}
42+
43+
// Prev returns the previous element of list l or nil if e is the first element.
44+
func (e Element[T]) Prev() Element[T] {
45+
return Element[T]{e.Element.Prev()}
46+
}
47+
48+
// List is a doubly linked list.
49+
type List[T any] struct {
50+
*list.List
51+
}
52+
53+
// New returns an empty list.
54+
func New[T any]() *List[T] {
55+
return &List[T]{List: list.New()}
56+
}
57+
58+
// Len returns the number of elements of list l.
59+
// The complexity is O(1).
60+
func (l *List[T]) Len() int { return l.List.Len() }
61+
62+
// Front returns the first element of list l or nil if the list is empty.
63+
func (l *List[T]) Front() Element[T] {
64+
return Element[T]{l.List.Front()}
65+
}
66+
67+
// Back returns the last element of list l or nil if the list is empty.
68+
func (l *List[T]) Back() Element[T] {
69+
return Element[T]{l.List.Back()}
70+
}
71+
72+
// Remove removes e from l if e is an element of list l.
73+
// It returns the element value e.Value.
74+
// The element must not be nil.
75+
func (l *List[T]) Remove(e Element[T]) T {
76+
return l.List.Remove(e.Element).(T)
77+
}
78+
79+
// PushFront inserts a new element e with value v at the front of list l and returns e.
80+
func (l *List[T]) PushFront(v T) Element[T] {
81+
return Element[T]{l.List.PushFront(v)}
82+
}
83+
84+
// PushBack inserts a new element e with value v at the back of list l and returns e.
85+
func (l *List[T]) PushBack(v T) Element[T] {
86+
return Element[T]{l.List.PushBack(v)}
87+
}
88+
89+
// InsertBefore inserts a new element e with value v immediately before mark and returns e.
90+
// If mark is not an element of l, the list is not modified.
91+
// The mark must not be nil.
92+
func (l *List[T]) InsertBefore(v T, mark Element[T]) Element[T] {
93+
return Element[T]{l.List.InsertBefore(v, mark.Element)}
94+
}
95+
96+
// InsertAfter inserts a new element e with value v immediately after mark and returns e.
97+
// If mark is not an element of l, the list is not modified.
98+
// The mark must not be nil.
99+
func (l *List[T]) InsertAfter(v T, mark Element[T]) Element[T] {
100+
return Element[T]{l.List.InsertAfter(v, mark.Element)}
101+
}
102+
103+
// MoveToFront moves element e to the front of list l.
104+
// If e is not an element of l, the list is not modified.
105+
// The element must not be nil.
106+
func (l *List[T]) MoveToFront(e Element[T]) {
107+
l.List.MoveToFront(e.Element)
108+
}
109+
110+
// MoveToBack moves element e to the back of list l.
111+
// If e is not an element of l, the list is not modified.
112+
// The element must not be nil.
113+
func (l *List[T]) MoveToBack(e Element[T]) {
114+
l.List.MoveToBack(e.Element)
115+
}
116+
117+
// MoveBefore moves element e to its new position before mark.
118+
// If e or mark is not an element of l, or e == mark, the list is not modified.
119+
// The element and mark must not be nil.
120+
func (l *List[T]) MoveBefore(e, mark Element[T]) {
121+
l.List.MoveBefore(e.Element, mark.Element)
122+
}
123+
124+
// MoveAfter moves element e to its new position after mark.
125+
// If e or mark is not an element of l, or e == mark, the list is not modified.
126+
// The element and mark must not be nil.
127+
func (l *List[T]) MoveAfter(e, mark Element[T]) {
128+
l.List.MoveAfter(e.Element, mark.Element)
129+
}
130+
131+
// PushBackList inserts a copy of another list at the back of list l.
132+
// The lists l and other may be the same. They must not be nil.
133+
func (l *List[T]) PushBackList(other *List[T]) {
134+
l.List.PushBackList(other.List)
135+
}
136+
137+
// PushFrontList inserts a copy of another list at the front of list l.
138+
// The lists l and other may be the same. They must not be nil.
139+
func (l *List[T]) PushFrontList(other *List[T]) {
140+
l.List.PushFrontList(other.List)
141+
}

0 commit comments

Comments
 (0)