-
Notifications
You must be signed in to change notification settings - Fork 30k
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
module: Allow runMain to be ESM #14369
Conversation
Going to be on vacation til TC39 next week / tests need to be pulled in from sketchy. Opening this since it is going to want a lot of eyes. |
Great to see progress here. Won't have an opportunity to review likely until after NodeSummit next week. |
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.
More to come later. Is the C++ resolution necessary or for performance? I kinda drew the line at parsing JSON in C++...
} | ||
|
||
async createModule() { | ||
const source = `${await asyncReadFile(this.url.pathname)}`; |
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's a specialized function you could use instead of this.url.pathname
.
`${this.url}`); | ||
} | ||
} | ||
Object.setPrototypeOf(StandardModuleRequest.prototype, Object.create(null)); |
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 this necessary? If so, why not just set the prototype to null
?
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 an encapsulation general thing so that it does not inherit from Object so cannot be affected by mutated globals. In line with #10135
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.
Yeah I get that, but you don't need to set the prototype object to an object with prototype null, you can just set the prototype of the prototype object to null.
A chart for easier consumption of what I just wrote:
Current:
| StandardModuleRequest.prototype |
|| [[Prototype]]
vv
| Object.create(null) |
|| [[Prototype]]
vv
| null |
Why not:
| StandardModuleRequest.prototype |
|| [[Prototype]]
vv
| null |
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, yes. we should change that.
lib/internal/loader/Loader.js
Outdated
const ModuleMap = require('internal/loader/ModuleMap'); | ||
const ModuleJob = require('internal/loader/ModuleJob'); | ||
const resolveRequestUrl = require('internal/loader/resolveRequestUrl'); | ||
const {Error, Promise: {resolve: PromiseResolve}} = global; |
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 this (and safe_globals
and other such things) necessary?
@@ -0,0 +1,24 @@ | |||
const copyProps = (unsafe, safe) => { |
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 this is necessary, 'use strict'
.
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.
was fixed, forgot to reply
const {URL} = require('url'); | ||
const internalCJSModule = require('internal/module'); | ||
const NativeModule = require('native_module'); | ||
const Path = require('path'); |
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 not use destructuring to prevent monkey patching as has been done elsewhere? Either way s/P/p/
.
src/loader/module_wrap.cc
Outdated
return; | ||
} | ||
|
||
const Local<Value> ret(result.ToObject( |
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 not just ret =
?
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.
@TimothyGu I agree with your comments here, but it might be easiest to just do what @targos did and push changes, esp. for suggestions like these, yourself
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/loader/module_wrap.cc
Outdated
} | ||
|
||
using namespace node::url; | ||
URL __init_cwd() { |
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.
Weird identation...
src/loader/module_wrap.cc
Outdated
else if (specifier[0] == '.') { | ||
if (len == 1 || specifier[1] == '/') return true; | ||
else if (specifier[1] == '.') { | ||
if (len == 2 || specifier[2] == '/') return true; |
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 this support 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.
Never mind, this is to be consistent with https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier.
src/loader/module_wrap.cc
Outdated
return URL(""); | ||
} | ||
URL Resolve(std::string specifier, URL* base, bool read_pkg_json) { | ||
auto pure_url = URL(specifier.c_str()); |
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.
URL
takes a std::string
as input.
src/loader/module_wrap.cc
Outdated
using v8::EscapableHandleScope; | ||
using v8::PropertyDescriptor; | ||
|
||
const std::string EXTENSIONS[] = {".mjs", ".js", ".json", ".node"}; |
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.
Did we reach a conclusion on the non-Web-compatible automatic extension adding behavior?
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 a very complex feature to review without any attached unit tests. Would you mind adding them?
I took the liberty to fix ESLint issues and compilation. make test
make test-parallel
|
src/loader/module_wrap.cc
Outdated
TryCatch compileCatch(iso); | ||
auto maybe_mod = ScriptCompiler::CompileModule(iso, &source); | ||
if (compileCatch.HasCaught()) { | ||
compileCatch.ReThrow(); |
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.
Not sure but I think all the TryCatch
can be replaced by maybe.isEmpty()
checks. /cc @addaleax
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, checks like these should just be maybe.IsEmpty()
(Unless there is some weird interaction with uncaught exception handlers that this code relies on, but I don’t think that is the case, and if it is, that should be documented)
adding myself as an assignee in an attempt to not forget about it |
The entire test suite passes here after dbd1ec3550423b0476d57bf638594bdd41e5f607. @targos can you recheck? |
@TimothyGu I pushed a last fix. Now |
@targos @TimothyGu You are awesome! 💙 |
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 rough review. Will give it a try to day hopefully.
I suppose we will need quite the assortment of test cases? Or are there already standard tests somewhere?
Also all errors should probably switched to the new internal/errors
system.
lib/internal/loader/index.js
Outdated
const loader = new Loader(); | ||
|
||
exports = module.exports = { | ||
loader, |
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 this file only necessary to keep a global loader
instance?
In other modules e.g. the repl, the REPL object has it as a static reference.
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.
we could move it to be a static ref since these files are never exposed to user code.
src/node_url.cc
Outdated
@@ -1283,7 +1283,9 @@ void URL::Parse(const char* input, | |||
} | |||
break; | |||
case kNoScheme: | |||
cannot_be_base = base->flags & URL_FLAGS_CANNOT_BE_BASE; | |||
if (has_base) { |
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 be in a separate commit/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.
dunno / after #14096 I think PRs are more intrisicly bundled if you need changes to land things, but might be mistaken
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'll split it out as it's a bug in a code path actually used by code in master 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.
please do split it out
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 to splitting it out. It may be worthwhile having a test case for it also since it doesn't seem to be caught by the WPT tests 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.
node.gyp
Outdated
# headers to make for a more pleasant IDE experience | ||
'src/loader/module_wrap.h', |
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.
All of these should be alphabetical. lib/internal/bootstrap_node.js',
is an exception for some historical reasons.
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’m also wondering, is there any particular reason for having this be in its own directory?
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.
@addaleax was for my own organization but we can keep with all files in a single dir if you wish.
lib/module.js
Outdated
@@ -32,6 +32,7 @@ const path = require('path'); | |||
const internalModuleReadFile = process.binding('fs').internalModuleReadFile; | |||
const internalModuleStat = process.binding('fs').internalModuleStat; | |||
const preserveSymlinks = !!process.binding('config').preserveSymlinks; | |||
const ESM = require('internal/loader/index'); |
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.
wouldn't internal/loader
be enough with an index.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.
The internal modules work differently, there is no path resolution mechanism for them
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, right.
filename = Module._resolveFilename(request, parent, isMain); | ||
} catch (e) { | ||
// try to keep stack | ||
e.stack; |
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's this for?
Is this actually a getter?
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.
lib/module.js
Outdated
throw e; | ||
} | ||
); | ||
return; |
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 somehow set these, as below?
process.mainModule = module;
module.id = '.';
} | ||
} | ||
}; | ||
const makeSafe = (unsafe, safe) => { |
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.
These should be function () {}
s?
(Please don't export arrow functions unless absolutely necessary.)
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.
Do we have a policy around that? What’s the reason?
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? Arrow functions aren't constructable, so you'd be exporting a tighter API
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.
@Fishrock123 can you clarify why?
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.
We haven't had much of a defined policy about this. There have been cases where we have avoided changing existing exports from regular functions to arrow functions but given that this is a new API, I'm not sure if there's an issue here.
lib/internal/loader/Loader.js
Outdated
try { | ||
return pathToFileURL(process.cwd()); | ||
} catch (e) { | ||
// If the current working directory no longer exists. |
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 like the regular modular loader may explode in this case?
We should keep the behavior consistent whatever it 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.
The regular loader doesn't explode. There are actually test cases for that. Preserving the original behavior is what I'm trying to 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.
seems sane
lib/internal/loader/Loader.js
Outdated
class Loader { | ||
constructor(base = getBase()) { | ||
this.moduleMap = new ModuleMap(); | ||
this.base = base ? new URL(`${base}`) : base; |
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 throw or something if base
is undefined
or null
?
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 above, this is necessary to keep Loader construction from throwing, in a case when it's still perfectly fine to use native modules and absolute paths.
If someone wants to port the existing tests, they are here: https://github.com/nodejs/sketchy-as-heck-loader-impl/tree/7e248d2b6fe8082532e15da1802b79d262a1d132/lib/internal/loader/test |
@Fishrock123 There are some test cases in @bmeck's sketchy-as-heck repo, I assume he has not yet had the time to port them over. |
TBH I'm still not sure about the whole safe_globals business. If someone can explain its necessity that would be best. |
New CI: https://ci.nodejs.org/job/node-test-commit/11293/ (edit: green) |
New CI: https://ci.nodejs.org/job/node-test-pull-request/9303/ A recap of my work done:
Pending the resolution from nodejs/node-eps#60, maybe we should delay the work on this a bit before the resolver algorithm is set in stone. Some good arguments from both sides. |
lib/internal/loader/Loader.js
Outdated
return getNamespaceOf(module); | ||
} | ||
} | ||
Object.setPrototypeOf(Loader.prototype, Object.create(null)); |
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 is the reason for this line? I am curious about its effect on performance.
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 an encapsulation general thing so that it does not inherit from Object so cannot be affected by mutated globals. In line with #10135
@TimothyGu ( #14369 (comment) )
That PR is additive and does not remove or conflict with any features of this PR. There is no need to delay this PR unless you have concerns with conflict either now or in the future. |
This follows the EPS an allows the node CLI to have ESM as an entry point. `node ./example.mjs`. A newer V8 is needed for `import()` so that is not included. `import.meta` is still in specification stage so that also is not included. PR-URL: #14369 Author: Bradley Farias <bradley.meck@gmail.com> Author: Guy Bedford <guybedford@gmail.com> Author: Jan Krems <jan.krems@groupon.com> Author: Timothy Gu <timothygu99@gmail.com> Author: Michaël Zasso <targos@protonmail.com> Author: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This follows the EPS an allows the node CLI to have ESM as an entry point. `node ./example.mjs`. A newer V8 is needed for `import()` so that is not included. `import.meta` is still in specification stage so that also is not included. PR-URL: #14369 Author: Bradley Farias <bradley.meck@gmail.com> Author: Guy Bedford <guybedford@gmail.com> Author: Jan Krems <jan.krems@groupon.com> Author: Timothy Gu <timothygu99@gmail.com> Author: Michaël Zasso <targos@protonmail.com> Author: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This follows the EPS an allows the node CLI to have ESM as an entry point. `node ./example.mjs`. A newer V8 is needed for `import()` so that is not included. `import.meta` is still in specification stage so that also is not included. PR-URL: #14369 Author: Bradley Farias <bradley.meck@gmail.com> Author: Guy Bedford <guybedford@gmail.com> Author: Jan Krems <jan.krems@groupon.com> Author: Timothy Gu <timothygu99@gmail.com> Author: Michaël Zasso <targos@protonmail.com> Author: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Notable Changes * build: * Snapshots are now re-enabled in V8 #14875 * console: * Implement minimal `console.group()`. #14910 * deps: * upgrade libuv to 1.14.1 #14866 * update nghttp2 to v1.25.0 #14955 * dns: * Add `verbatim` option to dns.lookup(). When true, results from the DNS resolver are passed on as-is, without the reshuffling that Node.js otherwise does that puts IPv4 addresses before IPv6 addresses. #14731 * fs: * add fs.copyFile and fs.copyFileSync which allows for more efficient copying of files. #15034 * inspector: * Enable async stack traces #13870 * module: * Add support for ESM. This is currently behind the `--experimental-modules` flag and requires the .mjs extension. `node --experimental-modules index.mjs` #14369 * napi: * implement promise #14365 * os: * Add support for CIDR notation to the output of the networkInterfaces() method. #14307 * perf_hooks: * An initial implementation of the Performance Timing API for Node.js. This is the same Performance Timing API implemented by modern browsers with a number of Node.js specific properties. The User Timing mark() and measure() APIs are implemented, as is a Node.js specific flavor of the Frame Timing for measuring event loop duration. #14680 * tls: * multiple PFX in createSecureContext [#14793](#14793) * Added new collaborators: * BridgeAR – Ruben Bridgewater PR-URL: #15308
Notable Changes * build: * Snapshots are now re-enabled in V8 #14875 * console: * Implement minimal `console.group()`. #14910 * deps: * upgrade libuv to 1.14.1 #14866 * update nghttp2 to v1.25.0 #14955 * dns: * Add `verbatim` option to dns.lookup(). When true, results from the DNS resolver are passed on as-is, without the reshuffling that Node.js otherwise does that puts IPv4 addresses before IPv6 addresses. #14731 * fs: * add fs.copyFile and fs.copyFileSync which allows for more efficient copying of files. #15034 * inspector: * Enable async stack traces #13870 * module: * Add support for ESM. This is currently behind the `--experimental-modules` flag and requires the .mjs extension. `node --experimental-modules index.mjs` #14369 * napi: * implement promise #14365 * os: * Add support for CIDR notation to the output of the networkInterfaces() method. #14307 * perf_hooks: * An initial implementation of the Performance Timing API for Node.js. This is the same Performance Timing API implemented by modern browsers with a number of Node.js specific properties. The User Timing mark() and measure() APIs are implemented, as is a Node.js specific flavor of the Frame Timing for measuring event loop duration. #14680 * tls: * multiple PFX in createSecureContext [#14793](#14793) * Added new collaborators: * BridgeAR – Ruben Bridgewater PR-URL: #15308
This is super exciting, thanks for all your hard work! 🎉 I read the Interop with existing modules part of the docs and noticed CommonJS modules are exported as default which makes sense since there were no named exports in CommonJS. This means I can use the following syntax just fine: import fs from 'fs';
import _ from 'lodash'; But I cannot use the following syntax: import * as fs from 'fs';
import * as _ from 'lodash'; Most importantly, I cannot use the following syntax: import { copyFile } from 'fs';
import { map } from 'lodash'; For node core APIs that's probably okay. But for all the existing CommonJS modules on npm, this could be a problem because tools that provide tree-shaking, expect the latter syntax. Is there a plan to support the latter syntax for importing smaller pieces of CommonJS modules? |
You're the man @bmeck GG! |
The syntax by which things are imported does not change the ModuleNamespace objects that are created in the ES spec (the result of We have a few problems supporting namespace creation for CJS with custom names for a few reasons and are trying to figure out an opt-in solution that module authors could do. In particular, we need to know the list of property names that As for tree shaking, it should still work the same as tools using that do not tree shake CJS modules (except https://github.com/indutny/webpack-common-shake, which should be unaffected). In real ESM there is no tree shaking so that is not an issue. Named properties are not on the top of my TODO list since they require opt-in changes and can not act like what babel does, but if someone wants to help spec out a pragma or something for opt-in; I am willing to review/help. |
This follows the EPS an allows the node CLI to have ESM as an entry point. `node ./example.mjs`. A newer V8 is needed for `import()` so that is not included. `import.meta` is still in specification stage so that also is not included. PR-URL: nodejs#14369 Author: Bradley Farias <bradley.meck@gmail.com> Author: Guy Bedford <guybedford@gmail.com> Author: Jan Krems <jan.krems@groupon.com> Author: Timothy Gu <timothygu99@gmail.com> Author: Michaël Zasso <targos@protonmail.com> Author: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
PR-URL: nodejs#15290 Refs: nodejs#14369 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Notable Changes * build: * Snapshots are now re-enabled in V8 nodejs#14875 * console: * Implement minimal `console.group()`. nodejs#14910 * deps: * upgrade libuv to 1.14.1 nodejs#14866 * update nghttp2 to v1.25.0 nodejs#14955 * dns: * Add `verbatim` option to dns.lookup(). When true, results from the DNS resolver are passed on as-is, without the reshuffling that Node.js otherwise does that puts IPv4 addresses before IPv6 addresses. nodejs#14731 * fs: * add fs.copyFile and fs.copyFileSync which allows for more efficient copying of files. nodejs#15034 * inspector: * Enable async stack traces nodejs#13870 * module: * Add support for ESM. This is currently behind the `--experimental-modules` flag and requires the .mjs extension. `node --experimental-modules index.mjs` nodejs#14369 * napi: * implement promise nodejs#14365 * os: * Add support for CIDR notation to the output of the networkInterfaces() method. nodejs#14307 * perf_hooks: * An initial implementation of the Performance Timing API for Node.js. This is the same Performance Timing API implemented by modern browsers with a number of Node.js specific properties. The User Timing mark() and measure() APIs are implemented, as is a Node.js specific flavor of the Frame Timing for measuring event loop duration. nodejs#14680 * tls: * multiple PFX in createSecureContext [nodejs#14793](nodejs#14793) * Added new collaborators: * BridgeAR – Ruben Bridgewater PR-URL: nodejs#15308
From the July 26 TC39 notes you said:
Are you now open to Node specific pragmas or does this mean you'd want the CJS import named exports pragma to make its way to the TC39? Is it too Node specific? |
@jdalton Node specific things within Node only CJS things ( |
} | ||
|
||
async resolve(specifier) { | ||
const request = resolveRequestUrl(this.base, specifier); |
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.
Missing await
statement.
This follows the EPS an allows the node CLI to have ESM as an entry point.
node ./example.mjs
. A newer V8 is needed forimport()
so that is notincluded.
import.meta
is still in specification stage so that also is notincluded.
Author: Bradley Farias bradley.meck@gmail.com
Author: Guy Bedford guybedford@gmail.com
Author: Jan Krems jan.krems@groupon.com
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)