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