Atlas › Test

TestRequire_DeadlockDetected

Exact test identity: go.temporal.io/server/common/testing/await/TestRequire_DeadlockDetected

Package
go.temporal.io/server/common/testing/await
Suite / test hierarchy
TestRequire_DeadlockDetected
Test
TestRequire_DeadlockDetected
Introduced at
require_ctx.go ×1 Frontier kind: Joint frontier
Covered ranges
44
Covered lines
175
Covered files
4

Covered source

Expand a file to inspect source; the > gutter marks covered lines.

go.temporal.io/server/common/testing/await/require_ctx.go 78 covered LOC · 22 ranges

Open complete file

20 const defaultSoftDeadlockTimeout = 30 * time.Second
21
22 > func softDeadlockTimeout() time.Duration { require_ctx.go
23 > if s := os.Getenv(softDeadlockTimeoutEnvVar); s != "" {
24 > if d, err := time.ParseDuration(s); err == nil { require_ctx.go
25 > return d
26 > }
27 }
28 return defaultSoftDeadlockTimeout
40 const defaultHardDeadlockTimeout = 10 * time.Second
41
42 > func hardDeadlockTimeout() time.Duration { require_ctx.go
43 > if s := os.Getenv(hardDeadlockTimeoutEnvVar); s != "" {
44 > if d, err := time.ParseDuration(s); err == nil { require_ctx.go
45 > return d
46 > }
47 }
48 return defaultHardDeadlockTimeout
54 // Pass the *await.T to require.*/assert.* — failures cause a retry, not a
55 // test failure. Use t.Context() inside the callback to honor the timeout.
56 > func Require(ctx context.Context, tb testing.TB, condition func(*T), timeout, pollInterval time.Duration) { require_ctx.go
57 > tb.Helper()
58 > run(ctx, tb, condition, legacyConfig(timeout, pollInterval, ""), "Require", requireMisuseHint, true)
59 > }
60
61 // Requiref is like [Require] but adds a formatted message to the timeout
74 misuseHint string,
75 cancellable bool,
76 > ) { require_ctx.go
77 > tb.Helper()
78 >
79 > // Skip if the test already failed — no point polling.
80 > if tb.Failed() {
81 tb.Logf("%s: skipping (test already failed)", funcName)
82 return
83 }
84 // Guard: context.WithDeadline panics on a nil parent.
85 > if parentCtx == nil { require_ctx.go
86 tb.Fatalf("%s: nil context", funcName)
87 return
88 }
89
90 > deadline := time.Now().Add(cfg.totalTimeout) require_ctx.go
91 >
92 > // Cap at the parent context's deadline if it's earlier than our timeout.
93 > if parentDeadline, hasDeadline := parentCtx.Deadline(); hasDeadline && parentDeadline.Before(deadline) {
94 deadline = parentDeadline
95 }
97 // Cap at the test's deadline if it's earlier than our deadline.
98 // Ideally, the parent context already accounts for the test's deadline - but we are being defensive.
99 > if d, ok := tb.(interface{ Deadline() (time.Time, bool) }); ok { require_ctx.go
100 if testDeadline, hasDeadline := d.Deadline(); hasDeadline && testDeadline.Before(deadline) {
101 deadline = testDeadline
103 }
104
105 > effectiveTimeout := max(0, time.Until(deadline)) require_ctx.go
106 > awaitCtx, awaitCancel := context.WithDeadline(parentCtx, deadline)
107 > defer awaitCancel()
108 >
109 > report := timeoutReport{effectiveTimeout: effectiveTimeout}
110 >
111 > for {
112 > // Parent context was canceled while we were sleeping (not our deadline).
113 > if err := awaitCtx.Err(); err != nil && !deadlineReached(deadline) {
114 report.reportAttemptErrors(tb)
115 tb.Fatalf("%s: context canceled before condition was satisfied: %v", funcName, err)
117 }
118
119 > report.nextPoll() require_ctx.go
120 >
121 > // Per-attempt context: bounded by the configured attempt timeout and
122 > // further capped by the overall awaitCtx.
123 > attemptCtx, attemptCancel := context.WithTimeout(awaitCtx, cfg.attemptTimeout)
124 > t := &T{tb: tb, ctx: attemptCtx}
125 >
126 > // Run attempt.
127 > res := runAttempt(t, condition, attemptCancel, funcName, cancellable)
128 > attemptCancel()
129 > if res.panicVal != nil {
130 panic(res.panicVal) // propagate to caller
131 }
132 > if res.deadlocked { require_ctx.go
133 > report.reportAttemptErrors(tb) require_ctx.go
134 > if cancellable {
135 > tb.Fatalf("%s: condition still running %v past context cancellation — does it honor t.Context()? (%d attempts)", require_ctx.go
136 > funcName, hardDeadlockTimeout(), report.attempts)
137 > } else { require_ctx.go
138 tb.Fatalf("%s: condition still running %v past deadline (%d attempts)",
139 funcName, hardDeadlockTimeout(), report.attempts)
211 funcName string,
212 cancellable bool,
213 > ) attemptResult { require_ctx.go
214 > done := make(chan attemptResult, 1)
215 >
216 > go func() {
217 > completed := false
218 > defer func() {
219 if r := recover(); r != nil {
220 if _, ok := r.(attemptFailed); ok {
229 done <- attemptResult{stopped: !completed}
230 }()
231 > condition(t) require_ctx.go
232 > completed = true
233 }()
234
235 > if cancellable { require_ctx.go
236 > // Soft phase: wait for the condition, our soft timer, or parent cancel. require_ctx.go
237 > softTimer := time.NewTimer(softDeadlockTimeout())
238 > defer softTimer.Stop()
239 >
240 > select {
241 case r := <-done:
242 return r
243 > case <-softTimer.C: require_ctx.go
244 > // Soft deadlock: log a warning.
245 > t.tb.Logf("%s: soft deadlock — condition still running after %v; waiting %v before declaring hard deadlock",
246 > funcName, softDeadlockTimeout(), hardDeadlockTimeout())
247 >
248 > // Cancel so the condition can observe ctx.Done().
249 > cancel()
250 case <-t.ctx.Done():
251 // Parent cancelled (await deadline reached or upstream cancel).
255
256 // Hard phase: wait for the condition or the hard timer.
257 > hardTimer := time.NewTimer(hardDeadlockTimeout()) require_ctx.go
258 > defer hardTimer.Stop()
259 >
260 > select {
261 case r := <-done:
262 return r
263 > case <-hardTimer.C: require_ctx.go
264 > return attemptResult{deadlocked: true}
265 }
266 }
go.temporal.io/server/common/testing/testcontext/context.go 70 covered LOC · 15 ranges

Open complete file

39
40 // DefaultTimeout returns the effective default timeout for test-scoped contexts.
41 > func DefaultTimeout() time.Duration { context.go
42 > return effectiveTimeout(0)
43 > }
44
45 // For returns the test-scoped context for tb. The context is canceled
49 // return the same context, but an explicit different timeout fails instead of
50 // being silently ignored.
51 > func For(tb testing.TB, opts ...Option) context.Context { context.go
52 > tb.Helper()
53 >
54 > cfg := config{timeout: DefaultTimeout()}
55 > for _, opt := range opts {
56 opt(&cfg)
57 }
58
59 > st := getContextState(tb, cfg.timeout) context.go
60 > st.configure(tb, cfg)
61 > return st.context()
62 }
63
98 }
99
100 > func getContextState(tb testing.TB, timeout time.Duration) *contextState { context.go
101 > tb.Helper()
102 >
103 > testContexts.Lock()
104 > defer testContexts.Unlock()
105 >
106 > if st, ok := testContexts.byTest[tb]; ok {
107 return st
108 }
109
110 > ctx, cancel := context.WithTimeout(tb.Context(), timeout) context.go
111 >
112 > // Annotate gRPC requests with the test name for OTEL tracing.
113 > ctx = metadata.AppendToOutgoingContext(ctx, testNameMetadataKey, tb.Name())
114 >
115 > st := &contextState{
116 > ctx: ctx,
117 > cancel: cancel,
118 > timeout: timeout,
119 > decorators: make(map[any]struct{}),
120 > }
121 > testContexts.byTest[tb] = st
122 >
123 > tb.Cleanup(func() {
124 > err := st.err()
125 > st.cancel()
126 > testContexts.Lock()
127 > delete(testContexts.byTest, tb)
128 > testContexts.Unlock()
129 > if err == context.DeadlineExceeded {
130 tb.Errorf("test exceeded timeout of %v", st.timeout)
131 }
132 > st.release() context.go
133 })
134 > return st context.go
135 }
136
137 > func (s *contextState) configure(tb testing.TB, cfg config) { context.go
138 > tb.Helper()
139 >
140 > s.mu.Lock()
141 > defer s.mu.Unlock()
142 >
143 > if cfg.timeoutSet && cfg.timeout != s.timeout {
144 tb.Fatalf("testcontext: test context already exists with timeout %v; cannot change it to %v", s.timeout, cfg.timeout)
145 }
168 }
169
170 > func (s *contextState) context() context.Context { context.go
171 > s.mu.Lock()
172 > defer s.mu.Unlock()
173 > return s.ctx
174 > }
175
176 > func (s *contextState) err() error { context.go
177 > s.mu.Lock()
178 > defer s.mu.Unlock()
179 > return s.ctx.Err()
180 > }
181
182 > func (s *contextState) release() { context.go
183 > s.mu.Lock()
184 > defer s.mu.Unlock()
185 > s.ctx = nil
186 > }
187
188 > func effectiveTimeout(customTimeout time.Duration) (timeout time.Duration) { context.go
189 > defer func() {
190 > // Build flag TEMPORAL_DEBUG applies a timeout multiplier to all test timeouts.
191 > timeout *= debug.TimeoutMultiplier
192 > }()
193
194 // 1. Custom timeout (via WithTimeout option).
195 > if customTimeout > 0 { context.go
196 return customTimeout
197 }
198
199 // 2. TEMPORAL_TEST_TIMEOUT environment variable.
200 > if envTimeout := os.Getenv("TEMPORAL_TEST_TIMEOUT"); envTimeout != "" { context.go
201 if dur, err := time.ParseDuration(envTimeout); err == nil && dur > 0 {
202 return dur
205
206 // 3. Default timeout.
207 > return defaultTimeout context.go
208 }
go.temporal.io/server/common/testing/await/config.go 17 covered LOC · 3 ranges

Open complete file

17 }
18
19 > func newConfig() config { config.go
20 > return config{
21 > attemptTimeout: envDuration(attemptTimeoutEnvVar, 10*time.Second) * debug.TimeoutMultiplier,
22 > }
23 > }
24
25 > func legacyConfig(timeout, pollInterval time.Duration, timeoutMsg string) config { config.go
26 > cfg := newConfig()
27 > cfg.totalTimeout = timeout
28 > cfg.pollInterval = pollInterval
29 > cfg.timeoutMsg = timeoutMsg
30 > return cfg
31 > }
32
33 > func envDuration(name string, fallback time.Duration) time.Duration { config.go
34 > if s := os.Getenv(name); s != "" {
35 > if d, err := time.ParseDuration(s); err == nil && d > 0 {
36 > return d
37 > }
38 }
39 return fallback
go.temporal.io/server/common/testing/await/report.go 10 covered LOC · 4 ranges

Open complete file

28 }
29
30 > func (r *timeoutReport) nextPoll() { report.go
31 > r.attempts++
32 > }
33
34 func (r *timeoutReport) recordErrors(errors []string) {
42 }
43
44 > func (r timeoutReport) reportAttemptErrors(tb testing.TB) { report.go
45 > reportAttemptErrors(tb, r.failures)
46 > }
47
48 func (r timeoutReport) reportTimeout(tb testing.TB, funcName, timeoutMsg string) {
56 }
57
58 > func reportAttemptErrors(tb testing.TB, failures []attemptFailure) { report.go
59 > if len(failures) == 0 {
60 > return report.go
61 > }
62
63 var b strings.Builder