Skip to content

Commit cb3e3e7

Browse files
committed
Merge remote-tracking branch 'couchbase/alice'
* couchbase/alice: MB-33852: Fix NOT_STORED being returned from arithmetic op Change-Id: Idf8748cb826961851dd045c7da7d8600d0531429
2 parents 38d49b1 + f4395e1 commit cb3e3e7

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

daemon/protocol/mcbp/arithmetic_context.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ ENGINE_ERROR_CODE ArithmeticCommandContext::storeNewItem() {
122122
} else if (ret == ENGINE_KEY_EEXISTS && cas == 0) {
123123
state = State::Reset;
124124
ret = ENGINE_SUCCESS;
125+
} else if(ret == ENGINE_NOT_STORED) {
126+
// hit race condition, item with our key was created between our call
127+
// to bucket_store() and when we checked to see if it existed, by
128+
// calling bucket_get() so just re-try by resetting our state to the
129+
// start again.
130+
state = State::Reset;
131+
ret = ENGINE_SUCCESS;
125132
}
126133

127134
return ret;

protocol/connection/client_connection.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,15 @@ class MemcachedConnection {
529529
uint32_t value = 0,
530530
const std::string& key = "");
531531

532+
/**
533+
* Disable the ewouldblock engine entirely.
534+
*/
535+
void disableEwouldBlockEngine() {
536+
// We disable the engine by telling it to inject the given error
537+
// the next 0 times
538+
configureEwouldBlockEngine(EWBEngineMode::Next_N, ENGINE_SUCCESS, 0);
539+
}
540+
532541
/**
533542
* Identify ourself to the server.
534543
*

tests/testapp/testapp_arithmetic.cc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,83 @@ TEST_P(ArithmeticTest, TestIllegalDatatype) {
224224
}
225225
}
226226

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

0 commit comments

Comments
 (0)