Skip to content
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

Remove strict dependency on Node v6.10.x #1139

Merged
merged 5 commits into from
Apr 10, 2018

Conversation

swgillespie
Copy link
Contributor

@swgillespie swgillespie commented Apr 9, 2018

This change makes use of four V8 intrinsics to avoid having to use a
native module to inspect the scope chains of live Function objects. This
unfortunately leads to the limitation of not allowing captures of 'this'
in arrow functions, but that is something we are willing to live with
for now.

Related to #1138 in that we can eliminate the need for our native Node module if we don't allow lexical capture of this in arrow functions. This PR passes all tests on node v6.10.2 that are not this-related. This PR does fail a few tests on node v8.10.0, but I think they are all due to benign differences in the error messages we give for native function capture. For example, here is one such failure:

       '() => handler': closure.spec.js(0,0): captured
         'handler', a function defined at
           '() => os': closure.spec.js(0,0): which captured
             module 'os' which indirectly referenced
      -        function 'cpus': os.js(0,0): which referenced
      -          function 'getCPUs': which could not be serialized because
      -            it was a native code function.
      +        function 'getHostname': which could not be serialized because
      +          it was a native code function.

       Function code:
      -  function getCPUs() { [native code] }
      +  function getHostname() { [native code] }

We'll fail to serialize this closure when we hit the first native code function we see, and apparently node v8.10.0 changed the implementation of os so that the first native code function we try to serialize is getCPUs.


For the changelog:

It is now no longer necessary to use a specific version of Node to run Pulumi programs. The only requirement now is that you must use a version of Node that is verson v6.10.2 or greater when running Pulumi programs.

@swgillespie
Copy link
Contributor Author

This is going to fail a few tests around this until Cyrus's PR goes in.

@swgillespie swgillespie added area/sdks Pulumi language SDKs customer/feedback labels Apr 9, 2018
@swgillespie
Copy link
Contributor Author

#1136

export NODE_PATH:=$(NODE_PATH):./runtime/native/build/Release

ensure::
cd runtime/native && ./ensure_node_v8.sh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yaay,

// the latter function returns the i'th entry in a function's scope chain, given a function and
// index i.
const getFunctionScopeDetails: (func: Function, index: number) => any[] =
new Function("func", "index", "return %GetFunctionScopeDetails(func, index);") as any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of a variable that is assigned a lambda... can you just make this a normal function? That seems like it would read a lot nicer... (or is it necessary for this to be a lambda to work properly?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore this.

const getFunctionScopeDetails: (func: Function, index: number) => any[] =
new Function("func", "index", "return %GetFunctionScopeDetails(func, index);") as any;
const getFunctionScopeCount: (func: Function) => number =
new Function("func", "return %GetFunctionScopeCount(func);") as any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same.

}

const pos = getSourcePosition(func);
const location = script.locationFromPosition(pos);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the tests set all line/cols to 0, can you manually test this and validate that everything is the correct 1-based numbers in the output?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an error using node v8.10.0 that you get when you try to capture os:

$ node --version
v8.10.0
[0 Seans-MacBook-Pro ~/go/src/github.com/pulumi/scratch/stack]
$ pulumi update
Performing changes:
* pulumi:pulumi:Stack: (same)
    [urn=urn:pulumi:test::stack::pulumi:pulumi:Stack::stack-test]
error: Error serializing '() => os': index.js(5,54)

'() => os': index.js(5,54): captured
  module 'os' which indirectly referenced
    function 'cpus': os.js(85,13): which referenced
      function 'getCPUs': which could not be serialized because
        it was a native code function.

Function code:
  function getCPUs() { [native code] }

Capturing modules can sometimes cause problems.
Consider using import('os') or require('os') inside '() => os': index.js(5,54)
info: no changes required:
      1 resource unchanged
error: an unhandled error occurred: Program exited with non-zero exit code: 1

The positions in index.js look correct to me.

// As a side-effect of importing this file, we must enable the --allow-natives-syntax V8
// flag. This is because we are using V8 intrinsics in order to implement this module.
import * as v8 from "v8";
v8.setFlagsFromString("--allow-natives-syntax");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the language host is going to launch node directly, can we just pass this to node itself when we launch it, instead of trying to set it at run-time?

Copy link
Contributor Author

@swgillespie swgillespie Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, but I found that to be pretty painful for dev scenarios. We use istanbul wrapping mocha to run our tests, and istanbul doesn't give us direct control over the Node command line(1). I ended up having to hack up the Makefiles to get it to work last time I went down this route.

I'm happy to do that if that's better, but this Node API is very stable and does effectively the same thing.

