@@ -13,7 +13,7 @@ import (
13
13
"github.com/PowerDNS/simpleblob"
14
14
"github.com/PowerDNS/simpleblob/backends/memory"
15
15
"github.com/sirupsen/logrus"
16
- "github.com/stretchr/testify/assert "
16
+ "github.com/stretchr/testify/require "
17
17
"powerdns.com/platform/lightningstream/config"
18
18
"powerdns.com/platform/lightningstream/config/logger"
19
19
"powerdns.com/platform/lightningstream/lmdbenv"
@@ -23,7 +23,7 @@ import (
23
23
24
24
const testLMDBName = "default"
25
25
const testDBIName = "test"
26
- const tick = 100 * time .Millisecond
26
+ const tick = 10 * time .Millisecond
27
27
28
28
func TestSyncer_Sync_startup (t * testing.T ) {
29
29
t .Run ("with-timestamped-schema" , func (t * testing.T ) {
@@ -44,6 +44,13 @@ func doTest(t *testing.T, withHeader bool) {
44
44
syncerA , envA := createInstance (t , "a" , st , withHeader )
45
45
syncerB , envB := createInstance (t , "b" , st , withHeader )
46
46
47
+ // For some reason trying to close the envs in this test segfaults on Linux (not on macOS).
48
+ // It appears like this is caused by the syncer still running after cancellation
49
+ // (and thus after the env is closed), but I did not get to the bottom of this yet.
50
+ // [signal SIGSEGV: segmentation violation code=0x1 addr=0x7fd015132090 pc=0x8dc942]
51
+ //defer envA.Close()
52
+ //defer envB.Close()
53
+
47
54
ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
48
55
defer cancel ()
49
56
ctxA , cancelA := context .WithCancel (ctx )
@@ -55,46 +62,36 @@ func doTest(t *testing.T, withHeader bool) {
55
62
t .Log ("Starting syncer A" )
56
63
go runSync (ctxA , syncerA )
57
64
58
- t .Log ("----------" )
59
- time .Sleep (2 * tick )
60
65
t .Log ("----------" )
61
66
62
67
// Expecting one snapshot on startup, because not empty
63
- logSnapshotList (t , st )
64
- entries := listInstanceSnapshots (st , "a" )
65
- assert .Len (t , entries , 1 , "A" )
68
+ requireSnapshotsLenWait (t , st , 1 , "A" )
66
69
67
70
// Start syncer B with an empty LMDB
71
+ // Starting with an empty LMDB is a special case that will not trigger any
72
+ // local snapshot.
68
73
t .Log ("Starting syncer B" )
69
74
go runSync (ctxB , syncerB )
70
75
71
- t .Log ("----------" )
72
- time .Sleep (2 * tick )
73
76
t .Log ("----------" )
74
77
75
- // No snapshot, because empty
76
- logSnapshotList (t , st )
77
- entries = listInstanceSnapshots (st , "b" )
78
- assert .Len (t , entries , 0 , "B" )
78
+ // Wait until the data from A was synced to B
79
+ assertKeyWait (t , envB , "foo" , "v1" , withHeader )
79
80
80
- assertKey (t , envB , "foo" , "v1" , withHeader )
81
+ // No snapshot made by B, because we started empty
82
+ requireSnapshotsLenWait (t , st , 0 , "B" )
81
83
82
84
// Now set something in B
83
85
setKey (t , envB , "foo" , "v2" , withHeader )
84
86
85
- t .Log ("----------" )
86
- time .Sleep (3 * tick )
87
87
t .Log ("----------" )
88
88
89
89
// New snapshot in B, no new one in A
90
- logSnapshotList (t , st )
91
- entries = listInstanceSnapshots (st , "b" )
92
- assert .Len (t , entries , 1 , "B" )
93
- entries = listInstanceSnapshots (st , "a" )
94
- assert .Len (t , entries , 1 , "A" )
90
+ requireSnapshotsLenWait (t , st , 1 , "B" )
91
+ requireSnapshotsLenWait (t , st , 1 , "A" )
95
92
96
93
// New value should be present in A
97
- assertKey (t , envB , "foo" , "v2" , withHeader )
94
+ assertKeyWait (t , envB , "foo" , "v2" , withHeader )
98
95
99
96
// Restart syncer for A
100
97
t .Log ("Restarting syncer A" )
@@ -103,16 +100,13 @@ func doTest(t *testing.T, withHeader bool) {
103
100
t .Log ("----------" )
104
101
go runSync (ctxA , syncerA )
105
102
106
- t .Log ("----------" )
107
- time .Sleep (3 * tick )
108
103
t .Log ("----------" )
109
104
110
105
// Check is the contents of A are still correct after restart
111
- assertKey (t , envA , "foo" , "v2" , withHeader )
112
- entries = listInstanceSnapshots (st , "a" )
106
+ assertKeyWait (t , envA , "foo" , "v2" , withHeader )
113
107
// A new snapshot should always be created on startup, in case the LMDB
114
108
// was modified while it was down.
115
- assert . Len (t , entries , 2 , "A" )
109
+ requireSnapshotsLenWait (t , st , 2 , "A" )
116
110
117
111
// Stopping syncer for A
118
112
t .Log ("Stopping syncer A" )
@@ -127,15 +121,12 @@ func doTest(t *testing.T, withHeader bool) {
127
121
ctxA , cancelA = context .WithCancel (ctx )
128
122
go runSync (ctxA , syncerA )
129
123
t .Log ("----------" )
130
- time .Sleep (6 * tick )
131
- t .Log ("----------" )
132
124
125
+ // New value in A should get synced to B
126
+ assertKeyWait (t , envB , "new" , "hello" , withHeader )
133
127
// Check if the contents of A are still correct after restart
134
- assertKey (t , envA , "new" , "hello" , withHeader )
135
- // It should also be synced to B
136
- assertKey (t , envB , "new" , "hello" , withHeader )
137
- entries = listInstanceSnapshots (st , "a" )
138
- assert .Len (t , entries , 3 , "A" )
128
+ assertKeyWait (t , envA , "new" , "hello" , withHeader )
129
+ requireSnapshotsLenWait (t , st , 3 , "A" )
139
130
140
131
cancelA ()
141
132
cancelB ()
@@ -144,16 +135,16 @@ func doTest(t *testing.T, withHeader bool) {
144
135
145
136
func createInstance (t * testing.T , name string , st simpleblob.Interface , timestamped bool ) (* Syncer , * lmdb.Env ) {
146
137
env , tmp , err := createLMDB (t )
147
- assert .NoError (t , err )
138
+ require .NoError (t , err )
148
139
149
140
c := createConfig (name , tmp , timestamped )
150
- syncer , err := New ("default" , st , c , c .LMDBs [testLMDBName ], Options {})
151
- assert .NoError (t , err )
141
+ syncer , err := New ("default" , env , st , c , c .LMDBs [testLMDBName ], Options {})
142
+ require .NoError (t , err )
152
143
153
144
return syncer , env
154
145
}
155
146
156
- func logSnapshotList (t * testing.T , st simpleblob.Interface ) {
147
+ func LogSnapshotList (t * testing.T , st simpleblob.Interface ) {
157
148
ctx := context .Background ()
158
149
entries , _ := st .List (ctx , "" )
159
150
var lines []string
@@ -178,17 +169,57 @@ func listInstanceSnapshots(st simpleblob.Interface, instance string) simpleblob.
178
169
return entries
179
170
}
180
171
172
+ func requireSnapshotsLenWait (t * testing.T , st simpleblob.Interface , expLen int , instance string ) {
173
+ var list simpleblob.BlobList
174
+ // Retry until it succeeds
175
+ var i int
176
+ const maxIter = 150
177
+ const sleepTime = 10 * time .Millisecond
178
+ defer func () {
179
+ t .Logf ("Waited %d/%d iterations for the expected snapshot length" , i , maxIter )
180
+ }()
181
+ for i = 0 ; i < maxIter ; i ++ {
182
+ list = listInstanceSnapshots (st , strings .ToLower (instance ))
183
+ l := len (list )
184
+ if l == expLen {
185
+ return
186
+ }
187
+ time .Sleep (sleepTime )
188
+ }
189
+ // This one is actually expected to fail, call it for the formatting
190
+ t .Logf ("Gave up on waiting for the expected snapshot length" )
191
+ require .Len (t , list , expLen , instance )
192
+ }
193
+
181
194
func runSync (ctx context.Context , syncer * Syncer ) {
182
195
err := syncer .Sync (ctx )
183
196
if err != nil && err != context .Canceled {
184
197
logrus .WithError (err ).WithField ("syncer" , syncer .name ).Error ("Syncer Sync error" )
185
198
}
186
199
}
187
200
188
- func assertKey (t * testing.T , env * lmdb.Env , key , val string , withHeader bool ) {
189
- kv , err := dumpData (env , withHeader )
190
- assert .NoError (t , err )
191
- assert .Equal (t , val , kv [key ])
201
+ func assertKeyWait (t * testing.T , env * lmdb.Env , key , val string , withHeader bool ) {
202
+ var kv map [string ]string
203
+ var err error
204
+ var i int
205
+ const maxIter = 150
206
+ const sleepTime = 10 * time .Millisecond
207
+ defer func () {
208
+ t .Logf ("Waited %d/%d iterations for the expected key" , i , maxIter )
209
+ }()
210
+ for i = 0 ; i < maxIter ; i ++ {
211
+ kv , err = dumpData (env , withHeader )
212
+ if err != nil && ! lmdb .IsNotFound (err ) {
213
+ require .NoError (t , err )
214
+ }
215
+ if kv [key ] == val {
216
+ return
217
+ }
218
+ time .Sleep (sleepTime )
219
+ }
220
+ // Expected to fail now, called for formatting
221
+ t .Logf ("Gave up on waiting for the expected key" )
222
+ require .Equal (t , val , kv [key ])
192
223
}
193
224
194
225
func setKey (t * testing.T , env * lmdb.Env , key , val string , withHeader bool ) {
@@ -210,13 +241,13 @@ func setKey(t *testing.T, env *lmdb.Env, key, val string, withHeader bool) {
210
241
err = txn .Put (dbi , []byte (key ), valb , 0 )
211
242
return err
212
243
})
213
- assert .NoError (t , err )
244
+ require .NoError (t , err )
214
245
}
215
246
216
247
func dumpData (env * lmdb.Env , withHeader bool ) (map [string ]string , error ) {
217
248
data := make (map [string ]string )
218
249
err := env .View (func (txn * lmdb.Txn ) error {
219
- dbi , err := txn .OpenDBI (testDBIName , lmdb . Create )
250
+ dbi , err := txn .OpenDBI (testDBIName , 0 )
220
251
if err != nil {
221
252
return err
222
253
}
0 commit comments