@@ -94,3 +94,118 @@ func TestFailpoint_mLockFail_When_remap(t *testing.T) {
94
94
95
95
require .NoError (t , err )
96
96
}
97
+
98
+ // TestIssue72 reproduces issue 72.
99
+ //
100
+ // When bbolt is processing a `Put` invocation, the key might be concurrently
101
+ // updated by the application which calls the `Put` API (although it shouldn't).
102
+ // It might lead to a situation that bbolt use an old key to find a proper
103
+ // position to insert the key/value pair, but actually inserts a new key.
104
+ // Eventually it might break the rule that all keys should be sorted. In a
105
+ // worse case, it might cause page elements to point to already freed pages.
106
+ //
107
+ // REF: https://github.com/etcd-io/bbolt/issues/72
108
+ func TestIssue72 (t * testing.T ) {
109
+ db := btesting .MustCreateDBWithOption (t , & bolt.Options {PageSize : 4096 })
110
+
111
+ bucketName := []byte (t .Name ())
112
+ err := db .Update (func (tx * bolt.Tx ) error {
113
+ _ , txerr := tx .CreateBucket (bucketName )
114
+ return txerr
115
+ })
116
+ require .NoError (t , err )
117
+
118
+ // The layout is like:
119
+ //
120
+ // +--+--+--+
121
+ // +------+1 |3 |10+---+
122
+ // | +-++--+--+ |
123
+ // | | |
124
+ // | | |
125
+ // +v-+--+ +v-+--+ +-v+--+--+
126
+ // |1 |2 | |3 |4 | |10|11|12|
127
+ // +--+--+ +--+--+ +--+--+--+
128
+ //
129
+ err = db .Update (func (tx * bolt.Tx ) error {
130
+ bk := tx .Bucket (bucketName )
131
+
132
+ for _ , id := range []int {1 , 2 , 3 , 4 , 10 , 11 , 12 } {
133
+ if txerr := bk .Put (idToBytes (id ), make ([]byte , 1000 )); txerr != nil {
134
+ return txerr
135
+ }
136
+ }
137
+ return nil
138
+ })
139
+ require .NoError (t , err )
140
+
141
+ require .NoError (t , gofail .Enable ("beforeBucketPut" , `sleep(5000)` ))
142
+
143
+ // +--+--+--+
144
+ // +------+1 |3 |1 +---+
145
+ // | +-++--+--+ |
146
+ // | | |
147
+ // | | |
148
+ // +v-+--+ +v-+--+ +-v+--+--+--+
149
+ // |1 |2 | |3 |4 | |1 |10|11|12|
150
+ // +--+--+ +--+--+ +--+--+--+--+
151
+ //
152
+ key := idToBytes (13 )
153
+ updatedKey := idToBytes (1 )
154
+ err = db .Update (func (tx * bolt.Tx ) error {
155
+ bk := tx .Bucket (bucketName )
156
+
157
+ go func () {
158
+ time .Sleep (3 * time .Second )
159
+ copy (key , updatedKey )
160
+ }()
161
+ return bk .Put (key , make ([]byte , 100 ))
162
+ })
163
+ require .NoError (t , err )
164
+
165
+ require .NoError (t , gofail .Disable ("beforeBucketPut" ))
166
+
167
+ // bbolt inserts 100 into last branch page. Since there are two `1`
168
+ // keys in branch, spill operation will update first `1` pointer and
169
+ // then last one won't be updated and continues to point to freed page.
170
+ //
171
+ //
172
+ // +--+--+--+
173
+ // +---------------+1 |3 |1 +---------+
174
+ // | +--++-+--+ |
175
+ // | | |
176
+ // | | |
177
+ // | +--+--+ +v-+--+ +-----v-----+
178
+ // | |1 |2 | |3 |4 | |freed page |
179
+ // | +--+--+ +--+--+ +-----------+
180
+ // |
181
+ // +v-+--+--+--+---+
182
+ // |1 |10|11|12|100|
183
+ // +--+--+--+--+---+
184
+ err = db .Update (func (tx * bolt.Tx ) error {
185
+ return tx .Bucket (bucketName ).Put (idToBytes (100 ), make ([]byte , 100 ))
186
+ })
187
+ require .NoError (t , err )
188
+
189
+ defer func () {
190
+ if r := recover (); r != nil {
191
+ t .Logf ("panic info:\n %v" , r )
192
+ }
193
+ }()
194
+
195
+ // Add more keys to ensure branch node to spill.
196
+ err = db .Update (func (tx * bolt.Tx ) error {
197
+ bk := tx .Bucket (bucketName )
198
+
199
+ for _ , id := range []int {101 , 102 , 103 , 104 , 105 } {
200
+ if txerr := bk .Put (idToBytes (id ), make ([]byte , 1000 )); txerr != nil {
201
+ return txerr
202
+ }
203
+ }
204
+ return nil
205
+ })
206
+ require .NoError (t , err )
207
+ }
208
+
209
+ func idToBytes (id int ) []byte {
210
+ return []byte (fmt .Sprintf ("%010d" , id ))
211
+ }
0 commit comments