Skip to content

Commit f4395e1

Browse files
committed
MB-33854: Merge remote-tracking branch 'couchbase/vulcan' into alice
Forward-merge of fix for MB-33852 (backport id of MB-33813). * couchbase/vulcan: MB-33852: Fix NOT_STORED being returned from arithmetic op Change-Id: Id8bc279d056b7b9f756ad9af076a23d60bacc8cb
2 parents 7f9f80f + 454186c commit f4395e1

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

daemon/protocol/mcbp/arithmetic_context.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ ENGINE_ERROR_CODE ArithmeticCommandContext::storeNewItem() {
113113
} else if (ret == ENGINE_KEY_EEXISTS && cas == 0) {
114114
state = State::Reset;
115115
ret = ENGINE_SUCCESS;
116+
} else if(ret == ENGINE_NOT_STORED) {
117+
// hit race condition, item with our key was created between our call
118+
// to bucket_store() and when we checked to see if it existed, by
119+
// calling bucket_get() so just re-try by resetting our state to the
120+
// start again.
121+
state = State::Reset;
122+
ret = ENGINE_SUCCESS;
116123
}
117124

118125
return ret;

tests/testapp/testapp_arithmetic.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,82 @@ TEST_P(ArithmeticTest, TestIllegalDatatype) {
221221
}
222222
}
223223

224+
/*
225+
* Unit test to test the fix for MB-33813
226+
* This test checks that we do not return NOT_STORED back to the client
227+
* if bucket_store returns it to ArithmeticCommandContext::storeNewItem().
228+
* Instead ArithmeticCommandContext::storeNewItem() should reset the
229+
* ArithmeticCommandContext state machine and try again.
230+
*
231+
* To check that ENGINE_NOT_STORED/NOT_STORED is not returned we use
232+
* eWouldBlockEngine to return ENGINE_NOT_STORED on the 3rd engine request using
233+
* the binary sequence 0b100.
234+
*
235+
* This works as the process we expect to see by the ArithmeticCommandContext
236+
* state machine is as follows:
237+
* start: GetItem
238+
* calls bucket_get() should return ENGINE_SUCCESS
239+
* -> CreateNewItem
240+
* calls bucket_allocate_ex should return ENGINE_SUCCESS
241+
* -> StoreNewItem
242+
* calls bucket_store return ENGINE_NOT_STORED
243+
* ENGINE_NOT_STORED returned so reset and try again.
244+
* -> Reset
245+
* -> GetItem
246+
* .... Do the work to increment the value again
247+
* -> Done
248+
*/
249+
TEST_P(ArithmeticTest, MB33813) {
250+
auto& connection = getConnection();
251+
std::string key(name + "_inc");
252+
253+
// Make the 3rd request send to the engine return ENGINE_NOT_STORED
254+
// In this case this will be the store that happens as a result of
255+
// a call to ArithmeticCommandContext::storeNewItem()
256+
// We also set all the higher bits after the 6th. So that if the sequence
257+
// of engine accesses change this test will fail.
258+
connection.configureEwouldBlockEngine(
259+
EWBEngineMode::Sequence,
260+
ENGINE_NOT_STORED,
261+
/* 0b11111111111111111111111111000100 */ 0xffffffc4);
262+
263+
EXPECT_EQ(1, connection.increment(key, 0, 1));
264+
265+
connection.disableEwouldBlockEngine();
266+
267+
Document doc = connection.get(key, 0);
268+
EXPECT_EQ("1", doc.value);
269+
270+
EXPECT_EQ(2, connection.increment(key, 1));
271+
272+
doc = connection.get(key, 0);
273+
EXPECT_EQ("2", doc.value);
274+
275+
// Sanity check do the same thing but with a decrement
276+
key = name + "_dec";
277+
// Make the 3rd request send to the engine return ENGINE_NOT_STORED
278+
// In this case this will be the store that happens as a result of
279+
// a call to ArithmeticCommandContext::storeNewItem()
280+
// We also set all the higher bits after the 6th. So that if the sequence
281+
// of engine accesses change this test will fail.
282+
connection.configureEwouldBlockEngine(
283+
EWBEngineMode::Sequence,
284+
ENGINE_NOT_STORED,
285+
/* 0b11111111111111111111111111000100 */ 0xffffffc4);
286+
287+
EXPECT_EQ(2, connection.decrement(key, 0, 2));
288+
289+
connection.disableEwouldBlockEngine();
290+
291+
doc = connection.get(key, 0);
292+
EXPECT_EQ("2", doc.value);
293+
294+
EXPECT_EQ(1, connection.decrement(key, 1));
295+
296+
doc = connection.get(key, 0);
297+
EXPECT_EQ("1", doc.value);
298+
}
299+
224300
static void test_stored_doc(MemcachedConnection& conn,
225301
const std::string& key,
226302
const std::string& content,

0 commit comments

Comments
 (0)