(1) Mocha doesn't either, but its command-line driver is intelligent enough to pass --allow-natives-syntax onto node.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer that we set it in code like this, since it can be done in TypeScript (no native code), and guarantees that it's set anytime the Pulumi runtime is loaded. Just seems less error prone.

BTW, what happens if we fail to set this? Closure serialization throws? Silently does the wrong thing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P. S. I did find it confusing that this is still called native.ts. I kept looking at it, expecting C++ code, and then getting confused by the presence of JavaScript. Probably just historical, but maybe v8.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, what happens if we fail to set this? Closure serialization throws? Silently does the wrong thing?

You'll get an immediate crash on startup.

> new Function("%AbortJS")
SyntaxError: Unexpected token %

Probably just historical, but maybe v8.ts?

Sure, I can do that. It collides with the builtin node module v8, but we shouldn't be using that anywhere other than this file anyway, so seems fine to me.

export function lookupCapturedVariableValue(func: Function, freeVariable: string, throwOnFailure: boolean): any {
// The implementation of this function is now very straightforward since the intrinsics do all of the
// difficult work.
const scopes = getScopesForFunction(func);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of getting the scopes, and then looking up in teh scopes... coudl we not just walk the scopes, stopping whne we find the thing we're looking for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we can do that - it's probably more efficient, too, since V8 allocates a ScopeIterator for each call to GetFunctionScopeDetails.

This change makes use of four V8 intrinsics to avoid having to use a
native module to inspect the scope chains of live Function objects. This
unfortunately leads to the limitation of not allowing captures of 'this'
in arrow functions, but that is something we are willing to live with
for now.
@swgillespie swgillespie force-pushed the swgillespie/pure-javascript-scopechain branch from 0b976bf to f443fb0 Compare April 9, 2018 23:17
Copy link
Member

@joeduffy joeduffy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 🍾 💯


lint::
$(GOMETALINTER) cmd/pulumi-language-nodejs/main.go | sort ; exit "$${PIPESTATUS[0]}"
tslint -c tslint.json -p tsconfig.json

build::
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${LANGUAGE_HOST}
cd runtime/native && node-gyp configure
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected to also see a boatload of red C++ code in this change? 😀

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coming up in the next commit! 😆

// As a side-effect of importing this file, we must enable the --allow-natives-syntax V8
// flag. This is because we are using V8 intrinsics in order to implement this module.
import * as v8 from "v8";
v8.setFlagsFromString("--allow-natives-syntax");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer that we set it in code like this, since it can be done in TypeScript (no native code), and guarantees that it's set anytime the Pulumi runtime is loaded. Just seems less error prone.

BTW, what happens if we fail to set this? Closure serialization throws? Silently does the wrong thing?

// As a side-effect of importing this file, we must enable the --allow-natives-syntax V8
// flag. This is because we are using V8 intrinsics in order to implement this module.
import * as v8 from "v8";
v8.setFlagsFromString("--allow-natives-syntax");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P. S. I did find it confusing that this is still called native.ts. I kept looking at it, expecting C++ code, and then getting confused by the presence of JavaScript. Probably just historical, but maybe v8.ts?

// The use of the Function constructor here and elsewhere in this file is because
// because V8 intrinsics are not valid JavaScript identifiers; they all begin with '%',
// which means that the TypeScript compiler issues errors for them.
new Function("func", "return %FunctionGetScript(func);") as any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is any public documentation on %FunctionGetScript -- and any other such APIs below -- do you mind also linking to it from within this file? It seems hard to find good information about them!

Copy link
Contributor Author

@swgillespie swgillespie Apr 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no documentation whatsoever! 😈 The only docs are the source of v8 itself.

Honestly, we are probably the experts on this topic now...

const getSourcePosition: (func: Function) => V8SourcePosition =
new Function("func", "return %FunctionGetScriptSourcePosition(func);") as any;

interface V8SourcePosition {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe document these types? I gather that V8SourcePosition is simply an object with identity that can be used to retrieve an associated V8SourceLocation? (Perhaps it has additional state we're simply choosing not to project at the moment?) Another case where linking to docs would be good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do, and yeah, that intuition is correct. No docs, but whatever V8SourcePosition is needs to be passed verbatim to locationFromPosition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting, though, every interface in here is a subset of the full object; I only encoded in the type system the fields that we intend to use here.

@swgillespie
Copy link
Contributor Author

Everything's green so I'll go ahead and merge this per the feedback. Let me know if I should make any changes.

@swgillespie swgillespie merged commit 682f908 into master Apr 10, 2018
@swgillespie swgillespie deleted the swgillespie/pure-javascript-scopechain branch April 10, 2018 17:04
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@lindydonna lindydonna changed the title Implement scope chain free variable lookup in pure TypeScript Remove strict dependency on Node v6.10.x Apr 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/sdks Pulumi language SDKs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants