-
Notifications
You must be signed in to change notification settings - Fork 30.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved memory management code in ParseSoaReply() #23628
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a comment.
src/cares_wrap.cc
Outdated
@@ -1064,30 +1064,29 @@ int ParseSoaReply(Environment* env, | |||
/* Can't use ares_parse_soa_reply() here which can only parse single record */ | |||
unsigned int ancount = cares_get_16bit(buf + 6); | |||
unsigned char* ptr = buf + NS_HFIXEDSZ; | |||
char* name; | |||
char* rr_name; | |||
int rr_type, rr_len; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this line be here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does seem to be added by accident?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. It looks related to merge conflicts or something (I'm pretty sure I removed that line recently).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cjihrig Yes you are right, that line is not in the mainline. Is it okay if I update the PR with removal of that line (rr_len and rr_type)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need a better abstration
src/cares_wrap.cc
Outdated
@@ -1064,30 +1064,28 @@ int ParseSoaReply(Environment* env, | |||
/* Can't use ares_parse_soa_reply() here which can only parse single record */ | |||
unsigned int ancount = cares_get_16bit(buf + 6); | |||
unsigned char* ptr = buf + NS_HFIXEDSZ; | |||
char* name; | |||
char* rr_name; | |||
MallocedBuffer<char> name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO you are using the wrong abstraction.
See the following guidelines:
ES.1: Prefer the standard library to other libraries and to “handcrafted code”
ES.2: Prefer suitable abstractions to direct use of language features
I think the better utility here is unique_ptr
, since you don't track the size in conjunction with the allocated memory.
Also according to the docs, the buffer need to be freed with ares_free_string
so:
MallocedBuffer<char> name; | |
struct AresDeleter { | |
void operator()(char* ptr) const { ares_free_string(ptr); } noexcept | |
}; | |
using ares_unique_ptr = std::unique_ptr<char, AresDeleter>; | |
char* name_tmp; | |
long temp_len; // NOLINT(runtime/int) | |
int status = ares_expand_name(ptr, buf, len, &name_tmp, &temp_len); | |
ares_unique_ptr name(name_tmp); | |
... | |
// In the smallest scope. | |
char* rr_name_tmp; | |
// Don't reuse status | |
int status2 = ares_expand_name(ptr, buf, len, &rr_name_tmp, &temp_len); | |
ares_unique_ptr rr_name(rr_name_tmp); |
Notice rr_name
should be in the smallest necessary scope:
ES.5: Keep scopes small
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there is no further transfer of ownership, I think that if you use this pattern, i.e.:
long temp_len; // NOLINT(runtime/int)
char* name_tmp;
int status = ares_expand_name(ptr, buf, len, &name_tmp, &temp_len);
const ares_unique_ptr name(name_tmp);
You can use a const ares_unique_ptr
getting more explicit code, and more compiler validation.
From CppReference:
Only non-const unique_ptr can transfer the ownership of the managed object to another unique_ptr. If an object's lifetime is managed by a const std::unique_ptr, it is limited to the scope in which the pointer was created.
src/cares_wrap.cc
Outdated
return ARES_EBADRESP; | ||
} | ||
ptr += temp_len + NS_QFIXEDSZ; | ||
|
||
for (unsigned int i = 0; i < ancount; i++) { | ||
status = ares_expand_name(ptr, buf, len, &rr_name, &temp_len); | ||
status = ares_expand_name(ptr, buf, len, &rr_name.data, &temp_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ES.26: Don’t use a variable for two unrelated purposes
src/cares_wrap.cc
Outdated
@@ -1098,76 +1096,67 @@ int ParseSoaReply(Environment* env, | |||
|
|||
/* only need SOA */ | |||
if (rr_type == ns_t_soa) { | |||
ares_soa_reply soa; | |||
MallocedBuffer<char> nsname; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
src/cares_wrap.cc
Outdated
|
||
status = ares_expand_name(ptr, buf, len, &soa.nsname, &temp_len); | ||
status = ares_expand_name(ptr, buf, len, &nsname.data, &temp_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"
src/cares_wrap.cc
Outdated
break; | ||
} | ||
ptr += temp_len; | ||
|
||
status = ares_expand_name(ptr, buf, len, &soa.hostmaster, &temp_len); | ||
status = ares_expand_name(ptr, buf, len, &hostmaster.data, &temp_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"
Hello @uttampawar and thank you for the contribution. |
@refack Thanks for the feedback. I did read up on C++ core guidelines as you suggested and it make sense to use unique_ptr interface for small usages like above. Let me come back with suggestion/modification. |
@addaleax @refack I like the solution suggested by @refack to use unique_ptr. At the same time, I could be wrong but I believe MallocedBuffer was introduced to create a common interface for all memory management patterns especially malloc() and free(). Also, IMHO in this case MallocedBuffer destructor should be using cares API (ares_free_string()) to free the memory instead of free(). Thanks to @refack for pointing that out. |
@uttampawar thank you for following up!
IMHO As for code bloat, you could define: struct AresDeleter {
void operator()(char* ptr) const { ares_free_string(ptr); } noexcept
};
using ares_unique_ptr = std::unique_ptr<char, AresDeleter>; in |
(Fwiw, in this case it would be |
d5800ce
to
16ca770
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ares_unique_ptr
looks good.
Left comments about unnecessary var recycling.
src/cares_wrap.cc
Outdated
return ARES_EBADRESP; | ||
} | ||
ptr += temp_len + NS_QFIXEDSZ; | ||
|
||
char* rr_name_temp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about that but wasn't sure. Thanks for clarifying.
src/cares_wrap.cc
Outdated
|
||
status = ares_expand_name(ptr, buf, len, &soa.nsname, &temp_len); | ||
status = ares_expand_name(ptr, buf, len, &nsname_temp, &temp_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@refack Currently the status to return is captured in one variable 'status'. In a for loop, all the 'break' statements breaks out of loop and return some status. Having multiple of these small scoped variables breaking the flow by having multiple return statements instead of break stmt. I've not seen any explicit reference, but is this acceptable coding standard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO what you did is great, that's exactly one of the reasons to avoid recycling.
Now you stop the function on first error and propagate it, and not continue to process in an error state.
Double win 🥇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW: it's was simpler to do this now because of the RAII semantics of ares_unique_ptr.
Triple win 🏆
Thanks, IMHO that part looks nice and succinct 👍 |
16ca770
to
c5a4017
Compare
@refack @addaleax I've rebased this patch on current master, and made changes addressing all the concerns. Looking for any feedback you may have. $ make -j4 test $ git log $TRAVIS_COMMIT_RANGE --pretty=format:'%h' --no-merges | tail -1 | xargs npx core-validate-commit --no-validate-metadata |
@refack Could you take another look? |
Thanks for the contribution! 🎉 (If you're interested in other possible contributions to Node.js but don't have a good idea of where to start looking, some ideas are posted at https://www.nodetodo.org/next-steps/.) |
Introduced use of smart pointers instead of MallocedBuffer to manage memory allocated in the cares library. PR-URL: #23628 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Should this be backported to v8.x and v10.x or should it bake a bit more |
Introduced use of smart pointers instead of MallocedBuffer to manage memory allocated in the cares library. PR-URL: #23628 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Introduced use of smart pointers instead of MallocedBuffer to manage memory allocated in the cares library. PR-URL: #23628 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Refael Ackermann <[email protected]>
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesTest results:
$ python tools/test.py -I 'parallel/test-dns*'
[00:00|% 100|+ 12|- 0]: Done
$ make test
[06:23|% 100|+ 2429|- 2]: Done
Failed tests are not due to this change.
This change was to improve memory management by use of MallocedBuffer instead of malloc()/free() calls. I further removed use of structure "ares_soa_reply" by using just local variables which removed 2 additional malloc/free calls via ares_expand_name().