@@ -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+
224300static void test_stored_doc (MemcachedConnection& conn,
225301 const std::string& key,
226302 const std::string& content,
0 commit comments