Skip to content

Commit 3a6a0d1

Browse files
jipanyangqiluo-msft
authored andcommitted
Fix incorret table data issue with consecutive producerState del/set … (sonic-net#240)
* Fix incorret table data issue with consecutive producerState del/set operations * Use _DEL_SET suffix for set of deleted keys in producerStateTable/consumerStateTable
1 parent 35b003d commit 3a6a0d1

5 files changed

+148
-7
lines changed

common/consumer_state_table_pops.lua

+7-3
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ local keys = redis.call('SPOP', KEYS[1], ARGV[1])
66
local n = table.getn(keys)
77
for i = 1, n do
88
local key = keys[i]
9+
-- Check if there was request to delete the key, clear it in table first
10+
local num = redis.call('SREM', KEYS[3], key)
11+
if num == 1 then
12+
redis.call('DEL', tablename..key)
13+
end
14+
-- Push the new set of field/value for this key in table
915
local fieldvalues = redis.call('HGETALL', stateprefix..tablename..key)
1016
table.insert(ret, {key, fieldvalues})
1117
for i = 1, #fieldvalues, 2 do
1218
redis.call('HSET', tablename..key, fieldvalues[i], fieldvalues[i + 1])
1319
end
20+
-- Clean up the key in temporary state table
1421
redis.call('DEL', stateprefix..tablename..key)
15-
if #fieldvalues == 0 then
16-
redis.call('DEL', tablename..key)
17-
end
1822
end
1923
return ret

common/consumerstatetable.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ void ConsumerStateTable::pops(std::deque<KeyOpFieldsValuesTuple> &vkco, const st
3838

3939
RedisCommand command;
4040
command.format(
41-
"EVALSHA %s 2 %s %s: %d %s",
41+
"EVALSHA %s 3 %s %s: %s %d %s",
4242
sha.c_str(),
4343
getKeySetName().c_str(),
4444
getTableName().c_str(),
45+
getDelKeySetName().c_str(),
4546
POP_BATCH_SIZE,
4647
getStateHashPrefix().c_str());
4748

common/producerstatetable.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ ProducerStateTable::ProducerStateTable(RedisPipeline *pipeline, const string &ta
3636

3737
string luaDel =
3838
"redis.call('SADD', KEYS[2], ARGV[2])\n"
39+
"redis.call('SADD', KEYS[4], ARGV[2])\n"
3940
"redis.call('DEL', KEYS[3])\n"
4041
"redis.call('PUBLISH', KEYS[1], ARGV[1])\n";
4142
m_shaDel = m_pipe->loadRedisScript(luaDel);
4243

4344
string luaClear =
4445
"redis.call('DEL', KEYS[1])\n"
45-
"redis.call('DEL', KEYS[2])\n";
46+
"redis.call('DEL', KEYS[2])\n"
47+
"redis.call('DEL', KEYS[3])\n";
4648
m_shaClear = m_pipe->loadRedisScript(luaClear);
4749
}
4850

@@ -100,13 +102,15 @@ void ProducerStateTable::del(const string &key, const string &op /*= DEL_COMMAND
100102
vector<string> args;
101103
args.emplace_back("EVALSHA");
102104
args.emplace_back(m_shaDel);
103-
args.emplace_back("3");
105+
args.emplace_back("4");
104106
args.emplace_back(getChannelName());
105107
args.emplace_back(getKeySetName());
106108
args.emplace_back(getStateHashPrefix() + getKeyName(key));
109+
args.emplace_back(getDelKeySetName());
107110
args.emplace_back("G");
108111
args.emplace_back(key);
109112
args.emplace_back("''");
113+
args.emplace_back("''");
110114

111115
// Transform data structure
112116
vector<const char *> args1;
@@ -145,9 +149,10 @@ void ProducerStateTable::clear()
145149
vector<string> args;
146150
args.emplace_back("EVALSHA");
147151
args.emplace_back(m_shaClear);
148-
args.emplace_back("2");
152+
args.emplace_back("3");
149153
args.emplace_back(getKeySetName());
150154
args.emplace_back(getStateHashPrefix() + getTableName());
155+
args.emplace_back(getDelKeySetName());
151156

152157
// Transform data structure
153158
vector<const char *> args1;

common/table.h

+3
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,16 @@ class TableName_KeyValueOpQueues {
194194
class TableName_KeySet {
195195
private:
196196
std::string m_key;
197+
std::string m_delkey;
197198
public:
198199
TableName_KeySet(const std::string &tableName)
199200
: m_key(tableName + "_KEY_SET")
201+
, m_delkey(tableName + "_DEL_SET")
200202
{
201203
}
202204

203205
std::string getKeySetName() const { return m_key; }
206+
std::string getDelKeySetName() const { return m_delkey; }
204207
std::string getStateHashPrefix() const { return "_"; }
205208
};
206209

tests/redis_state_ut.cpp

+128
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,134 @@ TEST(ConsumerStateTable, set_del_set)
374374
EXPECT_EQ(qlen, 0U);
375375
}
376376

377+
TEST(ConsumerStateTable, set_pop_del_set_pop_get)
378+
{
379+
clearDB();
380+
381+
/* Prepare producer */
382+
int index = 0;
383+
string tableName = "UT_REDIS_THREAD_" + to_string(index);
384+
DBConnector db(TEST_DB, "localhost", 6379, 0);
385+
ProducerStateTable p(&db, tableName);
386+
string key = "TheKey";
387+
int maxNumOfFields = 2;
388+
389+
/* First set operation */
390+
{
391+
vector<FieldValueTuple> fields;
392+
for (int j = 0; j < maxNumOfFields; j++)
393+
{
394+
FieldValueTuple t(field(j), value(j));
395+
fields.push_back(t);
396+
}
397+
p.set(key, fields);
398+
}
399+
400+
/* Prepare consumer */
401+
ConsumerStateTable c(&db, tableName);
402+
Select cs;
403+
Selectable *selectcs;
404+
cs.addSelectable(&c);
405+
406+
/* First pop operation */
407+
{
408+
int ret = cs.select(&selectcs);
409+
EXPECT_EQ(ret, Select::OBJECT);
410+
KeyOpFieldsValuesTuple kco;
411+
c.pop(kco);
412+
EXPECT_EQ(kfvKey(kco), key);
413+
EXPECT_EQ(kfvOp(kco), "SET");
414+
415+
auto fvs = kfvFieldsValues(kco);
416+
EXPECT_EQ(fvs.size(), (unsigned int)maxNumOfFields);
417+
418+
map<string, string> mm;
419+
for (auto fv: fvs)
420+
{
421+
mm[fvField(fv)] = fvValue(fv);
422+
}
423+
424+
for (int j = 0; j < maxNumOfFields; j ++)
425+
{
426+
EXPECT_EQ(mm[field(j)], value(j));
427+
}
428+
}
429+
430+
/* Del operation and second set operation will be merged */
431+
432+
/* Del operation */
433+
p.del(key);
434+
435+
/* Second set operation */
436+
{
437+
vector<FieldValueTuple> fields;
438+
for (int j = 0; j < maxNumOfFields * 2; j += 2)
439+
{
440+
FieldValueTuple t(field(j), value(j));
441+
fields.push_back(t);
442+
}
443+
p.set(key, fields);
444+
}
445+
446+
/* Second pop operation */
447+
{
448+
int ret = cs.select(&selectcs);
449+
EXPECT_EQ(ret, Select::OBJECT);
450+
KeyOpFieldsValuesTuple kco;
451+
c.pop(kco);
452+
EXPECT_EQ(kfvKey(kco), key);
453+
EXPECT_EQ(kfvOp(kco), "SET");
454+
455+
auto fvs = kfvFieldsValues(kco);
456+
457+
/* size of fvs should be maxNumOfFields, no "field 1" left from first set*/
458+
EXPECT_EQ(fvs.size(), (unsigned int)maxNumOfFields);
459+
460+
map<string, string> mm;
461+
for (auto fv: fvs)
462+
{
463+
mm[fvField(fv)] = fvValue(fv);
464+
}
465+
466+
for (int j = 0; j < maxNumOfFields * 2; j += 2)
467+
{
468+
EXPECT_EQ(mm[field(j)], value(j));
469+
}
470+
}
471+
472+
/* Get data directly from table in redis DB*/
473+
Table t(&db, tableName);
474+
vector<FieldValueTuple> values;
475+
t.get(key, values);
476+
/* size of values should be maxNumOfFields, no "field 1" left from first set */
477+
EXPECT_EQ(values.size(), (unsigned int)maxNumOfFields);
478+
479+
/*
480+
* Third pop operation, consumer received two consectuive signals.
481+
* data depleted upon first one
482+
*/
483+
{
484+
int ret = cs.select(&selectcs, 1000);
485+
EXPECT_EQ(ret, Select::OBJECT);
486+
KeyOpFieldsValuesTuple kco;
487+
c.pop(kco);
488+
EXPECT_EQ(kfvKey(kco), "");
489+
}
490+
491+
/* Third select operation */
492+
{
493+
int ret = cs.select(&selectcs, 1000);
494+
EXPECT_EQ(ret, Select::TIMEOUT);
495+
}
496+
497+
/* State Queue should be empty */
498+
RedisCommand keys;
499+
keys.format("KEYS %s*", (c.getStateHashPrefix() + tableName).c_str());
500+
RedisReply r(&db, keys, REDIS_REPLY_ARRAY);
501+
auto qlen = r.getContext()->elements;
502+
EXPECT_EQ(qlen, 0U);
503+
}
504+
377505
TEST(ConsumerStateTable, singlethread)
378506
{
379507
clearDB();

0 commit comments

Comments
 (0)