Skip to content

Commit

Permalink
[BUGFIX yarnpkg#7924] Fix Race in yarn pack stream code
Browse files Browse the repository at this point in the history
In some versions of Node.js (such as 12.16.2), yarn pack no-longer was running the `postpack` hook. Debugging the code in question (following example) lead us to notice that the following await never progressed, the streams `error` and `close` handlers were never invoked. In-fact the process was appearing to exit prematurely.

```js
// src/cli/commands/pack.js

await new Promise((resolve, reject) => {
 stream.pipe(fs2.createWriteStream(filename));
 stream.on(‘error’, reject); // reject is never invoked
 stream.on(‘close’, resolve); //  resolve is never invoked
 // reached
});

// never reached
```

What is going on here?

As it turns out, the above stream code is unsafe, and only appeared
to function do to a specific implementation detail of `zlib.Gzip`. Once that implementation detail changed in Node.js; the process would exit
while awaiting the above promise, and thus would leave the code which triggers the `postpack` hook unreachable.

The facts:

1. a Node.js process exits once it's event queue has been drained.
2. stream.pipe(…) does not add a task to the event queue
3. new Promise (…) does not add a task to the event queue
4. prior to node 2.16, an implementation of `zlib.Gzip` added a task to the event queue
5. nodejs/node@0e89b64 changed that behavior (confirmed via bisect)
6. in node 2.16 (and several other versions) `yarn pack` would exit prior to invoking the `postpack` hook.

Mystery solve!

That's a lot going on, so how can one safely use streams ?

Luckily Node.js has a new utility method (node >= 10) `const { pipeline } = require(‘stream’);`
This higher level utility has less caveats than `stream.pipe` / `stream.on(…)`.
and appears to be preferred in  Node.js's own documentation has been re-worked to use `pipeline` for all but the most specific low level operations.

In short, rather then:
```js
stream.pipe(otherStream);
`"

Under most circumstances it is likely wise to use
```js
const { pipeline } = require(‘stream’);

pipeline(stream, otherStream, err => {
  // node-back
});
`"

And if you are using streams with promises, consider first promisifying `pipeline`

```js
const { promisify } = require(‘util’);
const pipeline = promisify(require(‘stream’).pipeline)
`"
  • Loading branch information
stefanpenner committed Jun 16, 2020
1 parent e5327c5 commit 6b02ee6
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 6 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
"rimraf": "^2.5.0",
"semver": "^5.1.0",
"ssri": "^5.3.0",
"stream.pipeline-shim": "^1.1.0",
"strip-ansi": "^4.0.0",
"strip-bom": "^3.0.0",
"tar-fs": "^1.16.0",
"tar-stream": "^1.6.1",
"util.promisify": "^1.0.1",
"uuid": "^3.0.1",
"v8-compile-cache": "^2.0.0",
"validate-npm-package-license": "^3.0.4",
Expand Down
10 changes: 5 additions & 5 deletions src/cli/commands/pack.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import type {IgnoreFilter} from '../../util/filter.js';
import * as fs from '../../util/fs.js';
import {sortFilter, ignoreLinesToRegex, filterOverridenGitignores} from '../../util/filter.js';
import {MessageError} from '../../errors.js';
import * as promisify from 'util.promisify';
import * as pipelineShim from 'stream.pipeline-shim';

const pipeline = promisify(pipelineShim);

const zlib = require('zlib');
const path = require('path');
Expand Down Expand Up @@ -193,11 +197,7 @@ export async function run(

const stream = await pack(config);

await new Promise((resolve, reject) => {
stream.pipe(fs2.createWriteStream(filename));
stream.on('error', reject);
stream.on('close', resolve);
});
await pipeline(stream, fs2.createWriteStream(filename));

await config.executeLifecycleScript('postpack');

Expand Down
120 changes: 119 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2259,6 +2259,13 @@ define-properties@^1.1.2:
foreach "^2.0.5"
object-keys "^1.0.8"

define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
dependencies:
object-keys "^1.0.12"

define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
Expand Down Expand Up @@ -2484,6 +2491,23 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"

es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
version "1.17.6"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
dependencies:
es-to-primitive "^1.2.1"
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
is-callable "^1.2.0"
is-regex "^1.1.0"
object-inspect "^1.7.0"
object-keys "^1.1.1"
object.assign "^4.1.0"
string.prototype.trimend "^1.0.1"
string.prototype.trimstart "^1.0.1"

es-abstract@^1.5.1, es-abstract@^1.7.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
Expand All @@ -2504,6 +2528,15 @@ es-to-primitive@^1.1.1:
is-date-object "^1.0.1"
is-symbol "^1.0.1"

es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
dependencies:
is-callable "^1.1.4"
is-date-object "^1.0.1"
is-symbol "^1.0.2"

es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2:
version "0.10.45"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.45.tgz#0bfdf7b473da5919d5adf3bd25ceb754fccc3653"
Expand Down Expand Up @@ -3627,6 +3660,11 @@ has-symbols@^1.0.0:
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=

has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==

has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
Expand Down Expand Up @@ -3999,6 +4037,11 @@ is-callable@^1.1.1, is-callable@^1.1.3:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==

is-callable@^1.1.4, is-callable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==

is-ci@^1.0.10:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5"
Expand Down Expand Up @@ -4209,6 +4252,13 @@ is-regex@^1.0.4:
dependencies:
has "^1.0.1"

is-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
dependencies:
has-symbols "^1.0.1"

is-relative@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
Expand All @@ -4231,6 +4281,13 @@ is-symbol@^1.0.1:
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
integrity sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=

is-symbol@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
dependencies:
has-symbols "^1.0.1"

is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
Expand Down Expand Up @@ -5558,11 +5615,21 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"

object-inspect@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==

object-keys@^1.0.11, object-keys@^1.0.8:
version "1.0.12"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==

object-keys@^1.0.12, object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==

object-path@^0.11.2:
version "0.11.4"
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
Expand All @@ -5575,7 +5642,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"

object.assign@^4.0.4:
object.assign@^4.0.4, object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
Expand Down Expand Up @@ -5603,6 +5670,14 @@ object.getownpropertydescriptors@^2.0.3:
define-properties "^1.1.2"
es-abstract "^1.5.1"

object.getownpropertydescriptors@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"

object.map@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37"
Expand Down Expand Up @@ -6987,6 +7062,23 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=

stream.finished@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/stream.finished/-/stream.finished-1.2.0.tgz#40fc76092792d08a43388184fd0d42c6ab9523a0"
integrity sha512-xSp45f/glqd035qAtFUxAGvhotjY/EfqDNV+rQW8o7ffligiOjPaguTEvRzeQAhiQMCdkPEBrp5++S/rQyavWQ==
dependencies:
define-properties "^1.1.3"
function-bind "^1.1.1"

stream.pipeline-shim@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/stream.pipeline-shim/-/stream.pipeline-shim-1.1.0.tgz#70c94a5f9a1ab84951694a2cc108bc7a47000cb5"
integrity sha512-pSi/SZZDbSA5l3YYjSmJadCoD74/qSe79r9ZVR21lD4bpf+khn5Umi6AlfJrD8I0KQfGSqm/7Yp48dmithM+Vw==
dependencies:
define-properties "^1.1.3"
function-bind "^1.1.1"
stream.finished "^1.2.0"

strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
Expand Down Expand Up @@ -7025,6 +7117,22 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"

string.prototype.trimend@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"

string.prototype.trimstart@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.5"

string_decoder@^1.0.0, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
Expand Down Expand Up @@ -7508,6 +7616,16 @@ util.promisify@^1.0.0:
define-properties "^1.1.2"
object.getownpropertydescriptors "^2.0.3"

util.promisify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.2"
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"

util@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
Expand Down

0 comments on commit 6b02ee6

Please sign in to comment.