-
Notifications
You must be signed in to change notification settings - Fork 31.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
fs: encapsulate FSReqWrap more #18112
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.
LGTM
For completeness, let's do a benchmark run but I don't expect things to show up.
Benchmarks: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/92/
The point of the original code is to keep down the number of allocations. This PR undoes that but I don't understand why and the commit log does a poor job at explaining it. |
I'll iterate on this a bit more to reduce the allocations but the motivation is to make the introduction of the promises API easier. Looking at #17739, the idea is to introduce a |
src/node_file.cc
Outdated
void FSReqWrap::Dispose() { | ||
if (info_ != nullptr) | ||
info_->Dispose(); | ||
this->~FSReqWrap(); |
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.
Leaks this
, i.e., the FSReqWrap
. Should probably be just delete wrap
at the call site and move the if (info_ != nullptr)
bit to the destructor (and remove this method because it's no longer used after that.)
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.
+1 ... any similar concerns with FSReqInfo::Dispose immediately below it?
src/node_file.h
Outdated
size_t self_size() const override { return sizeof(*this); } | ||
void* operator new(size_t size) = delete; | ||
void* operator new(size_t size, char* storage) { return storage; } | ||
char* inline_data() { return reinterpret_cast<char*>(this + 1); } |
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.
Maybe add a CHECK_EQ(data_, reinterpret_cast<char*>(this + 1))
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.
good idea
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.
actually, I take that back. Adding that CHECK_EQ in here breaks things. I'll investigate further, but I think having the CHECK elsewhere may make more sense.
Looking at it, I believe the check actually needs to be:
CHECK_NE(data_, reinterpret_cast<char*>(this + 1));
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 assume you mean it breaks because of the memcpy after the placement new?
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
src/node_file.cc
Outdated
const bool copy = (data != nullptr && ownership == Ownership::COPY); | ||
const size_t size = copy ? 1 + strlen(data) : 0; | ||
char* const storage = new char[sizeof(*info_) + size]; | ||
info_ = new(storage) FSReqInfo(syscall, data, encoding); |
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's arguably neater and less error prone to move this into FSReqInfo.
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.
My apologies, but I need to ask: move which part into FSReqInfo?
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.
The placement new logic. I.e., make it so there is only one way to create an FSReqInfo instance and make that logic part of the class itself.
@addaleax @bnoordhuis ... ok, reworked this to drop the addition of |
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.
This is still adding the extra allocation, if I read it correctly?
src/node_file.cc
Outdated
const bool copy = (data != nullptr && ownership == Ownership::COPY); | ||
if (copy) { | ||
size_t size = 1 + strlen(data); | ||
data_ = static_cast<char*>(memcpy(Calloc<char>(size), data, size)); |
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.
Malloc(size)
should work just the same and doesn’t require zero-ing out memory that is being overwritten anyway
Also, these two lines are almost exactly reimplementing strdup()
.
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.
good point about strdup :-)
src/node_file.h
Outdated
const char* syscall_; | ||
const char* data_; | ||
const char* data_ = nullptr; | ||
const char* buffer_ = nullptr; |
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.
buffer_
and ReleaseEarly()
are essentially unused now, as far as I can tell?
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.
Ownership::COPY
(and therefore buffer_
) is used within the WriteString()
function, line 1008.
ReleaseEarly()
is used with FSReqAfterScope
(see line 157).
As far as I can tell, those are the only current uses and can likely be refactored away at some point but I think I'd rather prefer to avoid doing that in this PR
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.
oh wait... ha! yeah, you're right. hold on a sec...
(it's definitely a friday... brain already disengaged for the weekend)
58ae07d
to
8346f85
Compare
@addaleax ... ok, there... should be fixed. I'm toying with just going ahead and making |
src/node_file.cc
Outdated
return that; | ||
void FSReqWrap::Reject(Local<Value> reject) { | ||
Local<Value> argv[1] { reject }; | ||
MakeCallback(env()->oncomplete_string(), arraysize(argv), argv); |
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.
Can just be MakeCallback(env()->oncomplete_string(), 1, &reject);
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.
Yep, may as well. Will do
src/node_file.cc
Outdated
const bool copy = (data != nullptr && ownership == Ownership::COPY); | ||
if (copy) { | ||
// Setting buffer_ lets us know that we'll need to free on cleanup | ||
data_ = buffer_ = strdup(data); |
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.
strdup()
is almost certainly wrong. That only works for zero-terminated strings whereas data
can be arbitrary binary data, as far as I can tell.
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.
Ok yeah, looking at it, I think that current uses of this just end up working with strdup by accident. You're right tho, the code appears to have been originally written to support arbitrary binary data. Will change that back
src/node_file.h
Outdated
const char* data_; | ||
|
||
const char* data_ = nullptr; | ||
char* buffer_ = nullptr; |
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 you're making changes anyway, I'd change these to std::vector<char>
if possible.
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.
done
8346f85
to
d3e5477
Compare
@bnoordhuis @addaleax ... updated :-) |
CI is good. |
@mcollina @addaleax @bnoordhuis ... I'd like to get this landed so I can move forward with the promises support. PTAL |
@jasnell Looks like this needs a rebase first. |
heh, yeah, another PR landed shortly after I posted the comment yesterday. Will rebase today :-) |
d3e5477
to
ad908da
Compare
Rebased! |
@jasnell Idk, this still LGTM apart from the memory allocation change … can you explain how that improves encapsulation? The benchmark CI here seems to agree with the extra overhead not being a good idea… |
Which extra allocation? (Just wanna be sure) |
@jasnell If I understand correctly, this patch copies the data in the case of |
Ah yes. The FSReqWrap object memory was being allocated as sizeof(FSReqWrap) + the additional storage space necessary, then data was being copied in to that extra space. Now, the FSReqWrap is created and then the space to store The idea here is to introduce a variant on Previously, what was happening is that the user would create a |
@jasnell Yeah, I get that, but it’s not quite obvious how the change to the allocation model factors in here – why not e.g. store the promise like the callback, as a property on the JS object? |
Starting another benchmarking run since this changed fairly significantly since the last run: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/99/ |
One way we could keep the allocation the same would be to pass the data that is to be copied to the constructor of the |
@Matteo @jasnell Is this output easier to understand? You can see that the improvement is only slightly higher than the accuracy, which is calculated with a 5% risk in mind.
|
Yes it is! |
oh, I like that. @AndreasMadsen ... how easy would it be to modify benchmark/compare.R to give us that output? |
@jasnell It is easy. I have a local version of I have been tinkering with it ever since I discovered I was closing in on 100 commits :p |
CI looks good with only unrelated failures. |
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 some comments.
src/node_file.cc
Outdated
memcpy(*buffer_, data, len); | ||
data_ = *buffer_; | ||
} else { | ||
data_ = nullptr; |
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.
Superfluous assignment, implied by the CHECK_EQ().
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.
tbh, the CHECK_EQ really should go inside the if block. Either way, this assignment is superfluous.
src/node_file.h
Outdated
@@ -6,6 +6,8 @@ | |||
#include "node.h" | |||
#include "req_wrap-inl.h" | |||
|
|||
#include <vector> |
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.
Unused include.
data_ = nullptr; | ||
} | ||
} | ||
virtual void ResolveStat(); |
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.
Why are these methods virtual?
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.
because the plan is to have FSReqPromise
just extend from FSReqWrap
and override those. That may change tho.
@mcollina ... here are the results from the additional benchmark run...
|
The variance in the throughput benchmarks from run to run do, in fact, appear to be gc related. |
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
In further preparation for the Promises enabled fs API, further encapsulate FSReqWrap details. The intent here is to isolate, as much as possible, the functionality of the various fs functions from the details of how the results are notified. The promises implementation will use a `FSReqPromise` alternative to `FSReqWrap` that will use the same API so that both models can be used without changing any of the actual implementation details for the various methods.
1d855cc
to
fa78aa5
Compare
Ping @addaleax ... you good with this now also? |
One more post-squash post-nit-fix CI run: https://ci.nodejs.org/job/node-test-pull-request/12614/ |
@jasnell Yeah, the code changes LGTM – it’s still not really clear to me how this is going to help, but it also doesn’t really make anything worse, so I’m good here. :) |
@addaleax ... hopefully that will be more apparent in the follow on PR. The approach is largely to make handling the Promise easier and to avoid an extra boundary cross to create the Promise. |
In further preparation for the Promises enabled fs API, further encapsulate FSReqWrap details. The intent here is to isolate, as much as possible, the functionality of the various fs functions from the details of how the results are notified. The promises implementation will use a `FSReqPromise` alternative to `FSReqWrap` that will use the same API so that both models can be used without changing any of the actual implementation details for the various methods. PR-URL: #18112 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
Landed in 4b9ba9b |
Does this make sense for v9.x? If so should it will need to be backported |
In further preparation for the Promises enabled fs API, further encapsulate FSReqWrap details. The intent here is to isolate, as much as possible, the functionality of the various fs functions from the details of how the results are notified. The promises implementation will use a `FSReqPromise` alternative to `FSReqWrap` that will use the same API so that both models can be used without changing any of the actual implementation details for the various methods. PR-URL: nodejs#18112 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
In further preparation for the Promises enabled fs API, further encapsulate FSReqWrap details.
This is another bit of code extracted from the WIP fs promises PR: #17739. Pulling this out in order to make the eventual fs promises PR easier to review.
/cc @addaleax @mcollina
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)