Skip to content

Commit d601936

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 d601936

6 files changed

Lines changed: 180 additions & 55 deletions

File tree

common/failpoint/failpoint.go

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,111 @@
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+
// Failpoint represents one named failpoint's runtime state: whether it's
13+
// enabled, its optional string value (from a --failpoints Name=value pair),
14+
// and — for failpoints that need to pause code until externally resumed —
15+
// the signaling to coordinate that pause.
16+
type Failpoint struct {
17+
name Name
18+
value string
1719

18-
func init() {
19-
values = make(map[string]string)
20+
mu sync.Mutex
21+
reachedCh chan struct{}
22+
reachedClosed bool
23+
signalCh chan struct{}
24+
signalClosed bool
2025
}
2126

22-
func Reset() {
23-
values = make(map[string]string)
27+
func newFailpoint(name Name, value string) *Failpoint {
28+
return &Failpoint{
29+
name: name,
30+
value: value,
31+
reachedCh: make(chan struct{}),
32+
signalCh: make(chan struct{}),
33+
}
34+
}
35+
36+
func (fp *Failpoint) Name() Name { return fp.name }
37+
func (fp *Failpoint) Value() string { return fp.value }
38+
39+
// Wait signals that the caller has reached this pause point (so a concurrent
40+
// call to Reached returns) and then blocks until Signal is called.
41+
func (fp *Failpoint) Wait() {
42+
fp.mu.Lock()
43+
if !fp.reachedClosed {
44+
close(fp.reachedCh)
45+
fp.reachedClosed = true
46+
}
47+
fp.mu.Unlock()
48+
49+
<-fp.signalCh
50+
}
51+
52+
// Reached blocks until Wait has been called.
53+
func (fp *Failpoint) Reached() {
54+
<-fp.reachedCh
2455
}
2556

57+
// Signal releases a call blocked in Wait. Safe to call more than once.
58+
func (fp *Failpoint) Signal() {
59+
fp.mu.Lock()
60+
defer fp.mu.Unlock()
61+
if !fp.signalClosed {
62+
close(fp.signalCh)
63+
fp.signalClosed = true
64+
}
65+
}
66+
67+
// Manager tracks which failpoints are currently enabled.
68+
type Manager interface {
69+
// Parse enables a comma-separated list of failpoint=value pairs, as
70+
// passed to --failpoints.
71+
Parse(arg string)
72+
// Reset disables every failpoint.
73+
Reset()
74+
// Get returns the Failpoint registered under name, and true, if it is
75+
// currently enabled.
76+
Get(name Name) (*Failpoint, bool)
77+
}
78+
79+
// defaultManager is set by manager.go's or noop_manager.go's init function,
80+
// depending on whether this binary was built with the failpoints build tag.
81+
var defaultManager Manager
82+
2683
// ParseFailpoints registers a comma-separated list of failpoint=value pairs.
2784
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-
}
85+
defaultManager.Parse(arg)
86+
}
87+
88+
// Reset disables every failpoint.
89+
func Reset() {
90+
defaultManager.Reset()
3891
}
3992

4093
// Get returns the value of the given failpoint and true, if it exists, and
4194
// false otherwise.
42-
func Get(fp string) (string, bool) {
43-
val, ok := values[fp]
44-
return val, ok
95+
func Get(name Name) (string, bool) {
96+
fp, ok := defaultManager.Get(name)
97+
if !ok {
98+
return "", false
99+
}
100+
return fp.Value(), true
45101
}
46102

47103
// Enabled returns true iff the given failpoint has been turned on.
48-
func Enabled(fp string) bool {
49-
_, ok := Get(fp)
104+
func Enabled(name Name) bool {
105+
_, ok := defaultManager.Get(name)
50106
return ok
51107
}
108+
109+
// Lookup returns the Failpoint registered under name, and true, if it has
110+
// been enabled. Callers that need to pause until externally resumed (see
111+
// Failpoint.Wait) use this instead of Enabled.
112+
func Lookup(name Name) (*Failpoint, bool) {
113+
return defaultManager.Get(name)
114+
}

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)