Skip to content

Commit 571ead7

Browse files
sunhao.infsunhao.inf
authored andcommitted
Remove recursion of redis command parser
1 parent ef82950 commit 571ead7

2 files changed

Lines changed: 136 additions & 115 deletions

File tree

src/brpc/redis_command.cpp

Lines changed: 117 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -377,136 +377,138 @@ size_t RedisCommandParser::ParsedArgsSize() {
377377
ParseError RedisCommandParser::Consume(butil::IOBuf& buf,
378378
std::vector<butil::StringPiece>* args,
379379
butil::Arena* arena) {
380-
const auto pfc = static_cast<const char *>(buf.fetch1());
381-
if (pfc == NULL) {
382-
return PARSE_ERROR_NOT_ENOUGH_DATA;
383-
}
384-
// '*' stands for array "*<size>\r\n<sub-reply1><sub-reply2>..."
385-
if (!_parsing_array && *pfc != '*') {
386-
if (!std::isalpha(static_cast<unsigned char>(*pfc))) {
387-
return PARSE_ERROR_TRY_OTHERS;
380+
do {
381+
const auto pfc = static_cast<const char *>(buf.fetch1());
382+
if (pfc == NULL) {
383+
return PARSE_ERROR_NOT_ENOUGH_DATA;
388384
}
389-
const size_t buf_size = buf.size();
390-
const auto copy_str = static_cast<char *>(arena->allocate(buf_size + 1));
391-
buf.copy_to(copy_str, buf_size);
392-
if (*copy_str == ' ') {
385+
// '*' stands for array "*<size>\r\n<sub-reply1><sub-reply2>..."
386+
if (!_parsing_array && *pfc != '*') {
387+
if (!std::isalpha(static_cast<unsigned char>(*pfc))) {
388+
return PARSE_ERROR_TRY_OTHERS;
389+
}
390+
const size_t buf_size = buf.size();
391+
const auto copy_str = static_cast<char *>(arena->allocate(buf_size + 1));
392+
buf.copy_to(copy_str, buf_size);
393+
if (*copy_str == ' ') {
394+
return PARSE_ERROR_ABSOLUTELY_WRONG;
395+
}
396+
copy_str[buf_size] = '\0';
397+
const size_t crlf_pos = butil::StringPiece(copy_str, buf_size).find("\r\n");
398+
if (crlf_pos == butil::StringPiece::npos) { // not enough data
399+
return PARSE_ERROR_NOT_ENOUGH_DATA;
400+
}
401+
args->clear();
402+
size_t offset = 0;
403+
while (offset < crlf_pos && copy_str[offset] != ' ') {
404+
++offset;
405+
}
406+
const auto first_arg = static_cast<char*>(arena->allocate(offset));
407+
memcpy(first_arg, copy_str, offset);
408+
for (size_t i = 0; i < offset; ++i) {
409+
first_arg[i] = tolower(first_arg[i]);
410+
}
411+
args->push_back(butil::StringPiece(first_arg, offset));
412+
if (offset == crlf_pos) {
413+
// only one argument, directly return
414+
buf.pop_front(crlf_pos + 2);
415+
return PARSE_OK;
416+
}
417+
size_t arg_start_pos = ++offset;
418+
419+
for (; offset < crlf_pos; ++offset) {
420+
if (copy_str[offset] != ' ') {
421+
continue;
422+
}
423+
const auto arg_length = offset - arg_start_pos;
424+
const auto arg = static_cast<char *>(arena->allocate(arg_length));
425+
memcpy(arg, copy_str + arg_start_pos, arg_length);
426+
args->push_back(butil::StringPiece(arg, arg_length));
427+
arg_start_pos = ++offset;
428+
}
429+
430+
if (arg_start_pos < crlf_pos) {
431+
// process the last argument
432+
const auto arg_length = crlf_pos - arg_start_pos;
433+
const auto arg = static_cast<char *>(arena->allocate(arg_length));
434+
memcpy(arg, copy_str + arg_start_pos, arg_length);
435+
args->push_back(butil::StringPiece(arg, arg_length));
436+
}
437+
438+
buf.pop_front(crlf_pos + 2);
439+
return PARSE_OK;
440+
}
441+
// '$' stands for bulk string "$<length>\r\n<string>\r\n"
442+
if (_parsing_array && *pfc != '$') {
393443
return PARSE_ERROR_ABSOLUTELY_WRONG;
394444
}
395-
copy_str[buf_size] = '\0';
396-
const size_t crlf_pos = butil::StringPiece(copy_str, buf_size).find("\r\n");
445+
char intbuf[32]; // enough for fc + 64-bit decimal + \r\n
446+
const size_t ncopied = buf.copy_to(intbuf, sizeof(intbuf) - 1);
447+
intbuf[ncopied] = '\0';
448+
const size_t crlf_pos = butil::StringPiece(intbuf, ncopied).find("\r\n");
397449
if (crlf_pos == butil::StringPiece::npos) { // not enough data
398450
return PARSE_ERROR_NOT_ENOUGH_DATA;
399451
}
400-
args->clear();
401-
size_t offset = 0;
402-
while (offset < crlf_pos && copy_str[offset] != ' ') {
403-
++offset;
404-
}
405-
const auto first_arg = static_cast<char*>(arena->allocate(offset));
406-
memcpy(first_arg, copy_str, offset);
407-
for (size_t i = 0; i < offset; ++i) {
408-
first_arg[i] = tolower(first_arg[i]);
452+
char* endptr = NULL;
453+
int64_t value = strtoll(intbuf + 1/*skip fc*/, &endptr, 10);
454+
if (endptr != intbuf + crlf_pos) {
455+
LOG(ERROR) << '`' << intbuf + 1 << "' is not a valid 64-bit decimal";
456+
return PARSE_ERROR_ABSOLUTELY_WRONG;
409457
}
410-
args->push_back(butil::StringPiece(first_arg, offset));
411-
if (offset == crlf_pos) {
412-
// only one argument, directly return
413-
buf.pop_front(crlf_pos + 2);
414-
return PARSE_OK;
458+
if (value < 0) {
459+
LOG(ERROR) << "Invalid len=" << value << " in redis command";
460+
return PARSE_ERROR_ABSOLUTELY_WRONG;
415461
}
416-
size_t arg_start_pos = ++offset;
417-
418-
for (; offset < crlf_pos; ++offset) {
419-
if (copy_str[offset] != ' ') {
420-
continue;
462+
if (!_parsing_array) {
463+
if (value > (int64_t)(FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece))) {
464+
LOG(ERROR) << "command array size exceeds limit! max="
465+
<< (FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece))
466+
<< ", actually=" << value;
467+
return PARSE_ERROR_ABSOLUTELY_WRONG;
421468
}
422-
const auto arg_length = offset - arg_start_pos;
423-
const auto arg = static_cast<char *>(arena->allocate(arg_length));
424-
memcpy(arg, copy_str + arg_start_pos, arg_length);
425-
args->push_back(butil::StringPiece(arg, arg_length));
426-
arg_start_pos = ++offset;
469+
buf.pop_front(crlf_pos + 2/*CRLF*/);
470+
_parsing_array = true;
471+
_length = value;
472+
_index = 0;
473+
_args.resize(value);
474+
continue;
427475
}
428-
429-
if (arg_start_pos < crlf_pos) {
430-
// process the last argument
431-
const auto arg_length = crlf_pos - arg_start_pos;
432-
const auto arg = static_cast<char *>(arena->allocate(arg_length));
433-
memcpy(arg, copy_str + arg_start_pos, arg_length);
434-
args->push_back(butil::StringPiece(arg, arg_length));
476+
CHECK(_index < _length) << "a complete command has been parsed. "
477+
"impl of RedisCommandParser::Parse is buggy";
478+
const int64_t len = value; // `value' is length of the string
479+
if (len < 0) {
480+
LOG(ERROR) << "string in command is nil!";
481+
return PARSE_ERROR_ABSOLUTELY_WRONG;
435482
}
436-
437-
buf.pop_front(crlf_pos + 2);
438-
return PARSE_OK;
439-
}
440-
// '$' stands for bulk string "$<length>\r\n<string>\r\n"
441-
if (_parsing_array && *pfc != '$') {
442-
return PARSE_ERROR_ABSOLUTELY_WRONG;
443-
}
444-
char intbuf[32]; // enough for fc + 64-bit decimal + \r\n
445-
const size_t ncopied = buf.copy_to(intbuf, sizeof(intbuf) - 1);
446-
intbuf[ncopied] = '\0';
447-
const size_t crlf_pos = butil::StringPiece(intbuf, ncopied).find("\r\n");
448-
if (crlf_pos == butil::StringPiece::npos) { // not enough data
449-
return PARSE_ERROR_NOT_ENOUGH_DATA;
450-
}
451-
char* endptr = NULL;
452-
int64_t value = strtoll(intbuf + 1/*skip fc*/, &endptr, 10);
453-
if (endptr != intbuf + crlf_pos) {
454-
LOG(ERROR) << '`' << intbuf + 1 << "' is not a valid 64-bit decimal";
455-
return PARSE_ERROR_ABSOLUTELY_WRONG;
456-
}
457-
if (value < 0) {
458-
LOG(ERROR) << "Invalid len=" << value << " in redis command";
459-
return PARSE_ERROR_ABSOLUTELY_WRONG;
460-
}
461-
if (!_parsing_array) {
462-
if (value > (int64_t)(FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece))) {
463-
LOG(ERROR) << "command array size exceeds limit! max="
464-
<< (FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece))
465-
<< ", actually=" << value;
483+
if (len > FLAGS_redis_max_allocation_size) {
484+
LOG(ERROR) << "command string exceeds max allocation size! max="
485+
<< FLAGS_redis_max_allocation_size << ", actually=" << len;
466486
return PARSE_ERROR_ABSOLUTELY_WRONG;
467487
}
488+
if (buf.size() < crlf_pos + 2 + (size_t)len + 2/*CRLF*/) {
489+
return PARSE_ERROR_NOT_ENOUGH_DATA;
490+
}
468491
buf.pop_front(crlf_pos + 2/*CRLF*/);
469-
_parsing_array = true;
470-
_length = value;
471-
_index = 0;
472-
_args.resize(value);
473-
return Consume(buf, args, arena);
474-
}
475-
CHECK(_index < _length) << "a complete command has been parsed. "
476-
"impl of RedisCommandParser::Parse is buggy";
477-
const int64_t len = value; // `value' is length of the string
478-
if (len < 0) {
479-
LOG(ERROR) << "string in command is nil!";
480-
return PARSE_ERROR_ABSOLUTELY_WRONG;
481-
}
482-
if (len > FLAGS_redis_max_allocation_size) {
483-
LOG(ERROR) << "command string exceeds max allocation size! max="
484-
<< FLAGS_redis_max_allocation_size << ", actually=" << len;
485-
return PARSE_ERROR_ABSOLUTELY_WRONG;
486-
}
487-
if (buf.size() < crlf_pos + 2 + (size_t)len + 2/*CRLF*/) {
488-
return PARSE_ERROR_NOT_ENOUGH_DATA;
489-
}
490-
buf.pop_front(crlf_pos + 2/*CRLF*/);
491-
char* d = (char*)arena->allocate((len/8 + 1) * 8);
492-
buf.cutn(d, len);
493-
d[len] = '\0';
494-
_args[_index].set(d, len);
495-
if (_index == 0) {
496-
// convert it to lowercase when it is command name
497-
for (int i = 0; i < len; ++i) {
498-
d[i] = ::tolower(d[i]);
492+
char* d = (char*)arena->allocate((len/8 + 1) * 8);
493+
buf.cutn(d, len);
494+
d[len] = '\0';
495+
_args[_index].set(d, len);
496+
if (_index == 0) {
497+
// convert it to lowercase when it is command name
498+
for (int i = 0; i < len; ++i) {
499+
d[i] = ::tolower(d[i]);
500+
}
499501
}
500-
}
501-
char crlf[2];
502-
buf.cutn(crlf, sizeof(crlf));
503-
if (crlf[0] != '\r' || crlf[1] != '\n') {
504-
LOG(ERROR) << "string in command is not ended with CRLF";
505-
return PARSE_ERROR_ABSOLUTELY_WRONG;
506-
}
507-
if (++_index < _length) {
508-
return Consume(buf, args, arena);
509-
}
502+
char crlf[2];
503+
buf.cutn(crlf, sizeof(crlf));
504+
if (crlf[0] != '\r' || crlf[1] != '\n') {
505+
LOG(ERROR) << "string in command is not ended with CRLF";
506+
return PARSE_ERROR_ABSOLUTELY_WRONG;
507+
}
508+
if (++_index == _length) {
509+
break;
510+
}
511+
} while (true);
510512
args->swap(_args);
511513
Reset();
512514
return PARSE_OK;

test/brpc_redis_unittest.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,25 @@ TEST_F(RedisTest, memory_allocation_limits) {
14231423
brpc::ParseError err = parser.Consume(buf, &args, &arena);
14241424
ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, err);
14251425
}
1426+
1427+
{
1428+
// Test large command array work
1429+
int32_t original_limit_tmp = brpc::FLAGS_redis_max_allocation_size;
1430+
brpc::FLAGS_redis_max_allocation_size = 1024 * 1024;
1431+
brpc::RedisCommandParser parser;
1432+
butil::IOBuf buf;
1433+
int32_t large_array_size = brpc::FLAGS_redis_max_allocation_size / sizeof(butil::StringPiece);
1434+
std::string large_array_cmd = "*" + std::to_string(large_array_size) + "\r\n";
1435+
for(int i = 0; i < large_array_size; i++){
1436+
large_array_cmd.append("$1\r\n1\r\n");
1437+
}
1438+
buf.append(large_array_cmd);
1439+
1440+
std::vector<butil::StringPiece> args;
1441+
brpc::ParseError err = parser.Consume(buf, &args, &arena);
1442+
ASSERT_EQ(brpc::PARSE_OK, err);
1443+
brpc::FLAGS_redis_max_allocation_size = original_limit_tmp;
1444+
}
14261445

14271446
// Test valid cases within limits
14281447
{

0 commit comments

Comments
 (0)