Skip to content

Commit

Permalink
Merge branch 'main' into remove-bluebird
Browse files Browse the repository at this point in the history
  • Loading branch information
talentlessguy authored Jun 30, 2024
2 parents dd0d197 + 6689289 commit 3918e8a
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 47 deletions.
15 changes: 13 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "@appium/eslint-config-appium",
"extends": ["@appium/eslint-config-appium-ts"],
"overrides": [
{
"files": "test/**/*.js",
Expand All @@ -12,7 +12,18 @@
"files": "lib/**/*.js",
"rules": {
"promise/no-native": "off"
"@typescript-eslint/no-var-requires": "off"
}
},
{
"files": "scripts/**/*",
"parserOptions": {"sourceType": "script"},
"rules": {
"@typescript-eslint/no-var-requires": "off"
}
}
]
],
"rules": {
"require-await": "error"
}
}
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## [2.1.10](https://github.com/appium/node-teen_process/compare/v2.1.9...v2.1.10) (2024-06-29)

### Bug Fixes

* Restore CJS types ([057c009](https://github.com/appium/node-teen_process/commit/057c009e4e350a713c5657fa2b2b666bc69223b6))

## [2.1.9](https://github.com/appium/node-teen_process/compare/v2.1.8...v2.1.9) (2024-06-29)

### Bug Fixes

* Restore CJS ([#438](https://github.com/appium/node-teen_process/issues/438)) ([2ffb0da](https://github.com/appium/node-teen_process/commit/2ffb0da2c71f121f1fb434b1931cbeea99bd98c2))

## [2.1.8](https://github.com/appium/node-teen_process/compare/v2.1.7...v2.1.8) (2024-06-29)

### Miscellaneous Chores

* Optimize buffering logic in exec calls ([#437](https://github.com/appium/node-teen_process/issues/437)) ([775f509](https://github.com/appium/node-teen_process/commit/775f509b8e899c7e6cbbb8225361c9a9d03f241d))

## [2.1.7](https://github.com/appium/node-teen_process/compare/v2.1.6...v2.1.7) (2024-06-29)

### Miscellaneous Chores
Expand Down
60 changes: 60 additions & 0 deletions lib/circular-buffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export const MAX_BUFFER_SIZE = 100 * 1024 * 1024; // 100 MiB
const THRESHOLD = 0.15;

export class CircularBuffer {
private _buf: Buffer[];
private _size: number;
private _maxSize: number;

constructor(maxSize = MAX_BUFFER_SIZE) {
this._maxSize = maxSize;
this._buf = [];
this._size = 0;
}

get size(): number {
return this._size;
}

get count(): number {
return this._buf.length;
}

public add(item: Buffer): this {
this._buf.push(item);
this._size += item.length;
this._align();
return this;
}

public value(): Buffer {
return Buffer.concat(this._buf);
}

private _align(): void {
if (this._size <= this._maxSize) {
return;
}

let numberOfItemsToShift = 0;
// We add the threshold to avoid shifting the array for each `add` call,
// which reduces the CPU usage
const expectedSizeToShift = this._size - this._maxSize + Math.trunc(this._maxSize * THRESHOLD);
let actualShiftedSize = 0;
while (numberOfItemsToShift < this._buf.length - 1 && actualShiftedSize <= expectedSizeToShift) {
actualShiftedSize += this._buf[numberOfItemsToShift].length;
numberOfItemsToShift++;
}
if (numberOfItemsToShift > 0) {
this._buf.splice(0, numberOfItemsToShift);
this._size -= actualShiftedSize;
}
if (actualShiftedSize < expectedSizeToShift) {
// We have already deleted all buffer items, but one,
// although the recent item is still too big to fit into the allowed size limit
const remainderToShift = expectedSizeToShift - actualShiftedSize;
this._buf[0] = this._buf[0].subarray(remainderToShift);
this._size -= remainderToShift;
}
}
}
49 changes: 12 additions & 37 deletions lib/exec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/* eslint-disable promise/prefer-await-to-callbacks */

import { spawn } from 'child_process';
import { quote } from 'shell-quote';
import _ from 'lodash';
import { formatEnoent } from './helpers';

const MAX_BUFFER_SIZE = 100 * 1024 * 1024;
import { CircularBuffer, MAX_BUFFER_SIZE } from './circular-buffer';

/**
* Spawns a process
Expand Down Expand Up @@ -43,10 +40,8 @@ async function exec (cmd, args = [], originalOpts = /** @type {T} */({})) {
// spawn the child process with options; we don't currently expose any of
// the other 'spawn' options through the API
const proc = spawn(cmd, args, {cwd: opts.cwd, env: opts.env, shell: opts.shell});
/** @type {Buffer[]} */
const stdoutArr = [];
/** @type {Buffer[]} */
const stderrArr = [];
const stdoutBuffer = new CircularBuffer(opts.maxStdoutBufferSize);
const stderrBuffer = new CircularBuffer(opts.maxStderrBufferSize);
let timer = null;

// if the process errors out, reject the promise
Expand All @@ -61,7 +56,7 @@ async function exec (cmd, args = [], originalOpts = /** @type {T} */({})) {
reject(new Error(`Standard input '${err.syscall}' error: ${err.stack}`));
});
}
const handleStream = (streamType, streamProps) => {
const handleStream = (/** @type {string} */ streamType, /** @type {CircularBuffer} */ buffer) => {
if (!proc[streamType]) {
return;
}
Expand All @@ -77,47 +72,27 @@ async function exec (cmd, args = [], originalOpts = /** @type {T} */({})) {
}

// keep track of the stream if we don't want to ignore it
const {chunks, maxSize} = streamProps;
let size = 0;
proc[streamType].on('data', (/** @type {Buffer} */ chunk) => {
chunks.push(chunk);
size += chunk.length;
while (chunks.length > 1 && size >= maxSize) {
size -= chunks[0].length;
chunks.shift();
}
buffer.add(chunk);
if (opts.logger && _.isFunction(opts.logger.debug)) {
opts.logger.debug(chunk.toString());
}
});
};
handleStream('stdout', {
maxSize: opts.maxStdoutBufferSize,
chunks: stdoutArr,
});
handleStream('stderr', {
maxSize: opts.maxStderrBufferSize,
chunks: stderrArr,
});
handleStream('stdout', stdoutBuffer);
handleStream('stderr', stderrBuffer);

/**
* @template {boolean} U
* @param {U} isBuffer
* @returns {U extends true ? {stdout: Buffer, stderr: Buffer} : {stdout: string, stderr: string}}
*/
function getStdio (isBuffer) {
/** @type {string | Buffer} */
let stdout;
/** @type {string | Buffer} */
let stderr;
if (isBuffer) {
stdout = Buffer.concat(stdoutArr);
stderr = Buffer.concat(stderrArr);
} else {
stdout = Buffer.concat(stdoutArr).toString(opts.encoding);
stderr = Buffer.concat(stderrArr).toString(opts.encoding);
}
return /** @type {U extends true ? {stdout: Buffer, stderr: Buffer} : {stdout: string, stderr: string}} */({stdout, stderr});
const stdout = isBuffer ? stdoutBuffer.value() : stdoutBuffer.value().toString(opts.encoding);
const stderr = isBuffer ? stderrBuffer.value() : stderrBuffer.value().toString(opts.encoding);
return /** @type {U extends true ? {stdout: Buffer, stderr: Buffer} : {stdout: string, stderr: string}} */(
{stdout, stderr}
);
}

// if the process ends, either resolve or reject the promise based on the
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "teen_process",
"version": "2.1.7",
"version": "2.1.10",
"description": "A grown up version of Node's spawn/exec",
"keywords": [
"child_process",
Expand All @@ -20,7 +20,7 @@
},
"license": "Apache-2.0",
"author": "Appium Contributors",
"main": "./index.js",
"main": "index.js",
"bin": {},
"directories": {
"lib": "lib"
Expand Down Expand Up @@ -52,14 +52,13 @@
"source-map-support": "^0.x"
},
"devDependencies": {
"@appium/eslint-config-appium": "^8.0.5",
"@appium/eslint-config-appium-ts": "^0.x",
"@appium/tsconfig": "^0.x",
"@appium/types": "^0.x",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/chai-as-promised": "^7.1.8",
"@types/lodash": "^4.14.202",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.4",
"@types/shell-quote": "^1.7.5",
"@types/source-map-support": "^0.x",
Expand Down
45 changes: 45 additions & 0 deletions test/circular-buffer-specs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CircularBuffer } from '../lib/circular-buffer';


describe('CircularBuffer', function () {
let chai;

before(async function() {
chai = await import('chai');
const chaiAsPromised = await import('chai-as-promised');

chai.should();
chai.use(chaiAsPromised.default);
});

it('should properly rotate', function () {
const maxSize = 100;
const buffer = new CircularBuffer(maxSize);
buffer.count.should.equal(0);
buffer.size.should.equal(0);
buffer.add(Buffer.from('x'.repeat(maxSize)));
buffer.count.should.equal(1);
buffer.size.should.equal(maxSize);
buffer.value().should.eql(Buffer.from('x'.repeat(maxSize)));
buffer.add(Buffer.from('y'.repeat(maxSize)));
buffer.count.should.equal(1);
buffer.size.should.equal(85);
buffer.value().should.eql(Buffer.from('y'.repeat(85)));
});

it('should properly rotate if the incoming value is too large', function () {
const maxSize = 100;
const buffer = new CircularBuffer(maxSize);
buffer.count.should.equal(0);
buffer.size.should.equal(0);
buffer.add(Buffer.from('x'.repeat(maxSize)));
buffer.count.should.equal(1);
buffer.size.should.equal(maxSize);
buffer.value().should.eql(Buffer.from('x'.repeat(maxSize)));
buffer.add(Buffer.from('y'.repeat(maxSize + 10)));
buffer.count.should.equal(1);
buffer.size.should.equal(85);
buffer.value().should.eql(Buffer.from('y'.repeat(85)));
});

});
1 change: 1 addition & 0 deletions test/subproc-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ describe('SubProcess', function () {
await subproc.start(0);
await delay(50);
lines.should.eql([
'circular-buffer-specs.js',
'exec-specs.js',
'fixtures',
'helpers.js',
Expand Down
9 changes: 5 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"checkJs": true,
"types": ["node", "mocha"]
"types": ["node"],
"checkJs": true
},
"include": ["./lib/**/*", "./test/**/*"],
"exclude": ["test/fixtures/**/*"]
"include": [
"lib"
]
}

0 comments on commit 3918e8a

Please sign in to comment.