Skip to content

Commit 0f08f00

Browse files
committed
TOOLS-4237 Convert failpoints to a struct-based interface
Replace the plain-string failpoint identifiers and duplicated per-build-tag function implementations with a Manager interface (a real manager backed by a mutex-guarded map, and a noopManager used when the failpoints build tag isn't set) plus a Failpoint struct that carries a name, optional string value, and the signaling needed by failpoints that pause code until externally resumed. This follows the same shape as mongosync's mongo-go/failpoints package (Manager interface + Failpoint struct with a resume channel), scaled down to what mongo-tools actually needs: a single global default manager rather than per-instance injection, since mongo-tools failpoints are configured once via a CLI flag rather than per test-created app context. Only the tiny "which manager is the default" decision is build-tag gated now (in manager.go's and noop_manager.go's own init functions); the Failpoint type and the package-level API (ParseFailpoints, Reset, Get, Enabled, Lookup) are unconditionally compiled, matching how mongosync keeps its failpoints package itself untagged and gates only the wiring layer.
1 parent b9dcadc commit 0f08f00

6 files changed

Lines changed: 203 additions & 55 deletions

File tree

common/failpoint/failpoint.go

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,134 @@
44
// not use this file except in compliance with the License. You may obtain
55
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
66

7-
//go:build failpoints
8-
97
// Package failpoint implements triggers for custom debugging behavior.
108
package failpoint
119

12-
import (
13-
"strings"
14-
)
10+
import "sync"
1511

16-
var values map[string]string
12+
// pauseSignal coordinates a single pause: reached is closed by wait to
13+
// signal that the paused code has arrived at its pause point, and signal is
14+
// closed by signal to release it. mu guards the two closed flags so that
15+
// closing either channel more than once is a safe no-op rather than a panic.
16+
type pauseSignal struct {
17+
mu sync.Mutex
18+
reachedCh chan struct{}
19+
reachedClosed bool
20+
signalCh chan struct{}
21+
signalClosed bool
22+
}
1723

18-
func init() {
19-
values = make(map[string]string)
24+
func newPauseSignal() *pauseSignal {
25+
return &pauseSignal{
26+
reachedCh: make(chan struct{}),
27+
signalCh: make(chan struct{}),
28+
}
2029
}
2130

22-
func Reset() {
23-
values = make(map[string]string)
31+
// wait closes reachedCh (so a concurrent call to reached returns) and then
32+
// blocks until signal is called.
33+
func (p *pauseSignal) wait() {
34+
p.mu.Lock()
35+
if !p.reachedClosed {
36+
close(p.reachedCh)
37+
p.reachedClosed = true
38+
}
39+
p.mu.Unlock()
40+
41+
<-p.signalCh
42+
}
43+
44+
// reached blocks until wait has been called.
45+
func (p *pauseSignal) reached() {
46+
<-p.reachedCh
47+
}
48+
49+
// signal releases a call blocked in wait. Safe to call more than once.
50+
func (p *pauseSignal) signal() {
51+
p.mu.Lock()
52+
defer p.mu.Unlock()
53+
if !p.signalClosed {
54+
close(p.signalCh)
55+
p.signalClosed = true
56+
}
57+
}
58+
59+
// Failpoint represents one named failpoint's runtime state: whether it's
60+
// enabled, its optional string value (from a --failpoints Name=value pair),
61+
// and — for failpoints that need to pause code until externally resumed —
62+
// the signaling to coordinate that pause.
63+
type Failpoint struct {
64+
name Name
65+
value string
66+
pause *pauseSignal
67+
}
68+
69+
func newFailpoint(name Name, value string) *Failpoint {
70+
return &Failpoint{
71+
name: name,
72+
value: value,
73+
pause: newPauseSignal(),
74+
}
75+
}
76+
77+
func (fp *Failpoint) Name() Name { return fp.name }
78+
func (fp *Failpoint) Value() string { return fp.value }
79+
80+
// Wait signals that the caller has reached this pause point (so a concurrent
81+
// call to Reached returns) and then blocks until Signal is called.
82+
func (fp *Failpoint) Wait() { fp.pause.wait() }
83+
84+
// Reached blocks until Wait has been called.
85+
func (fp *Failpoint) Reached() { fp.pause.reached() }
86+
87+
// Signal releases a call blocked in Wait. Safe to call more than once.
88+
func (fp *Failpoint) Signal() { fp.pause.signal() }
89+
90+
// Manager tracks which failpoints are currently enabled.
91+
type Manager interface {
92+
// Parse enables a comma-separated list of failpoint=value pairs, as
93+
// passed to --failpoints.
94+
Parse(arg string)
95+
// Reset disables every failpoint.
96+
Reset()
97+
// Get returns the Failpoint registered under name, and true, if it is
98+
// currently enabled.
99+
Get(name Name) (*Failpoint, bool)
24100
}
25101

102+
// defaultManager is set by manager.go's or noop_manager.go's init function,
103+
// depending on whether this binary was built with the failpoints build tag.
104+
var defaultManager Manager
105+
26106
// ParseFailpoints registers a comma-separated list of failpoint=value pairs.
27107
func ParseFailpoints(arg string) {
28-
args := strings.Split(arg, ",")
29-
for _, fp := range args {
30-
if sep := strings.Index(fp, "="); sep != -1 {
31-
key := fp[:sep]
32-
val := fp[sep+1:]
33-
values[key] = val
34-
continue
35-
}
36-
values[fp] = ""
37-
}
108+
defaultManager.Parse(arg)
109+
}
110+
111+
// Reset disables every failpoint.
112+
func Reset() {
113+
defaultManager.Reset()
38114
}
39115

40116
// Get returns the value of the given failpoint and true, if it exists, and
41117
// false otherwise.
42-
func Get(fp string) (string, bool) {
43-
val, ok := values[fp]
44-
return val, ok
118+
func Get(name Name) (string, bool) {
119+
fp, ok := defaultManager.Get(name)
120+
if !ok {
121+
return "", false
122+
}
123+
return fp.Value(), true
45124
}
46125

47126
// Enabled returns true iff the given failpoint has been turned on.
48-
func Enabled(fp string) bool {
49-
_, ok := Get(fp)
127+
func Enabled(name Name) bool {
128+
_, ok := defaultManager.Get(name)
50129
return ok
51130
}
131+
132+
// Lookup returns the Failpoint registered under name, and true, if it has
133+
// been enabled. Callers that need to pause until externally resumed (see
134+
// Failpoint.Wait) use this instead of Enabled.
135+
func Lookup(name Name) (*Failpoint, bool) {
136+
return defaultManager.Get(name)
137+
}

common/failpoint/failpoint_disabled.go

Lines changed: 0 additions & 24 deletions
This file was deleted.

common/failpoint/failpoints.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
package failpoint
88

9-
// Supported failpoint names.
9+
// Name identifies a failpoint that can be turned on via --failpoints.
10+
type Name string
11+
12+
func (n Name) String() string { return string(n) }
13+
14+
// Supported failpoints.
1015
const (
11-
PauseBeforeDumping = "PauseBeforeDumping"
12-
SlowBSONDump = "SlowBSONDump"
16+
PauseBeforeDumping = Name("PauseBeforeDumping")
17+
SlowBSONDump = Name("SlowBSONDump")
1318
)

common/failpoint/manager.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (C) MongoDB, Inc. 2014-present.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
7+
//go:build failpoints
8+
9+
package failpoint
10+
11+
import (
12+
"strings"
13+
"sync"
14+
)
15+
16+
// manager is the real Manager implementation, used when this binary is
17+
// built with the failpoints build tag.
18+
type manager struct {
19+
mu sync.Mutex
20+
fps map[Name]*Failpoint
21+
}
22+
23+
func newManager() *manager {
24+
return &manager{fps: make(map[Name]*Failpoint)}
25+
}
26+
27+
func (m *manager) Parse(arg string) {
28+
m.mu.Lock()
29+
defer m.mu.Unlock()
30+
31+
for part := range strings.SplitSeq(arg, ",") {
32+
name, value, _ := strings.Cut(part, "=")
33+
m.fps[Name(name)] = newFailpoint(Name(name), value)
34+
}
35+
}
36+
37+
func (m *manager) Reset() {
38+
m.mu.Lock()
39+
defer m.mu.Unlock()
40+
m.fps = make(map[Name]*Failpoint)
41+
}
42+
43+
func (m *manager) Get(name Name) (*Failpoint, bool) {
44+
m.mu.Lock()
45+
defer m.mu.Unlock()
46+
fp, ok := m.fps[name]
47+
return fp, ok
48+
}
49+
50+
func init() {
51+
defaultManager = newManager()
52+
}

common/failpoint/noop_manager.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (C) MongoDB, Inc. 2014-present.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
// not use this file except in compliance with the License. You may obtain
5+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
7+
//go:build !failpoints
8+
9+
package failpoint
10+
11+
// noopManager is the Manager implementation used when this binary is built
12+
// without the failpoints build tag: every failpoint is always disabled.
13+
type noopManager struct{}
14+
15+
func newNoopManager() *noopManager {
16+
return &noopManager{}
17+
}
18+
19+
func (*noopManager) Parse(string) {}
20+
21+
func (*noopManager) Reset() {}
22+
23+
func (*noopManager) Get(Name) (*Failpoint, bool) {
24+
return nil, false
25+
}
26+
27+
func init() {
28+
defaultManager = newNoopManager()
29+
}

mongodump/oplog_dump_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func TestOplogDumpCollModIndexUniqueness(t *testing.T) {
172172
require.NoError(t, md.Init())
173173

174174
// Enable a failpoint so that the test can create oplogs during dump.
175-
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping)
175+
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping.String())
176176
defer failpoint.Reset()
177177

178178
go func() {
@@ -330,7 +330,7 @@ func TestOplogDumpBypassDocumentValidation(t *testing.T) {
330330
require.NoError(t, md.Init())
331331

332332
// Enable a failpoint so that the test can create oplogs during dump.
333-
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping)
333+
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping.String())
334334
defer failpoint.Reset()
335335

336336
go func() {
@@ -480,7 +480,7 @@ func TestOplogDumpCollModTTL(t *testing.T) {
480480
require.NoError(t, md.Init())
481481

482482
// Enable a failpoint so that the test can create oplogs during dump.
483-
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping)
483+
failpoint.ParseFailpoints(failpoint.PauseBeforeDumping.String())
484484
defer failpoint.Reset()
485485

486486
go func() {

0 commit comments

Comments
 (0)