-
Notifications
You must be signed in to change notification settings - Fork 6.3k
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
Guide on "Don't block the event loop" #1478
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.
Haven't finished ready in entirety yet, but one general opinion yet: I think you should go for a more formal tone.
This sounds too much like a blog post to me, not like an "official" guide. That may not be the general feeling of everyone, but I for one would prefer a more neutral tone on a guide found on Node.js official website.
Coming soon: complete review, although I'm not as technical as some!
Thanks for the comment on tone. I will try a more formal approach in my next revision. Thoughts about the technical content? |
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.
As said, I'm not the most technical reviewer you could get, I'm largely a noob in Node.js and JS itself. I still hope my comments will help!
|
||
A safer alternative is the [node-re2](https://github.com/uhop/node-re2) project, which uses Google's blazing-fast [RE2](https://github.com/google/re2) regexp engine. But be warned, RE2 is not 100% compatible with Node's regexps, so check for regressions if you swap in the node-re2 module to handle your regexps. And really complicated regexps may not be supported by node-re2. | ||
|
||
As usual, if you're trying to match something "obvious", like a URL or a file path, use an npm module! |
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.
Quick question here, mostly for my own education:
In that case, wouldn't stringVar.indexOf(searchVar) !== -1
or stringVar.includes(searchVar) !== false
be more efficient, and eliminating the need of another module?
I originally come from PHP, and replacing preg_match()
with stripos($string, $search) !== false
is mostly preferred for the same reasons, so I thought it would be the same in JS.
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.
If you're just doing a strcmp
or substr
-style string match, then yes, you should just use indexOf
. You don't need a regular expression for this, nor will a regexp actually be dangerous in this context.
But sometimes people need more matching power for more complicated expressions, and a sufficiently complicated regexp can lead to REDOS.
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.
Okay, that's reassuring 😄
Maybe in that case you could specify this alternative, and that sufficiently complex regex are the real problem here? (I'm not quite sure what a sufficiently complete regex would be either; regex using escaped/reserved chars and/or logn regex?)
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.
Is the new REDOS section clearer? Still needs work?
|
||
This gives all of the other pending clients a turn. | ||
It's pretty easy to accomplish this by sticking whatever state you're carrying around in a closure or an object. | ||
And you might want to add a TODO to revisit this section of your code later. |
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.
Some example might be good here. I think most newcomers/basic users won't necessarily have a clue about what you're talking here. The notion of 'immediate' in the event loop can be misleading and some people won't understand why it would partly solve the problem IMHO.
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. I've been thinking about some examples.
And you might want to add a TODO to revisit this section of your code later. | ||
|
||
## Don't block the Worker Pool | ||
Work in progress. |
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.
Damn, that was the piece I was most eager to read 😄
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.
Soon!
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.
Alrighty, first draft of this is up.
@davisjam It seems your local email in git settings does not match your GitHub one. Can you add a local one to GitHub email list so that your contributions to Node.js repositories can be associated with your GitHub account? |
20a30d5
to
eeb8dfe
Compare
@vsemozhetbyt Thanks, always forget this. |
I haven't revised anything after the REDOS section yet. |
- REDOS - JSON DOS - "Complex calculations without blocking the Event Loop"
@davisjam Way better now IMHO! It's more precise, the examples add a lot, and the tone is better suited to the subject and target as I see it. |
The first draft of the full document is up. Comments appreciated. |
+1 Cool guide! I learned a lot, especially from the REDOS section. |
@Tiriel When you have a moment, can you ping some appropriate folks for technical feedback? Your thoughts on style and correctness are of course also much appreciated. |
Let's cc @nodejs/documentation for a start. |
cc/ @sam-github if you're around, seems like something you'd be interested in. |
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.
Its good to provide some information on this, particularly to provide implementation advice to users. It would be easy for someone to read this and think that network I/O occurs in the uv thread pool (which it doesn't), I think that aspect should be much more clear.
|
||
## Should you read this guide? | ||
If you're writing a server using Node, keep reading. | ||
If you're writing command-line scripts with Node, this guide might still come in handy, but it isn't crucial. |
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.
cli scripts can do, for example, heavy DNS lookups at the same time as file I/O, so worker pool can be blocked.
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.
True. I view the server case as more concerning because of the DoS possibility. Do you want me to remove this caveat?
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.
Remove, or expand to make more clear the situations in which some things aren't problems. No need to optimize anything for a short-running app that does a one-shot HTTP request is what you are trying to say, I think.
If you're writing command-line scripts with Node, this guide might still come in handy, but it isn't crucial. | ||
|
||
## TL; DR | ||
Node.js runs JavaScript code in the Event Loop and handles I/O in a Worker Pool (the threadpool). |
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.
No, just file I/O, network I/O is handled on the main loop.
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. I'll fix to clarify what kinds of I/O go to the Worker Pool.
## TL; DR | ||
Node.js runs JavaScript code in the Event Loop and handles I/O in a Worker Pool (the threadpool). | ||
Node scales well, sometimes better than more heavyweight approaches like Apache. | ||
The secret to Node's scalability is that it uses a small number of threads to handle many clients. |
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.
where small number of threads is likely zero thread for a network server (no file i/o
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 depends on the env variable and on whether the server has ever used the threadpool, since libuv's threads are created the first time the threadpool is used. I don't see a reason to confuse the issue though, this is intended as the high-level summary.
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 feel this confuses the issue, though, because the secret to node's scalability isn't that it uses threadpools well (so does java), the secret is that a fairly typical HTTP server making outbound DB or other network requests uses no threads at all.
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.
Right. It uses one thread -- the main loop -- to handle many clients, rather than Apache-style with one thread per active client.
|
||
## A quick review of Node's architecture | ||
|
||
Node uses the Event-Driven Architecture: it has an Event Loop for orchestration and a Worker Pool for expensive tasks. |
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.
file i/o isn't expensive, no more so than network i/o, it just doesn't have good completion APIs at the O/S level to allow it to be done on the main loop, so threads are needed so that i/o can be done with blocking system calls
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 understanding of the libuv loop (which is limited to Linux) is that the trouble is not the completion APIs but rather the ability of the OS to say that the file descriptor is ready and thus not to block when you do I/O on it. libuv only sends network traffic when it will not block in doing so, but the OS will not guarantee that read/write on a file descriptor will be non-blocking.
Based on this understanding, my claim is correct - the I/O done on the event loop is always cheap from the event loop's perspective because the OS guarantees it to be non-blocking.
Am I missing something 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.
Maybe. The actual problem that is that you can't poll/epoll/select/kqueue a file descriptor for readiness, and so we can't do non-blocking reads or writes. And I guess you could call a blocking call "expensive", but network i/o can be done in thread pools with blocking calls, and lots of languages do exactly that. node doesn't, because network i/o can be non-blocking on unix, whereas file i/o can't (without the async i/o posix extensions, but they are poorly supported, and while windows has better async i/o for files, early tests by libuv authors showed that it would randomly block, it was to flaky to be useful).
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.
Last I checked, posix async I/O is just a threadpool in libc, no different from the libuv threadpool. I've prototyped Node with kernel AIO to see if things had improved since 2011, but the O_DIRECT restriction remains and its costs are catastrophic.
Anyway, I'll flesh out this discussion a bit 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.
aio was intended to be implemented in kernel and thus to rock... but hasn't happened yet, the saga is long, some info here: https://lwn.net/Articles/724198/. There is various user-space emulation using blocking I/O in a thread pool.... so, yeah, uv doesn't use that, there is no point.
The Event Loop's queue is full of pending events. | ||
When the Event Loop sees a new event, it invokes its callback, which is JavaScript code that sees your Node application. | ||
The Event Loop is the only consumer of this queue. | ||
The Node runtime produces events for this queue. |
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 o/s produces the events, not the node runtime
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, via things like epoll. Where do you see the line between technical accuracy and "missing the forest for the trees" in guides like this?
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 its not really a queue, if it was, we could ask "how long is that queue?", but we can't.... because it doesn't exist. node reads from the o/s's internal queue, gets a set of ready events, processes them then blocks reading again. no queue. the whole queue thing is brought up sometimes, but I don't think introducing something that doesn' exist is helpful in understanding how node works. I never describe anything like it in https://www.youtube.com/watch?v=P9csgxBgaZ8, for example
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.
From a framework developer perspective, it's a queue that you can only see looking backwards. From an application developer perspective, though, I think the concept of a queue is a fine approximation of reality. I'm envisioning the target audience to be application developers for whom I think the "queue" concept is fine.
How strongly do you feel about this?
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 am OK with the whole thing, except this sentence:
The Node runtime produces events for this queue.
I think that specific sentence should be removed. node doesn't produce the events. it asks for the o/s to produce them. I wouldn't say that I'm producing a cappucino when I order a cappucino at a cafe. Causing it to be produced? Sure, producing it? no.
When the Event Loop sees a new event, it invokes its callback, which is JavaScript code that sees your Node application. | ||
The Event Loop is the only consumer of this queue. | ||
The Node runtime produces events for this queue. | ||
Specifically, Node asks libuv to monitor file descriptors using [epoll](http://man7.org/linux/man-pages/man7/epoll.7.html)), and the Event Loop's queue consists of the ready descriptors. |
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.
only on linux, its kqueue on OS X, and other mechanisms on Windows
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, good catch.
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.
What do you think about saying "an epoll-like system call" with a link to the epoll man page? Part of my goal is to point users to resources that will give them a richer understanding of Node concepts and Node under the hood. That was also my intent with the "cooperative multitasking" link.
The consumers of this queue are the Workers in the Worker Pool, who all compete to work on the next Task. | ||
Most of the time, the Event Loop is the only producer of Tasks for the Worker Pool. | ||
|
||
It is crucial to understand that this queue-based approach means that the Event Loop and the Workers rely on [cooperative multitasking](https://en.wikipedia.org/wiki/Cooperative_multitasking). |
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 isn't the common understanding of cooperative multi-tasking. coop tasking means that the running thread can suspend itself, but node doesn't do this, ever (on main loop or in pool). it runs to completion, which is not cooperative multitasking
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 couldn't think of a better way to describe this other than saying "queue-based". Should I stick with that? Is there a better term?
Counterpoint, though -- queue-based is "cooperative multitasking" in the sense that a callback/task yields precisely when it completes.
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 think that other than the queue to the uv work pool (which is actually a queue), I don't think queues should be mentioned. Not sure what a good replacement is, sorry! I can see the ghostly parallels, both node and co-op multitasking systems require careful consideration of how much work you do, and how you construct your app, but the analogy breaks down fast, because cooperative multi-tasking has a fairly agreed upon definition, and its not what node does, so better stay away from it, IMO.
##### Best practices for offloading | ||
Follow these best practices for offloading: | ||
1. You should create your own Computation Worker Pool for extensive computation. | ||
2. The Computation Worker Pool should have no more than Workers than your machine has [logical cores](https://nodejs.org/api/os.html#os_os_cpus). |
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.
what about when you have multiple addons, each with a number of threads equal to the number of logical cores?
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 would suggest creating a single Computation Worker Pool at the application level, similar to the Node Worker Pool that handles I/O from all points in the application. Do you have any reference applications I can look at to describe alternate patterns?
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.
but no such thing exists, so current addons don't use it. it has been proposed that uv expose two work APIs, one for blocking non-cpu intensive calls (file i/o, dns.lookup), the other for cpu intensive activities (crypto.pbkdf for key generation, etc), but no code has been PRed, or benchmarks to show it will work. so, in the meantime, you have to consider all the internal pools of all the addons, and actually most addons will mix cpu and i/o intensive work in the same uv work pool. I'm slowly working towards getting metrics in node about the state of the uv pool, but that doesn't exist yet, so not worth mentioning 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.
OK. If I added such an API would it be of interest? Is anyone working on this right now?
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.
Also, I'll just remove this section. It sounds like this is too much of an open problem to worry about right now.
### What is the Node Worker Pool used for? | ||
In the Node core modules, the Node Worker Pool is used for the asynchronous versions of the following classes of APIs: | ||
1. File system I/O | ||
2. DNS queries |
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.
only dns.lookup, the other ones don't use the pool
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. I'll fix this. I believe all of the FS APIs map directly to libuv versions, though?
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.
https://nodejs.org/api/fs.html#fs_threadpool_usage - except fs.FSWatcher()
Then this function would still be asynchronous, but the cost of each partition would be `O(n)`, not `O(1)`, making it much less safe to use for arbitrary values of `n`. | ||
|
||
## Conclusion | ||
Node has threads called Event Handlers, i.e. the Event Loop and the threadpool Workers, and these execute JS- and C++-callbacks. |
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 thread in the libuv work pool aren't called Event Handlers, except here, by you! And the lib uv workers never execute JS code. I don't think its helpful to overgeneralize the main thread to describe it as a kind of worker.
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, I agree that this is a weird overgeneralization. I'll take another look at this.
Thanks for all the great comments, @sam-github! I'll get started on a revision. |
Just a quick semi off-topic: |
- Rewrite the "A quick review of Node" section - Remove "Event Handler" abstraction - Provide more internal details - Relax the "Offloading best practices" - New task variation example: crypto.randomBytes - Misc. formatting and bonus material
@sam-github e9c40e9 attempts to address your concerns. |
This includes I/O for which an operating system does not provide a non-blocking version, as well as particularly CPU-intensive tasks. | ||
|
||
These are the Node module APIs that make use of this Worker Pool: | ||
1. I/O-intensive |
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.
worth mentioning net
module?
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 is my understanding that the net
module does not use the Worker Pool, thanks to good OS support for non-blocking network I/O. The module docs do not bear the usual caveats about the threadpool, and as far as I can tell from the source there is no call to uv_queue_work
.
Am I wrong in this? Or are you suggesting I explicitly mention the net
module as a "safe" option? If so, I attempted to do so in the section "What code runs on the Event Loop?" with the sentence "The Event Loop will also fulfill the non-blocking asynchronous requests made by its callbacks, e.g., network I/O.". Do you think I should name a few examples there, like net
and UDP
?
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.
correct, net does not use threads
thanks for addressing my comments @davisjam |
Sure thing, @sam-github. Any more suggestions, or people that should be tagged? |
@jasnell was looking at loop behaviour, he might have some comments |
Then this function would still be asynchronous, but the cost of each partition would be `O(n)`, not `O(1)`, making it much less safe to use for arbitrary values of `n`. | ||
|
||
## Conclusion | ||
Node has two types of threads: one Event Loop and `k` Workers. |
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.
What about the JavaScript thread?
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 am referring to the "JavaScript thread" as the Event Loop throughout, to contrast it with the Worker Pool (threadpool) threads, and because it does more than JavaScript (e.g. wandering into libuv to fulfill network I/O requests, timers, etc.).
See the section "What code runs on the Event Loop?"
"In summary, the Event Loop executes the JavaScript callbacks registered for events, and is also responsible for fulfilling non-blocking asynchronous requests like network I/O."
Let me know if this is not clear or if you have alternative suggestions for phrasing.
#### Variation example: Long-running file system reads | ||
Suppose your server must read files in order to handle some client requests. | ||
After consulting Node's [File system](https://nodejs.org/api/fs.html) APIs, you opted to use `fs.readFile()` for simplicity. | ||
However, `fs.readFile()` is not partitioned: it submits a single `fs.read()` Task spanning the entire file. |
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 actually being fixed if I understand correctly: nodejs/node#17054
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.
That's the idea, anyway. But this guide is describing things as they are, not as I hope they will be.
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.
See also nodejs/node#17250.
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.
Might be worth adding a link to nodejs/node#17054 in here to note the proposed change then.
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.
Added a link in 0a42816, thank you.
I recognize this is a pretty long document, but does anyone have additional change requests? |
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 really helpful, thanks a lot for writing this!
LGTM, couple of questions though.
`JSON.parse` and `JSON.stringify` are other potentially expensive operations. | ||
While these are `O(n)` in the length of the input, for large `n` they can take surprisingly long. | ||
|
||
If your server manipulates JSON objects, particularly those from a client, you should be cautious about the size of the objects or strings you work with on the Event Loop. |
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.
Is it worth mentioning asynchronous JSON parsing modules here (and then linking to The risks of npm modules
section)?
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, will do.
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.
Addressed in ccd4f75.
|
||
In a server, *you should not use the synchronous APIs from these modules*. | ||
These APIs are expensive, because they involve significant computation or require I/O. | ||
If you execute them on the Event Loop, they will take far longer to complete than a typical JavaScript instruction, blocking the Event Loop. |
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.
What do you do if there is no async core api?
Maybe the answer is "raise an issue in core", but this was my question on reading this, so maybe worth saying something.
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.
Thanks, I'll try another pass on this.
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.
Addressed in ccd4f75.
## The risks of npm modules | ||
Node developers benefit tremendously from the [npm ecosystem](https://www.npmjs.com/), with hundreds of thousands of modules offering functionality to accelerate your development process. | ||
|
||
However, the vast majority of these modules are written by third-party developers and undergo no formal verification process. |
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.
Does node core undergo a formal verification process? Mentioning it here makes it seem like it's in contrast to something else, but I'm not sure what that something is.
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.
You're right, "formal" isn't the right word. It's more of a "many eyes on Node, few eyes on most 3rd-party modules" type situation. I'll change the wording.
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.
Addressed in ccd4f75.
@sam-github Anything else needed for your approval? |
looks good to me |
The full draft has been up for about 2 weeks and has gone through a few reviews. Ready to be landed? |
Towards #1471.
Not finished yet, hoping to get some initial feedback.