From 22020dcf84103456af6c36c46f07f852cec19626 Mon Sep 17 00:00:00 2001 From: Petka Antonov Date: Sat, 13 Feb 2016 16:34:18 +0200 Subject: [PATCH 01/10] Add lock free message queue --- LICENSE | 32 ++++++++++ common.gypi | 7 +- src/producer-consumer-queue.h | 116 ++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 src/producer-consumer-queue.h diff --git a/LICENSE b/LICENSE index f527a1c8b23124..e5fa1f5acd9dc8 100644 --- a/LICENSE +++ b/LICENSE @@ -1056,3 +1056,35 @@ The externally maintained libraries used by Node.js are: ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """ + +- ProducerConsumerQueue, located at src/producer-consumer-queue.h. The folly::ProducerConsumerQueue class is a + one-producer one-consumer queue with very low synchronization overhead. + ProducerConsumerQueue's license follows: + """ + Copyright 2015 Facebook, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + """ + + Significant changes made to the software: + + - Removed Boost dependency + - Removed support for storing values directly + - Removed construction and destruction of the queue items feature + - Added initialization of all values to nullptr + - Made size a template parameter + - Crash instead of throw if malloc fails in constructor + - Changed namespace from folly to node + - Removed sizeGuess(), isFull(), isEmpty(), popFront() and frontPtr() methods + - Renamed write() to PushBack(), read() to PopFront() + - Added padding to fields diff --git a/common.gypi b/common.gypi index 5b8b2c09d6b4a9..efc8bf24fa5428 100644 --- a/common.gypi +++ b/common.gypi @@ -279,7 +279,7 @@ 'libraries': [ '-llog' ], }], ['OS=="mac"', { - 'defines': ['_DARWIN_USE_64_BIT_INODE=1'], + 'defines': ['_DARWIN_USE_64_BIT_INODE=1', 'NODE_OS_MACOSX'], 'xcode_settings': { 'ALWAYS_SEARCH_USER_PATHS': 'NO', 'GCC_CW_ASM_SYNTAX': 'NO', # No -fasm-blocks @@ -290,7 +290,7 @@ 'GCC_ENABLE_PASCAL_STRINGS': 'NO', # No -mpascal-strings 'GCC_THREADSAFE_STATICS': 'NO', # -fno-threadsafe-statics 'PREBINDING': 'NO', # No -Wl,-prebind - 'MACOSX_DEPLOYMENT_TARGET': '10.5', # -mmacosx-version-min=10.5 + 'MACOSX_DEPLOYMENT_TARGET': '10.7', # -mmacosx-version-min=10.7 'USE_HEADERMAP': 'NO', 'OTHER_CFLAGS': [ '-fno-strict-aliasing', @@ -317,7 +317,8 @@ ['clang==1', { 'xcode_settings': { 'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0', - 'CLANG_CXX_LANGUAGE_STANDARD': 'gnu++0x', # -std=gnu++0x + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', # -std=c++11 + 'CLANG_CXX_LIBRARY': 'libc++', #-stdlib=libc++ }, }], ], diff --git a/src/producer-consumer-queue.h b/src/producer-consumer-queue.h new file mode 100644 index 00000000000000..a8223d38623b75 --- /dev/null +++ b/src/producer-consumer-queue.h @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Significant changes made to the software: + * + * - Removed Boost dependency + * - Removed support for storing values directly + * - Removed construction and destruction of the queue items feature + * - Added initialization of all values to nullptr + * - Made size a template parameter + * - Crash instead of throw if malloc fails in constructor + * - Changed namespace from folly to node + * - Removed sizeGuess(), isFull(), isEmpty(), popFront() and frontPtr() methods + * - Renamed write() to PushBack(), read() to PopFront() + * - Added padding to fields + * + */ + +// @author Bo Hu (bhu@fb.com) +// @author Jordan DeLong (delong.j@fb.com) + +#ifndef SRC_PRODUCER_CONSUMER_QUEUE_H_ +#define SRC_PRODUCER_CONSUMER_QUEUE_H_ + +#include "util.h" +#include +#include + +namespace node { + +/* + * ProducerConsumerQueue is a one producer and one consumer queue + * without locks. + */ +template +class ProducerConsumerQueue { + public: + // size must be >= 2. + // + // Also, note that the number of usable slots in the queue at any + // given time is actually (size-1), so if you start with an empty queue, + // PushBack will fail after size-1 insertions. + ProducerConsumerQueue() : readIndex_(0), writeIndex_(0) { + STATIC_ASSERT(size >= 2); + memset(&records_, 0, sizeof(records_[0]) * size); + } + + ~ProducerConsumerQueue() { + while (T* record = PopFront()) + delete record; + } + + // Returns false if the queue is full, cannot insert nullptr + bool PushBack(T* item) { + CHECK_NE(item, nullptr); + auto const currentWrite = writeIndex_.load(std::memory_order_relaxed); + auto nextRecord = currentWrite + 1; + if (nextRecord == size) { + nextRecord = 0; + } + + if (nextRecord != readIndex_.load(std::memory_order_acquire)) { + records_[currentWrite] = item; + writeIndex_.store(nextRecord, std::memory_order_release); + return true; + } + + // queue is full + return false; + } + + // Returns nullptr if the queue is empty. + T* PopFront() { + auto const currentRead = readIndex_.load(std::memory_order_relaxed); + if (currentRead == writeIndex_.load(std::memory_order_acquire)) { + // queue is empty + return nullptr; + } + + auto nextRecord = currentRead + 1; + if (nextRecord == size) { + nextRecord = 0; + } + T* ret = records_[currentRead]; + readIndex_.store(nextRecord, std::memory_order_release); + CHECK_NE(ret, nullptr); + return ret; + } + private: + static const size_t kCacheLineLength = 128; + typedef char padding[kCacheLineLength]; + padding padding1_; + T* records_[size]; + padding padding2_; + std::atomic readIndex_; + padding padding3_; + std::atomic writeIndex_; + padding padding4_; + DISALLOW_COPY_AND_ASSIGN(ProducerConsumerQueue); +}; + +} // namespace node + +#endif // SRC_PRODUCER_CONSUMER_QUEUE_H_ From 682fc683341b65da1c2e2918020487258a0e27a7 Mon Sep 17 00:00:00 2001 From: Petka Antonov Date: Sat, 13 Feb 2016 16:41:42 +0200 Subject: [PATCH 02/10] add worker tests --- Makefile | 6 + test/common.js | 43 +++++- test/parallel/test-util-inspect.js | 5 +- test/workers/test-api.js | 55 ++++++++ test/workers/test-child-process.js | 62 +++++++++ test/workers/test-crypto.js | 40 ++++++ test/workers/test-domain.js | 33 +++++ test/workers/test-error-event.js | 36 ++++++ test/workers/test-eval.js | 22 ++++ test/workers/test-fs.js | 78 +++++++++++ test/workers/test-keep-alive.js | 28 ++++ test/workers/test-messaging-intense.js | 47 +++++++ test/workers/test-process.js | 39 ++++++ test/workers/test-stream.js | 63 +++++++++ test/workers/test-termination-2.js | 24 ++++ test/workers/test-termination-3.js | 35 +++++ test/workers/test-termination-exit-2.js | 38 ++++++ test/workers/test-termination-exit.js | 35 +++++ test/workers/test-termination-grand-parent.js | 56 ++++++++ test/workers/test-termination-owner.js | 33 +++++ test/workers/test-termination-races.js | 122 ++++++++++++++++++ test/workers/test-termination.js | 29 +++++ test/workers/test-unref-2.js | 37 ++++++ test/workers/test-unref.js | 30 +++++ test/workers/test-vm.js | 42 ++++++ test/workers/test-zlib.js | 32 +++++ test/workers/testcfg.py | 6 + tools/test.py | 1 + vcbuild.bat | 1 + 29 files changed, 1074 insertions(+), 4 deletions(-) create mode 100644 test/workers/test-api.js create mode 100644 test/workers/test-child-process.js create mode 100644 test/workers/test-crypto.js create mode 100644 test/workers/test-domain.js create mode 100644 test/workers/test-error-event.js create mode 100644 test/workers/test-eval.js create mode 100644 test/workers/test-fs.js create mode 100644 test/workers/test-keep-alive.js create mode 100644 test/workers/test-messaging-intense.js create mode 100644 test/workers/test-process.js create mode 100644 test/workers/test-stream.js create mode 100644 test/workers/test-termination-2.js create mode 100644 test/workers/test-termination-3.js create mode 100644 test/workers/test-termination-exit-2.js create mode 100644 test/workers/test-termination-exit.js create mode 100644 test/workers/test-termination-grand-parent.js create mode 100644 test/workers/test-termination-owner.js create mode 100644 test/workers/test-termination-races.js create mode 100644 test/workers/test-termination.js create mode 100644 test/workers/test-unref-2.js create mode 100644 test/workers/test-unref.js create mode 100644 test/workers/test-vm.js create mode 100644 test/workers/test-zlib.js create mode 100644 test/workers/testcfg.py diff --git a/Makefile b/Makefile index bcb434d6a5576e..4ada402fffe9be 100644 --- a/Makefile +++ b/Makefile @@ -231,6 +231,12 @@ test-v8-benchmarks: test-v8-all: test-v8 test-v8-intl test-v8-benchmarks # runs all v8 tests +test-workers: all + $(PYTHON) tools/test.py --mode=release workers -J + +test-workers-debug: all + $(PYTHON) tools/test.py --mode=debug workers -J + apidoc_sources = $(wildcard doc/api/*.markdown) apidocs = $(addprefix out/,$(apidoc_sources:.markdown=.html)) \ $(addprefix out/,$(apidoc_sources:.markdown=.json)) diff --git a/test/common.js b/test/common.js index b7c64d2852af13..23bc7875189f00 100644 --- a/test/common.js +++ b/test/common.js @@ -70,9 +70,16 @@ exports.refreshTmpDir = function() { fs.mkdirSync(exports.tmpDir); }; -if (process.env.TEST_THREAD_ID) { - exports.PORT += process.env.TEST_THREAD_ID * 100; - exports.tmpDirName += '.' + process.env.TEST_THREAD_ID; +if ((process.workerData && process.workerData.testThreadId) || + process.env.TEST_THREAD_ID) { + const id = +((process.workerData && process.workerData.testThreadId) || + process.env.TEST_THREAD_ID); + + // Distribute ports in parallel tests + if (!process.env.NODE_COMMON_PORT) + exports.PORT += id * 100; + + exports.tmpDirName += '.' + id; } exports.tmpDir = path.join(testRoot, exports.tmpDirName); @@ -449,6 +456,7 @@ exports.fileExists = function(pathname) { } }; + exports.fail = function(msg) { assert.fail(null, null, msg); }; @@ -504,3 +512,32 @@ exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) { return expectedExitCodes.indexOf(exitCode) > -1; } }; + +exports.runTestInsideWorker = function(testFilePath, data) { + const Worker = require('worker'); + return new Promise(function(resolve, reject) { + var worker = new Worker(testFilePath, { + keepAlive: false, + data: data + }); + worker.on('exit', function(exitCode) { + if (exitCode === 0) + resolve(); + else + reject(new Error(util.format( + '%s exited with code %s', testFile, exitCode))); + }); + + worker.on('error', function(e) { + reject(new Error(util.format( + 'Running %s inside worker failed:\n%s', testFilePath, e.stack))); + }); + }); +}; + +// For using test/common when it's not required. +exports.use = function() {}; + +process.on('unhandledRejection', function(e) { + throw e; +}); diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 57833d65f5bb95..2edb5ec123f500 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -532,7 +532,10 @@ assert.equal(util.inspect(set, true), 'Set { \'foo\', [size]: 1, bar: 42 }'); // test Promise assert.equal(util.inspect(Promise.resolve(3)), 'Promise { 3 }'); -assert.equal(util.inspect(Promise.reject(3)), 'Promise { 3 }'); +var rejectedPromise = Promise.reject(3); +// Handle rejection. +rejectedPromise.catch(function() {}); +assert.equal(util.inspect(rejectedPromise), 'Promise { 3 }'); assert.equal(util.inspect(new Promise(function() {})), 'Promise { }'); var promise = Promise.resolve('foo'); promise.bar = 42; diff --git a/test/workers/test-api.js b/test/workers/test-api.js new file mode 100644 index 00000000000000..d66addb792f645 --- /dev/null +++ b/test/workers/test-api.js @@ -0,0 +1,55 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + try { + Worker(); + } catch (e) {checks++;} + + try { + Worker(1); + } catch (e) {checks++;} + + try { + Worker('str'); + } catch (e) {checks++;} + + try { + new Worker(); + } catch (e) {checks++;} + + try { + new Worker(1); + } catch (e) {checks++;} + + var aWorker = new Worker(__filename, {keepAlive: false}); + + aWorker.postMessage({hello: 'world'}); + aWorker.on('message', function(data) { + assert.deepEqual({hello: 'world'}, data); + checks++; + }); + aWorker.on('exit', function() { + checks++; + try { + aWorker.postMessage([]); + } catch (e) {checks++;} + try { + aWorker.terminate(); + } catch (e) {checks++;} + }); + process.on('beforeExit', function() { + assert.equal(9, checks); + }); +} else { + Worker.on('message', function(data) { + assert.deepEqual({hello: 'world'}, data); + Worker.postMessage({hello: 'world'}); + }); +} diff --git a/test/workers/test-child-process.js b/test/workers/test-child-process.js new file mode 100644 index 00000000000000..7d9521293bbea8 --- /dev/null +++ b/test/workers/test-child-process.js @@ -0,0 +1,62 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +var tests = [ + 'test/parallel/test-child-process-buffering.js', + 'test/parallel/test-child-process-cwd.js', + // Workers cannot mutate environment variables + //'test/parallel/test-child-process-default-options.js', + 'test/parallel/test-child-process-detached.js', + 'test/parallel/test-child-process-disconnect.js', + 'test/parallel/test-child-process-double-pipe.js', + 'test/parallel/test-child-process-env.js', + 'test/parallel/test-child-process-exec-buffer.js', + 'test/parallel/test-child-process-exec-cwd.js', + 'test/parallel/test-child-process-exec-env.js', + 'test/parallel/test-child-process-exec-error.js', + 'test/parallel/test-child-process-exit-code.js', + 'test/parallel/test-child-process-fork3.js', + 'test/parallel/test-child-process-fork-and-spawn.js', + 'test/parallel/test-child-process-fork-close.js', + 'test/parallel/test-child-process-fork-dgram.js', + 'test/parallel/test-child-process-fork-exec-argv.js', + 'test/parallel/test-child-process-fork-exec-path.js', + 'test/parallel/test-child-process-fork.js', + 'test/parallel/test-child-process-fork-net2.js', + 'test/parallel/test-child-process-fork-net.js', + 'test/parallel/test-child-process-fork-ref2.js', + 'test/parallel/test-child-process-fork-ref.js', + 'test/parallel/test-child-process-internal.js', + 'test/parallel/test-child-process-ipc.js', + 'test/parallel/test-child-process-kill.js', + 'test/parallel/test-child-process-recv-handle.js', + 'test/parallel/test-child-process-send-utf8.js', + 'test/parallel/test-child-process-set-blocking.js', + 'test/parallel/test-child-process-silent.js', + 'test/parallel/test-child-process-spawn-error.js', + 'test/parallel/test-child-process-spawnsync-env.js', + 'test/parallel/test-child-process-spawnsync-input.js', + 'test/parallel/test-child-process-spawnsync.js', + 'test/parallel/test-child-process-spawnsync-timeout.js', + 'test/parallel/test-child-process-spawn-typeerror.js', + 'test/parallel/test-child-process-stdin-ipc.js', + 'test/parallel/test-child-process-stdin.js', + 'test/parallel/test-child-process-stdio-big-write-end.js', + 'test/parallel/test-child-process-stdio-inherit.js', + 'test/parallel/test-child-process-stdio.js', + 'test/parallel/test-child-process-stdout-flush-exit.js', + 'test/parallel/test-child-process-stdout-flush.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/test-crypto.js b/test/workers/test-crypto.js new file mode 100644 index 00000000000000..c40cba1d20000a --- /dev/null +++ b/test/workers/test-crypto.js @@ -0,0 +1,40 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +// When using OpenSSL this test should put the locking_callback to good use +var tests = [ + 'test/parallel/test-crypto-authenticated.js', + 'test/parallel/test-crypto-binary-default.js', + 'test/parallel/test-crypto-certificate.js', + 'test/parallel/test-crypto-cipher-decipher.js', + 'test/parallel/test-crypto-dh.js', + 'test/parallel/test-crypto-dh-odd-key.js', + 'test/parallel/test-crypto-domain.js', + 'test/parallel/test-crypto-domains.js', + 'test/parallel/test-crypto-ecb.js', + 'test/parallel/test-crypto-from-binary.js', + 'test/parallel/test-crypto-hash.js', + 'test/parallel/test-crypto-hash-stream-pipe.js', + 'test/parallel/test-crypto-hmac.js', + 'test/parallel/test-crypto.js', + 'test/parallel/test-crypto-padding-aes256.js', + 'test/parallel/test-crypto-padding.js', + 'test/parallel/test-crypto-pbkdf2.js', + 'test/parallel/test-crypto-random.js', + 'test/parallel/test-crypto-rsa-dsa.js', + 'test/parallel/test-crypto-sign-verify.js', + 'test/parallel/test-crypto-stream.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/test-domain.js b/test/workers/test-domain.js new file mode 100644 index 00000000000000..413d50290ed13c --- /dev/null +++ b/test/workers/test-domain.js @@ -0,0 +1,33 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +var tests = [ + 'test/parallel/test-domain-abort-on-uncaught.js', + 'test/parallel/test-domain-crypto.js', + 'test/parallel/test-domain-enter-exit.js', + 'test/parallel/test-domain-exit-dispose-again.js', + 'test/parallel/test-domain-exit-dispose.js', + 'test/parallel/test-domain-from-timer.js', + 'test/parallel/test-domain-http-server.js', + 'test/parallel/test-domain-implicit-fs.js', + 'test/parallel/test-domain.js', + 'test/parallel/test-domain-multi.js', + 'test/parallel/test-domain-nested.js', + 'test/parallel/test-domain-nested-throw.js', + 'test/parallel/test-domain-safe-exit.js', + 'test/parallel/test-domain-stack.js', + 'test/parallel/test-domain-timers.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/test-error-event.js b/test/workers/test-error-event.js new file mode 100644 index 00000000000000..df69d9b7fe6ee2 --- /dev/null +++ b/test/workers/test-error-event.js @@ -0,0 +1,36 @@ +// Flags: --experimental-workers +'use strict'; + +// Uncaught exceptions inside worker bubble to the parent thread's worker +// object's 'error' event. + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename, {keepAlive: false}); + + aWorker.on('error', function(e) { + assert(e instanceof ReferenceError); + assert.equal('ReferenceError: undefinedReference is not defined', + e.toString()); + checks++; + }); + + aWorker.on('message', function(data) { + assert.deepEqual({hello: 'world'}, data); + checks++; + }); + + process.on('beforeExit', function() { + assert.equal(2, checks); + }); +} else { + setTimeout(function() { + Worker.postMessage({hello: 'world'}); + }, 5); + undefinedReference; +} diff --git a/test/workers/test-eval.js b/test/workers/test-eval.js new file mode 100644 index 00000000000000..955f5f11d995b4 --- /dev/null +++ b/test/workers/test-eval.js @@ -0,0 +1,22 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); + +var gotMessage = false; +var a = new Worker('require("worker").postMessage(null);', { + keepAlive: false, + eval: true +}); + +a.on('message', function(data) { + assert.strictEqual(data, null); + gotMessage = true; +}); + +process.on('beforeExit', function() { + assert(gotMessage); +}); diff --git a/test/workers/test-fs.js b/test/workers/test-fs.js new file mode 100644 index 00000000000000..23ff97b5f88f77 --- /dev/null +++ b/test/workers/test-fs.js @@ -0,0 +1,78 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); + +var tests = [ + 'test/parallel/test-domain-implicit-fs.js', + 'test/parallel/test-fs-access.js', + 'test/parallel/test-fs-append-file.js', + 'test/parallel/test-fs-append-file-sync.js', + 'test/parallel/test-fs-chmod.js', + 'test/parallel/test-fs-empty-readStream.js', + 'test/parallel/test-fs-error-messages.js', + 'test/parallel/test-fs-exists.js', + 'test/parallel/test-fs-fsync.js', + 'test/parallel/test-fs-long-path.js', + 'test/parallel/test-fs-make-callback.js', + 'test/parallel/test-fs-mkdir.js', + 'test/parallel/test-fs-non-number-arguments-throw.js', + 'test/parallel/test-fs-null-bytes.js', + 'test/parallel/test-fs-open-flags.js', + 'test/parallel/test-fs-open.js', + 'test/parallel/test-fs-read-buffer.js', + 'test/parallel/test-fs-readfile-empty.js', + 'test/parallel/test-fs-readfile-error.js', + 'test/parallel/test-fs-readfile-pipe.js', + 'test/parallel/test-fs-read-file-sync-hostname.js', + 'test/parallel/test-fs-read-file-sync.js', + 'test/parallel/test-fs-readfile-unlink.js', + 'test/parallel/test-fs-readfile-zero-byte-liar.js', + 'test/parallel/test-fs-read.js', + 'test/parallel/test-fs-read-stream-err.js', + 'test/parallel/test-fs-read-stream-fd.js', + 'test/parallel/test-fs-read-stream-fd-leak.js', + 'test/parallel/test-fs-read-stream-inherit.js', + 'test/parallel/test-fs-read-stream.js', + 'test/parallel/test-fs-read-stream-resume.js', + // Workers cannot chdir. + // 'test/parallel/test-fs-realpath.js', + 'test/parallel/test-fs-sir-writes-alot.js', + 'test/parallel/test-fs-stat.js', + 'test/parallel/test-fs-stream-double-close.js', + 'test/parallel/test-fs-symlink-dir-junction.js', + 'test/parallel/test-fs-symlink-dir-junction-relative.js', + 'test/parallel/test-fs-symlink.js', + 'test/parallel/test-fs-sync-fd-leak.js', + 'test/parallel/test-fs-truncate-fd.js', + 'test/parallel/test-fs-truncate-GH-6233.js', + 'test/parallel/test-fs-truncate.js', + 'test/parallel/test-fs-utimes.js', + 'test/parallel/test-fs-write-buffer.js', + 'test/parallel/test-fs-write-file-buffer.js', + 'test/parallel/test-fs-write-file.js', + // Workers cannot change umask. + // 'test/parallel/test-fs-write-file-sync.js', + 'test/parallel/test-fs-write.js', + 'test/parallel/test-fs-write-stream-change-open.js', + 'test/parallel/test-fs-write-stream-end.js', + 'test/parallel/test-fs-write-stream-err.js', + 'test/parallel/test-fs-write-stream.js', + 'test/parallel/test-fs-write-string-coerce.js', + 'test/parallel/test-fs-write-sync.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + (function(i) { + var shareOfTests = + tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile, {testThreadId: '' + i}); + }); + }); + })(i); +} diff --git a/test/workers/test-keep-alive.js b/test/workers/test-keep-alive.js new file mode 100644 index 00000000000000..b109210d538551 --- /dev/null +++ b/test/workers/test-keep-alive.js @@ -0,0 +1,28 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + aWorker.on('exit', function() { + if (checks === 2) + checks++; + }); + aWorker.on('message', function() { + checks++; + setTimeout(function() { + checks++; + aWorker.terminate(); + }, 5); + }); + process.on('beforeExit', function() { + assert.equal(3, checks); + }); +} else { + Worker.postMessage(); +} diff --git a/test/workers/test-messaging-intense.js b/test/workers/test-messaging-intense.js new file mode 100644 index 00000000000000..28a774003504b6 --- /dev/null +++ b/test/workers/test-messaging-intense.js @@ -0,0 +1,47 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); + +// Test that both primary and backup queue work +if (process.isMainThread) { + const TO_SEND = 4096; + const POST_MORE_THRESHOLD = 1024; + // 2 is the number of messages posted back by the worker for each message + // it receives + const EXPECTED_TO_RECEIVE = ((TO_SEND / POST_MORE_THRESHOLD * 2) - 1) * + POST_MORE_THRESHOLD * 2 + TO_SEND * 2; + + var messagesReceived = 0; + var worker = new Worker(__filename); + worker.on('message', function() { + messagesReceived++; + if ((messagesReceived % POST_MORE_THRESHOLD) === 0 && + messagesReceived < (TO_SEND * 2)) { + var l = POST_MORE_THRESHOLD; + while (l--) + worker.postMessage(); + } + + if (messagesReceived == EXPECTED_TO_RECEIVE) + worker.terminate(); + }); + + var l = TO_SEND; + while (l--) + worker.postMessage(); + + process.on('beforeExit', function() { + assert.equal(EXPECTED_TO_RECEIVE, messagesReceived); + }); +} else { + Worker.on('message', function() { + Worker.postMessage(); + process.nextTick(function() { + Worker.postMessage(); + }); + }); +} diff --git a/test/workers/test-process.js b/test/workers/test-process.js new file mode 100644 index 00000000000000..44586932c06ab2 --- /dev/null +++ b/test/workers/test-process.js @@ -0,0 +1,39 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); + +var tests = [ + 'test/parallel/test-process-argv-0.js', + 'test/parallel/test-process-before-exit.js', + 'test/parallel/test-process-binding.js', + 'test/parallel/test-process-config.js', + // Only main thread can mutate process environment. + // 'test/parallel/test-process-env.js', + 'test/parallel/test-process-exec-argv.js', + 'test/parallel/test-process-exit-code.js', + 'test/parallel/test-process-exit-from-before-exit.js', + 'test/parallel/test-process-exit.js', + 'test/parallel/test-process-exit-recursive.js', + 'test/parallel/test-process-getgroups.js', + 'test/parallel/test-process-hrtime.js', + 'test/parallel/test-process-kill-null.js', + 'test/parallel/test-process-kill-pid.js', + 'test/parallel/test-process-next-tick.js', + 'test/parallel/test-process-raw-debug.js', + 'test/parallel/test-process-remove-all-signal-listeners.js', + 'test/parallel/test-process-versions.js', + 'test/parallel/test-process-wrap.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/test-stream.js b/test/workers/test-stream.js new file mode 100644 index 00000000000000..c5effa94d2779b --- /dev/null +++ b/test/workers/test-stream.js @@ -0,0 +1,63 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); + +var tests = [ + 'test/parallel/test-stream2-base64-single-char-read-end.js', + 'test/parallel/test-stream2-compatibility.js', + 'test/parallel/test-stream2-finish-pipe.js', + 'test/parallel/test-stream2-large-read-stall.js', + 'test/parallel/test-stream2-objects.js', + 'test/parallel/test-stream2-pipe-error-handling.js', + 'test/parallel/test-stream2-pipe-error-once-listener.js', + 'test/parallel/test-stream2-push.js', + 'test/parallel/test-stream2-readable-empty-buffer-no-eof.js', + 'test/parallel/test-stream2-readable-from-list.js', + 'test/parallel/test-stream2-readable-legacy-drain.js', + 'test/parallel/test-stream2-readable-non-empty-end.js', + 'test/parallel/test-stream2-readable-wrap-empty.js', + 'test/parallel/test-stream2-readable-wrap.js', + 'test/parallel/test-stream2-read-sync-stack.js', + 'test/parallel/test-stream2-set-encoding.js', + 'test/parallel/test-stream2-transform.js', + 'test/parallel/test-stream2-unpipe-drain.js', + 'test/parallel/test-stream2-unpipe-leak.js', + 'test/parallel/test-stream2-writable.js', + 'test/parallel/test-stream3-pause-then-read.js', + 'test/parallel/test-stream-big-packet.js', + 'test/parallel/test-stream-big-push.js', + 'test/parallel/test-stream-duplex.js', + 'test/parallel/test-stream-end-paused.js', + 'test/parallel/test-stream-ispaused.js', + 'test/parallel/test-stream-pipe-after-end.js', + 'test/parallel/test-stream-pipe-cleanup.js', + 'test/parallel/test-stream-pipe-error-handling.js', + 'test/parallel/test-stream-pipe-event.js', + 'test/parallel/test-stream-push-order.js', + 'test/parallel/test-stream-push-strings.js', + 'test/parallel/test-stream-readable-constructor-set-methods.js', + 'test/parallel/test-stream-readable-event.js', + 'test/parallel/test-stream-readable-flow-recursion.js', + 'test/parallel/test-stream-transform-constructor-set-methods.js', + 'test/parallel/test-stream-transform-objectmode-falsey-value.js', + 'test/parallel/test-stream-transform-split-objectmode.js', + 'test/parallel/test-stream-unshift-empty-chunk.js', + 'test/parallel/test-stream-unshift-read-race.js', + 'test/parallel/test-stream-writable-change-default-encoding.js', + 'test/parallel/test-stream-writable-constructor-set-methods.js', + 'test/parallel/test-stream-writable-decoded-encoding.js', + 'test/parallel/test-stream-writev.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/test-termination-2.js b/test/workers/test-termination-2.js new file mode 100644 index 00000000000000..3b622c3854357a --- /dev/null +++ b/test/workers/test-termination-2.js @@ -0,0 +1,24 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + aWorker.terminate(function() { + checks++; + }); + aWorker.on('message', function() { + checks++; + }); + process.on('beforeExit', function() { + assert.equal(1, checks); + }); +} else { + while (true) + Worker.postMessage({hello: 'world'}); +} diff --git a/test/workers/test-termination-3.js b/test/workers/test-termination-3.js new file mode 100644 index 00000000000000..5037d46d87727d --- /dev/null +++ b/test/workers/test-termination-3.js @@ -0,0 +1,35 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + var gotMessage = false; + aWorker.on('message', function() { + if (gotMessage) return; + gotMessage = true; + checks++; + aWorker.postMessage(); + aWorker.postMessage(); + aWorker.postMessage(); + aWorker.postMessage(); + aWorker.terminate(function() { + checks++; + }); + }); + process.on('beforeExit', function() { + assert.equal(2, checks); + }); +} else { + Worker.on('message', function() { + while (true) + Worker.postMessage({hello: 'world'}); + }); + + Worker.postMessage(); +} diff --git a/test/workers/test-termination-exit-2.js b/test/workers/test-termination-exit-2.js new file mode 100644 index 00000000000000..a32de24994513a --- /dev/null +++ b/test/workers/test-termination-exit-2.js @@ -0,0 +1,38 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename, {keepAlive: false}); + aWorker.on('exit', function(code) { + assert.equal(1337, code); + checks++; + }); + aWorker.on('message', function(data) { + assert.equal(data, 0); + checks++; + }); + process.on('beforeExit', function() { + assert.equal(2, checks); + }); +} else { + var emits = 0; + process.on('beforeExit', function() { + setInterval(function() { + Worker.postMessage({hello: 'world'}); + }, 5000); + setImmediate(function f() { + Worker.postMessage({hello: 'world'}); + setImmediate(f); + }); + process.exit(1337); + }); + process.on('exit', function() { + Worker.postMessage(emits++); + }); +} diff --git a/test/workers/test-termination-exit.js b/test/workers/test-termination-exit.js new file mode 100644 index 00000000000000..252d90dd16cb3a --- /dev/null +++ b/test/workers/test-termination-exit.js @@ -0,0 +1,35 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + aWorker.on('exit', function(code) { + assert.equal(1337, code); + checks++; + }); + aWorker.on('message', function() { + assert.fail(); + }); + process.on('beforeExit', function() { + assert.equal(1, checks); + }); +} else { + setInterval(function() { + Worker.postMessage({hello: 'world'}); + }, 5000); + setImmediate(function f() { + Worker.postMessage({hello: 'world'}); + setImmediate(f); + }); + (function() { + [1337, 2, 3].map(function(value) { + process.exit(value); + }); + })(); +} diff --git a/test/workers/test-termination-grand-parent.js b/test/workers/test-termination-grand-parent.js new file mode 100644 index 00000000000000..437a7bf3a092c6 --- /dev/null +++ b/test/workers/test-termination-grand-parent.js @@ -0,0 +1,56 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var ids = []; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + aWorker.postMessage({ + init: true, + subWorker: false + }); + aWorker.on('message', function(data) { + ids.push(data.id); + if (ids.length === 4) { + // Terminating the main worker should terminate its 4 sub-workers + aWorker.terminate(); + } + }); + process.on('beforeExit', function() { + assert.deepEqual([0, 1, 2, 3].sort(), ids.sort()); + }); +} else { + Worker.on('message', function(data) { + if (data.init) { + if (data.subWorker) { + subWorker(data.id); + } else { + mainWorker(); + } + } + }); + + var mainWorker = function() { + var l = 4; + while (l--) { + var worker = new Worker(__filename); + worker.postMessage({ + init: true, + subWorker: true, + id: l + }); + worker.on('message', function(payload) { + Worker.postMessage(payload); + }); + } + }; + + var subWorker = function(id) { + Worker.postMessage({id: id}); + while (true); + }; +} diff --git a/test/workers/test-termination-owner.js b/test/workers/test-termination-owner.js new file mode 100644 index 00000000000000..4ee10881284da5 --- /dev/null +++ b/test/workers/test-termination-owner.js @@ -0,0 +1,33 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var Worker = require('worker'); + +// Test that termination of a worker that is in the middle of processing +// messages from its sub-worker works. + +if (process.isMainThread) { + var worker = new Worker(__filename); + worker.postMessage({main: true}); + worker.on('message', function() { + worker.terminate(); + }); +} else { + Worker.on('message', function(data) { + if (data.main) { + var messagesReceived = 0; + var subworker = new Worker(__filename); + subworker.postMessage({main: false}); + subworker.on('message', function() { + messagesReceived++; + + if (messagesReceived === 512) + Worker.postMessage(); + }); + } else { + while (true) Worker.postMessage(); + } + }); +} diff --git a/test/workers/test-termination-races.js b/test/workers/test-termination-races.js new file mode 100644 index 00000000000000..38c3d775f123ef --- /dev/null +++ b/test/workers/test-termination-races.js @@ -0,0 +1,122 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; +var MESSAGES_PER_GRAND_CHILD_WORKER = 20; + + +if (process.isMainThread) { + var l = 4; + var workers = {}; + while (l--) { + var worker = new Worker(__filename); + worker.on('message', function(worker) { + return function(data) { + if (workers[data.id]) return; + checks++; + worker.terminate(); + workers[data.id] = true; + }; + }(worker)); + worker.postMessage({id: l}); + } + process.on('beforeExit', function() { + assert.equal(4, checks); + }); +} else { + Worker.on('message', function(data) { + if (data.id <= 3) { + runImmediateChildWorker(data); + } else { + runGrandChildWorker(data); + } + }); + + var runImmediateChildWorker = function(mainData) { + var messages = {}; + var l = 4; + while (l--) { + var subWorkerId = mainData.id * 4 + 4 + l; + messages[subWorkerId] = 0; + var worker = new Worker(__filename); + worker.on('message', function(data) { + var count = ++messages[data.id]; + if (count === MESSAGES_PER_GRAND_CHILD_WORKER) { + Worker.postMessage({id: mainData.id}); + } + }); + worker.postMessage({id: subWorkerId}); + } + }; + + var runGrandChildWorker = function(data) { + var l = MESSAGES_PER_GRAND_CHILD_WORKER; + process.stdout; + process.stderr; + process.stdin; + try {require('assert');} catch (e) {} + try {require('buffer');} catch (e) {} + try {require('child_process');} catch (e) {} + try {require('cluster');} catch (e) {} + try {require('console');} catch (e) {} + try {require('constants');} catch (e) {} + try {require('crypto');} catch (e) {} + try {require('_debug_agent');} catch (e) {} + try {require('_debugger');} catch (e) {} + try {require('dgram');} catch (e) {} + try {require('dns');} catch (e) {} + try {require('domain');} catch (e) {} + try {require('events');} catch (e) {} + try {require('freelist');} catch (e) {} + try {require('fs');} catch (e) {} + try {require('_http_agent');} catch (e) {} + try {require('_http_client');} catch (e) {} + try {require('_http_common');} catch (e) {} + try {require('_http_incoming');} catch (e) {} + try {require('http');} catch (e) {} + try {require('_http_outgoing');} catch (e) {} + try {require('_http_server');} catch (e) {} + try {require('https');} catch (e) {} + try {require('_linklist');} catch (e) {} + try {require('module');} catch (e) {} + try {require('net');} catch (e) {} + try {require('os');} catch (e) {} + try {require('path');} catch (e) {} + try {require('process');} catch (e) {} + try {require('punycode');} catch (e) {} + try {require('querystring');} catch (e) {} + try {require('readline');} catch (e) {} + try {require('repl');} catch (e) {} + try {require('smalloc');} catch (e) {} + try {require('_stream_duplex');} catch (e) {} + try {require('stream');} catch (e) {} + try {require('_stream_passthrough');} catch (e) {} + try {require('_stream_readable');} catch (e) {} + try {require('_stream_transform');} catch (e) {} + try {require('_stream_wrap');} catch (e) {} + try {require('_stream_writable');} catch (e) {} + try {require('string_decoder');} catch (e) {} + try {require('timers');} catch (e) {} + try {require('_tls_common');} catch (e) {} + try {require('tls');} catch (e) {} + try {require('_tls_legacy');} catch (e) {} + try {require('_tls_wrap');} catch (e) {} + try {require('tty');} catch (e) {} + try {require('url');} catch (e) {} + try {require('util');} catch (e) {} + try {require('v8');} catch (e) {} + try {require('vm');} catch (e) {} + try {require('worker');} catch (e) {} + try {require('zlib');} catch (e) {} + while (l--) { + Worker.postMessage({ + id: data.id + }); + } + }; + +} diff --git a/test/workers/test-termination.js b/test/workers/test-termination.js new file mode 100644 index 00000000000000..a2b0dac82c12d0 --- /dev/null +++ b/test/workers/test-termination.js @@ -0,0 +1,29 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + aWorker.terminate(function() { + checks++; + }); + aWorker.on('message', function() { + assert.fail(); + }); + process.on('beforeExit', function() { + assert.equal(1, checks); + }); +} else { + setInterval(function() { + Worker.postMessage({hello: 'world'}); + }, 5000); + setImmediate(function f() { + Worker.postMessage({hello: 'world'}); + setImmediate(f); + }); +} diff --git a/test/workers/test-unref-2.js b/test/workers/test-unref-2.js new file mode 100644 index 00000000000000..65828e4ed7179d --- /dev/null +++ b/test/workers/test-unref-2.js @@ -0,0 +1,37 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var timer = setInterval(function() {}, 1000); + var aWorker = new Worker(__filename); + aWorker.on('exit', function() { + checks++; + }); + aWorker.on('message', function() { + checks++; + setTimeout(function() { + checks++; + aWorker.terminate(function() { + checks++; + clearInterval(timer); + }); + }, 5); + }); + process.on('beforeExit', function() { + assert.equal(4, checks); + }); + aWorker.unref(); + aWorker.postMessage(); +} else { + Worker.on('message', function() { + setTimeout(function() { + Worker.postMessage(); + }, 1); + }); +} diff --git a/test/workers/test-unref.js b/test/workers/test-unref.js new file mode 100644 index 00000000000000..dbe1c3ed79ab3b --- /dev/null +++ b/test/workers/test-unref.js @@ -0,0 +1,30 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +common.use(); +var assert = require('assert'); +var Worker = require('worker'); +var checks = 0; + +if (process.isMainThread) { + var aWorker = new Worker(__filename); + aWorker.on('exit', function() { + checks++; + }); + aWorker.on('message', function() { + checks++; + setTimeout(function() { + checks++; + aWorker.terminate(); + }, 5); + }); + process.on('beforeExit', function() { + assert.equal(0, checks); + }); + aWorker.unref(); +} else { + setInterval(function() { + Worker.postMessage(); + }, 5); +} diff --git a/test/workers/test-vm.js b/test/workers/test-vm.js new file mode 100644 index 00000000000000..d7d3f75536a88e --- /dev/null +++ b/test/workers/test-vm.js @@ -0,0 +1,42 @@ +// Flags: --experimental-workers --harmony_proxies +'use strict'; + +var common = require('../common'); + +var tests = [ + 'test/parallel/test-vm-basic.js', + 'test/parallel/test-vm-context-async-script.js', + 'test/parallel/test-vm-context.js', + 'test/parallel/test-vm-context-property-forwarding.js', + 'test/parallel/test-vm-create-and-run-in-context.js', + 'test/parallel/test-vm-create-context-accessors.js', + 'test/parallel/test-vm-create-context-arg.js', + 'test/parallel/test-vm-create-context-circular-reference.js', + 'test/parallel/test-vm-cross-context.js', + 'test/parallel/test-vm-debug-context.js', + 'test/parallel/test-vm-function-declaration.js', + 'test/parallel/test-vm-global-define-property.js', + 'test/parallel/test-vm-global-identity.js', + 'test/parallel/test-vm-harmony-proxies.js', + 'test/parallel/test-vm-harmony-symbols.js', + 'test/parallel/test-vm-is-context.js', + 'test/parallel/test-vm-new-script-new-context.js', + 'test/parallel/test-vm-new-script-this-context.js', + // Needs --expose-gc but exposing it for the process causes other tests + // to fail + // 'test/parallel/test-vm-run-in-new-context.js', + 'test/parallel/test-vm-static-this.js', + 'test/parallel/test-vm-timeout.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/test-zlib.js b/test/workers/test-zlib.js new file mode 100644 index 00000000000000..18ca3d7bf07662 --- /dev/null +++ b/test/workers/test-zlib.js @@ -0,0 +1,32 @@ +// Flags: --experimental-workers +'use strict'; + +var common = require('../common'); +var tests = [ + 'test/parallel/test-zlib-close-after-write.js', + 'test/parallel/test-zlib-convenience-methods.js', + 'test/parallel/test-zlib-dictionary-fail.js', + 'test/parallel/test-zlib-dictionary.js', + 'test/parallel/test-zlib-flush.js', + 'test/parallel/test-zlib-from-gzip.js', + 'test/parallel/test-zlib-from-string.js', + 'test/parallel/test-zlib-invalid-input.js', + 'test/parallel/test-zlib.js', + 'test/parallel/test-zlib-params.js', + 'test/parallel/test-zlib-random-byte-pipes.js', + 'test/parallel/test-zlib-write-after-close.js', + 'test/parallel/test-zlib-write-after-flush.js', + 'test/parallel/test-zlib-zero-byte.js' +]; + +var parallelism = 4; +var testsPerThread = Math.ceil(tests.length / parallelism); +for (var i = 0; i < parallelism; ++i) { + var shareOfTests = tests.slice(i * testsPerThread, (i + 1) * testsPerThread); + var cur = Promise.resolve(); + shareOfTests.forEach(function(testFile) { + cur = cur.then(function() { + return common.runTestInsideWorker(testFile); + }); + }); +} diff --git a/test/workers/testcfg.py b/test/workers/testcfg.py new file mode 100644 index 00000000000000..9b0ca8a0438bda --- /dev/null +++ b/test/workers/testcfg.py @@ -0,0 +1,6 @@ +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import testpy + +def GetConfiguration(context, root): + return testpy.SimpleTestConfiguration(context, root, 'workers') diff --git a/tools/test.py b/tools/test.py index 217e72b870a6f8..a4912aa7cb3c3e 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1415,6 +1415,7 @@ def ExpandCommand(args): 'addons', 'gc', 'debugger', + 'workers' ] diff --git a/vcbuild.bat b/vcbuild.bat index 8b2c67b50fe155..93e6116bd553db 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -61,6 +61,7 @@ if /i "%1"=="test-message" set test_args=%test_args% message&goto arg-ok if /i "%1"=="test-gc" set test_args=%test_args% gc&set buildnodeweak=1&goto arg-ok if /i "%1"=="test-internet" set test_args=%test_args% internet&goto arg-ok if /i "%1"=="test-pummel" set test_args=%test_args% pummel&goto arg-ok +if /i "%1"=="test-workers" set test_args=%test_args% workers&goto arg-ok if /i "%1"=="test-all" set test_args=%test_args% sequential parallel message gc internet pummel&set buildnodeweak=1&set jslint=1&goto arg-ok if /i "%1"=="jslint" set jslint=1&goto arg-ok if /i "%1"=="msi" set msi=1&set licensertf=1&set download_arg="--download=all"&set i18n_arg=small-icu&goto arg-ok From 3910d0dad732a3ec820e6d71eb086891c6448d61 Mon Sep 17 00:00:00 2001 From: Petka Antonov Date: Sat, 13 Feb 2016 16:42:43 +0200 Subject: [PATCH 03/10] Extract ContextifyScript to a header --- node.gyp | 1 + src/node-contextify.h | 59 ++++ src/node_contextify.cc | 640 +++++++++++++++++++++-------------------- 3 files changed, 383 insertions(+), 317 deletions(-) create mode 100644 src/node-contextify.h diff --git a/node.gyp b/node.gyp index dc525e1421333d..007afbce8505ca 100644 --- a/node.gyp +++ b/node.gyp @@ -164,6 +164,7 @@ 'src/node.h', 'src/node_buffer.h', 'src/node_constants.h', + 'src/node-contextify.h', 'src/node_file.h', 'src/node_http_parser.h', 'src/node_internals.h', diff --git a/src/node-contextify.h b/src/node-contextify.h new file mode 100644 index 00000000000000..7e7036bb043589 --- /dev/null +++ b/src/node-contextify.h @@ -0,0 +1,59 @@ +#ifndef SRC_NODE_CONTEXTIFY_H_ +#define SRC_NODE_CONTEXTIFY_H_ + +#include "base-object.h" +#include "env.h" +#include "v8.h" + +namespace node { + +class ContextifyScript : public BaseObject { + public: + ContextifyScript(Environment* env, v8::Local object); + ~ContextifyScript() override; + + void Dispose(); + static void Init(Environment* env, v8::Local target); + // args: code, [options] + static void New(const v8::FunctionCallbackInfo& args); + static bool InstanceOf(Environment* env, const v8::Local& value); + // args: [options] + static void RunInThisContext( + const v8::FunctionCallbackInfo& args); + // args: sandbox, [options] + static void RunInContext(const v8::FunctionCallbackInfo& args); + static int64_t GetTimeoutArg( + const v8::FunctionCallbackInfo& args, const int i); + static bool GetDisplayErrorsArg( + const v8::FunctionCallbackInfo& args, const int i); + static void DecorateErrorStack(Environment* env, + const v8::TryCatch& try_catch); + static v8::Local GetFilenameArg( + const v8::FunctionCallbackInfo& args, const int i); + static v8::MaybeLocal GetCachedData( + Environment* env, + const v8::FunctionCallbackInfo& args, + const int i); + static v8::Local GetLineOffsetArg( + const v8::FunctionCallbackInfo& args, + const int i); + static v8::Local GetColumnOffsetArg( + const v8::FunctionCallbackInfo& args, + const int i); + static bool GetProduceCachedData( + Environment* env, + const v8::FunctionCallbackInfo& args, + const int i); + + static bool EvalMachine(Environment* env, + const int64_t timeout, + const bool display_errors, + const v8::FunctionCallbackInfo& args, + v8::TryCatch& try_catch); + private: + v8::Persistent script_; +}; + +} // namespace node + +#endif // SRC_NODE_CONTEXTIFY_H_ diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 900b6b922df2be..6ed83711a4b5cb 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -1,3 +1,5 @@ +#include "node-contextify.h" + #include "node.h" #include "node_internals.h" #include "node_watchdog.h" @@ -461,407 +463,411 @@ class ContextifyContext { } }; -class ContextifyScript : public BaseObject { - private: - Persistent script_; - public: - static void Init(Environment* env, Local target) { - HandleScope scope(env->isolate()); - Local class_name = - FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); +void ContextifyScript::Init(Environment* env, Local target) { + HandleScope scope(env->isolate()); + Local class_name = + FIXED_ONE_BYTE_STRING(env->isolate(), "ContextifyScript"); - Local script_tmpl = env->NewFunctionTemplate(New); - script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); - script_tmpl->SetClassName(class_name); - env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); - env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); + Local script_tmpl = env->NewFunctionTemplate(New); + script_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + script_tmpl->SetClassName(class_name); + env->SetProtoMethod(script_tmpl, "runInContext", RunInContext); + env->SetProtoMethod(script_tmpl, "runInThisContext", RunInThisContext); - target->Set(class_name, script_tmpl->GetFunction()); - env->set_script_context_constructor_template(script_tmpl); - } + target->Set(class_name, script_tmpl->GetFunction()); + env->set_script_context_constructor_template(script_tmpl); +} // args: code, [options] - static void New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); +void ContextifyScript::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (!args.IsConstructCall()) { + return env->ThrowError("Must call vm.Script as a constructor."); + } + + ContextifyScript* contextify_script = + new ContextifyScript(env, args.This()); + contextify_script->persistent().SetWrapperClassId( + ClassId::CONTEXTIFY_SCRIPT); + + TryCatch try_catch; + Local code = args[0]->ToString(env->isolate()); + Local filename = GetFilenameArg(args, 1); + Local lineOffset = GetLineOffsetArg(args, 1); + Local columnOffset = GetColumnOffsetArg(args, 1); + bool display_errors = GetDisplayErrorsArg(args, 1); + MaybeLocal cached_data_buf = GetCachedData(env, args, 1); + bool produce_cached_data = GetProduceCachedData(env, args, 1); + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return; + } + + ScriptCompiler::CachedData* cached_data = nullptr; + if (!cached_data_buf.IsEmpty()) { + Local ui8 = cached_data_buf.ToLocalChecked(); + ArrayBuffer::Contents contents = ui8->Buffer()->GetContents(); + cached_data = new ScriptCompiler::CachedData( + static_cast(contents.Data()) + ui8->ByteOffset(), + ui8->ByteLength()); + } + + ScriptOrigin origin(filename, lineOffset, columnOffset); + ScriptCompiler::Source source(code, origin, cached_data); + ScriptCompiler::CompileOptions compile_options = + ScriptCompiler::kNoCompileOptions; + + if (source.GetCachedData() != nullptr) + compile_options = ScriptCompiler::kConsumeCodeCache; + else if (produce_cached_data) + compile_options = ScriptCompiler::kProduceCodeCache; + + Local v8_script = ScriptCompiler::CompileUnbound( + env->isolate(), + &source, + compile_options); + + if (v8_script.IsEmpty()) { + if (display_errors) { + DecorateErrorStack(env, try_catch); + } + try_catch.ReThrow(); + return; + } + contextify_script->script_.Reset(env->isolate(), v8_script); + + if (compile_options == ScriptCompiler::kConsumeCodeCache) { + args.This()->Set( + env->cached_data_rejected_string(), + Boolean::New(env->isolate(), source.GetCachedData()->rejected)); + } else if (compile_options == ScriptCompiler::kProduceCodeCache) { + const ScriptCompiler::CachedData* cached_data = source.GetCachedData(); + MaybeLocal buf = Buffer::Copy( + env, + reinterpret_cast(cached_data->data), + cached_data->length); + args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); + } +} - if (!args.IsConstructCall()) { - return env->ThrowError("Must call vm.Script as a constructor."); - } - ContextifyScript* contextify_script = - new ContextifyScript(env, args.This()); +bool ContextifyScript::InstanceOf(Environment* env, const Local& value) { + return !value.IsEmpty() && + env->script_context_constructor_template()->HasInstance(value); +} - TryCatch try_catch; - Local code = args[0]->ToString(env->isolate()); - Local filename = GetFilenameArg(args, 1); - Local lineOffset = GetLineOffsetArg(args, 1); - Local columnOffset = GetColumnOffsetArg(args, 1); - bool display_errors = GetDisplayErrorsArg(args, 1); - MaybeLocal cached_data_buf = GetCachedData(env, args, 1); - bool produce_cached_data = GetProduceCachedData(env, args, 1); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } - ScriptCompiler::CachedData* cached_data = nullptr; - if (!cached_data_buf.IsEmpty()) { - Local ui8 = cached_data_buf.ToLocalChecked(); - ArrayBuffer::Contents contents = ui8->Buffer()->GetContents(); - cached_data = new ScriptCompiler::CachedData( - static_cast(contents.Data()) + ui8->ByteOffset(), - ui8->ByteLength()); - } + // args: [options] +void ContextifyScript::RunInThisContext( + const FunctionCallbackInfo& args) { + // Assemble arguments + TryCatch try_catch; + uint64_t timeout = GetTimeoutArg(args, 0); + bool display_errors = GetDisplayErrorsArg(args, 0); + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return; + } + + // Do the eval within this context + Environment* env = Environment::GetCurrent(args); + EvalMachine(env, timeout, display_errors, args, try_catch); +} - ScriptOrigin origin(filename, lineOffset, columnOffset); - ScriptCompiler::Source source(code, origin, cached_data); - ScriptCompiler::CompileOptions compile_options = - ScriptCompiler::kNoCompileOptions; + // args: sandbox, [options] +void ContextifyScript::RunInContext(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); - if (source.GetCachedData() != nullptr) - compile_options = ScriptCompiler::kConsumeCodeCache; - else if (produce_cached_data) - compile_options = ScriptCompiler::kProduceCodeCache; + int64_t timeout; + bool display_errors; - Local v8_script = ScriptCompiler::CompileUnbound( - env->isolate(), - &source, - compile_options); + // Assemble arguments + if (!args[0]->IsObject()) { + return env->ThrowTypeError( + "contextifiedSandbox argument must be an object."); + } - if (v8_script.IsEmpty()) { - if (display_errors) { - DecorateErrorStack(env, try_catch); - } + Local sandbox = args[0].As(); + { + TryCatch try_catch; + timeout = GetTimeoutArg(args, 1); + display_errors = GetDisplayErrorsArg(args, 1); + if (try_catch.HasCaught()) { try_catch.ReThrow(); return; } - contextify_script->script_.Reset(env->isolate(), v8_script); - - if (compile_options == ScriptCompiler::kConsumeCodeCache) { - args.This()->Set( - env->cached_data_rejected_string(), - Boolean::New(env->isolate(), source.GetCachedData()->rejected)); - } else if (compile_options == ScriptCompiler::kProduceCodeCache) { - const ScriptCompiler::CachedData* cached_data = source.GetCachedData(); - MaybeLocal buf = Buffer::Copy( - env, - reinterpret_cast(cached_data->data), - cached_data->length); - args.This()->Set(env->cached_data_string(), buf.ToLocalChecked()); - } } - - static bool InstanceOf(Environment* env, const Local& value) { - return !value.IsEmpty() && - env->script_context_constructor_template()->HasInstance(value); + // Get the context from the sandbox + ContextifyContext* contextify_context = + ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); + if (contextify_context == nullptr) { + return env->ThrowTypeError( + "sandbox argument must have been converted to a context."); } + if (contextify_context->context().IsEmpty()) + return; - // args: [options] - static void RunInThisContext(const FunctionCallbackInfo& args) { - // Assemble arguments + { TryCatch try_catch; - uint64_t timeout = GetTimeoutArg(args, 0); - bool display_errors = GetDisplayErrorsArg(args, 0); + // Do the eval within the context + Context::Scope context_scope(contextify_context->context()); + if (EvalMachine(contextify_context->env(), + timeout, + display_errors, + args, + try_catch)) { + contextify_context->CopyProperties(); + } + if (try_catch.HasCaught()) { try_catch.ReThrow(); return; } - - // Do the eval within this context - Environment* env = Environment::GetCurrent(args); - EvalMachine(env, timeout, display_errors, args, try_catch); } +} - // args: sandbox, [options] - static void RunInContext(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - int64_t timeout; - bool display_errors; - - // Assemble arguments - if (!args[0]->IsObject()) { - return env->ThrowTypeError( - "contextifiedSandbox argument must be an object."); - } +void ContextifyScript::DecorateErrorStack(Environment* env, + const TryCatch& try_catch) { + Local exception = try_catch.Exception(); - Local sandbox = args[0].As(); - { - TryCatch try_catch; - timeout = GetTimeoutArg(args, 1); - display_errors = GetDisplayErrorsArg(args, 1); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } - } + if (!exception->IsObject()) + return; - // Get the context from the sandbox - ContextifyContext* contextify_context = - ContextifyContext::ContextFromContextifiedSandbox(env, sandbox); - if (contextify_context == nullptr) { - return env->ThrowTypeError( - "sandbox argument must have been converted to a context."); - } + Local err_obj = exception.As(); - if (contextify_context->context().IsEmpty()) - return; + if (IsExceptionDecorated(env, err_obj)) + return; - { - TryCatch try_catch; - // Do the eval within the context - Context::Scope context_scope(contextify_context->context()); - if (EvalMachine(contextify_context->env(), - timeout, - display_errors, - args, - try_catch)) { - contextify_context->CopyProperties(); - } + AppendExceptionLine(env, exception, try_catch.Message()); + Local stack = err_obj->Get(env->stack_string()); + auto maybe_value = + err_obj->GetPrivate( + env->context(), + env->arrow_message_private_symbol()); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } - } + Local arrow; + if (!(maybe_value.ToLocal(&arrow) && + arrow->IsString() && + stack->IsString())) { + return; } - static void DecorateErrorStack(Environment* env, const TryCatch& try_catch) { - Local exception = try_catch.Exception(); - - if (!exception->IsObject()) - return; - - Local err_obj = exception.As(); - - if (IsExceptionDecorated(env, err_obj)) - return; - - AppendExceptionLine(env, exception, try_catch.Message()); - Local stack = err_obj->Get(env->stack_string()); - auto maybe_value = - err_obj->GetPrivate( - env->context(), - env->arrow_message_private_symbol()); + Local decorated_stack = String::Concat(arrow.As(), + stack.As()); + err_obj->Set(env->stack_string(), decorated_stack); + err_obj->SetPrivate( + env->context(), + env->decorated_private_symbol(), + True(env->isolate())); +} - Local arrow; - if (!(maybe_value.ToLocal(&arrow) && - arrow->IsString() && - stack->IsString())) { - return; - } +int64_t ContextifyScript::GetTimeoutArg(const FunctionCallbackInfo& args, + const int i) { + if (args[i]->IsUndefined() || args[i]->IsString()) { + return -1; + } + if (!args[i]->IsObject()) { + Environment::ThrowTypeError(args.GetIsolate(), + "options must be an object"); + return -1; + } - Local decorated_stack = String::Concat(arrow.As(), - stack.As()); - err_obj->Set(env->stack_string(), decorated_stack); - err_obj->SetPrivate( - env->context(), - env->decorated_private_symbol(), - True(env->isolate())); + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "timeout"); + Local value = args[i].As()->Get(key); + if (value->IsUndefined()) { + return -1; } + int64_t timeout = value->IntegerValue(); - static int64_t GetTimeoutArg(const FunctionCallbackInfo& args, - const int i) { - if (args[i]->IsUndefined() || args[i]->IsString()) { - return -1; - } - if (!args[i]->IsObject()) { - Environment::ThrowTypeError(args.GetIsolate(), - "options must be an object"); - return -1; - } + if (timeout <= 0) { + Environment::ThrowRangeError(args.GetIsolate(), + "timeout must be a positive number"); + return -1; + } + return timeout; +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "timeout"); - Local value = args[i].As()->Get(key); - if (value->IsUndefined()) { - return -1; - } - int64_t timeout = value->IntegerValue(); - if (timeout <= 0) { - Environment::ThrowRangeError(args.GetIsolate(), - "timeout must be a positive number"); - return -1; - } - return timeout; +bool ContextifyScript::GetDisplayErrorsArg( + const FunctionCallbackInfo& args, const int i) { + if (args[i]->IsUndefined() || args[i]->IsString()) { + return true; + } + if (!args[i]->IsObject()) { + Environment::ThrowTypeError(args.GetIsolate(), + "options must be an object"); + return false; } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), + "displayErrors"); + Local value = args[i].As()->Get(key); - static bool GetDisplayErrorsArg(const FunctionCallbackInfo& args, - const int i) { - if (args[i]->IsUndefined() || args[i]->IsString()) { - return true; - } - if (!args[i]->IsObject()) { - Environment::ThrowTypeError(args.GetIsolate(), - "options must be an object"); - return false; - } + return value->IsUndefined() ? true : value->BooleanValue(); +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), - "displayErrors"); - Local value = args[i].As()->Get(key); - return value->IsUndefined() ? true : value->BooleanValue(); - } +Local ContextifyScript::GetFilenameArg( + const FunctionCallbackInfo& args, const int i) { + Local defaultFilename = + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine."); + if (args[i]->IsUndefined()) { + return defaultFilename; + } + if (args[i]->IsString()) { + return args[i].As(); + } + if (!args[i]->IsObject()) { + Environment::ThrowTypeError(args.GetIsolate(), + "options must be an object"); + return Local(); + } - static Local GetFilenameArg(const FunctionCallbackInfo& args, - const int i) { - Local defaultFilename = - FIXED_ONE_BYTE_STRING(args.GetIsolate(), "evalmachine."); + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename"); + Local value = args[i].As()->Get(key); - if (args[i]->IsUndefined()) { - return defaultFilename; - } - if (args[i]->IsString()) { - return args[i].As(); - } - if (!args[i]->IsObject()) { - Environment::ThrowTypeError(args.GetIsolate(), - "options must be an object"); - return Local(); - } + if (value->IsUndefined()) + return defaultFilename; + return value->ToString(args.GetIsolate()); +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "filename"); - Local value = args[i].As()->Get(key); - if (value->IsUndefined()) - return defaultFilename; - return value->ToString(args.GetIsolate()); +MaybeLocal ContextifyScript::GetCachedData( + Environment* env, + const FunctionCallbackInfo& args, + const int i) { + if (!args[i]->IsObject()) { + return MaybeLocal(); + } + Local value = args[i].As()->Get(env->cached_data_string()); + if (value->IsUndefined()) { + return MaybeLocal(); } + if (!value->IsUint8Array()) { + Environment::ThrowTypeError( + args.GetIsolate(), + "options.cachedData must be a Buffer instance"); + return MaybeLocal(); + } - static MaybeLocal GetCachedData( - Environment* env, - const FunctionCallbackInfo& args, - const int i) { - if (!args[i]->IsObject()) { - return MaybeLocal(); - } - Local value = args[i].As()->Get(env->cached_data_string()); - if (value->IsUndefined()) { - return MaybeLocal(); - } + return value.As(); +} - if (!value->IsUint8Array()) { - Environment::ThrowTypeError( - args.GetIsolate(), - "options.cachedData must be a Buffer instance"); - return MaybeLocal(); - } - return value.As(); +bool ContextifyScript::GetProduceCachedData( + Environment* env, + const FunctionCallbackInfo& args, + const int i) { + if (!args[i]->IsObject()) { + return false; } + Local value = + args[i].As()->Get(env->produce_cached_data_string()); + return value->IsTrue(); +} - static bool GetProduceCachedData( - Environment* env, - const FunctionCallbackInfo& args, - const int i) { - if (!args[i]->IsObject()) { - return false; - } - Local value = - args[i].As()->Get(env->produce_cached_data_string()); - return value->IsTrue(); +Local ContextifyScript::GetLineOffsetArg( + const FunctionCallbackInfo& args, + const int i) { + Local defaultLineOffset = Integer::New(args.GetIsolate(), 0); + + if (!args[i]->IsObject()) { + return defaultLineOffset; } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "lineOffset"); + Local value = args[i].As()->Get(key); - static Local GetLineOffsetArg( - const FunctionCallbackInfo& args, - const int i) { - Local defaultLineOffset = Integer::New(args.GetIsolate(), 0); + return value->IsUndefined() ? defaultLineOffset : value->ToInteger(); +} - if (!args[i]->IsObject()) { - return defaultLineOffset; - } - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), "lineOffset"); - Local value = args[i].As()->Get(key); +Local ContextifyScript::GetColumnOffsetArg( + const FunctionCallbackInfo& args, + const int i) { + Local defaultColumnOffset = Integer::New(args.GetIsolate(), 0); - return value->IsUndefined() ? defaultLineOffset : value->ToInteger(); + if (!args[i]->IsObject()) { + return defaultColumnOffset; } + Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), + "columnOffset"); + Local value = args[i].As()->Get(key); - static Local GetColumnOffsetArg( - const FunctionCallbackInfo& args, - const int i) { - Local defaultColumnOffset = Integer::New(args.GetIsolate(), 0); - - if (!args[i]->IsObject()) { - return defaultColumnOffset; - } + return value->IsUndefined() ? defaultColumnOffset : value->ToInteger(); +} - Local key = FIXED_ONE_BYTE_STRING(args.GetIsolate(), - "columnOffset"); - Local value = args[i].As()->Get(key); - return value->IsUndefined() ? defaultColumnOffset : value->ToInteger(); +bool ContextifyScript::EvalMachine(Environment* env, + const int64_t timeout, + const bool display_errors, + const FunctionCallbackInfo& args, + TryCatch& try_catch) { + if (!env->CanCallIntoJs()) + return false; + if (!ContextifyScript::InstanceOf(env, args.Holder())) { + env->ThrowTypeError( + "Script methods can only be called on script instances."); + return false; } + ContextifyScript* wrapped_script = Unwrap(args.Holder()); + Local unbound_script = + PersistentToLocal(env->isolate(), wrapped_script->script_); + Local