Skip to content

Commit

Permalink
feat: add more env variables support (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
JounQin committed Jul 11, 2021
1 parent 09f553d commit 2809da0
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 80 deletions.
5 changes: 5 additions & 0 deletions .changeset/poor-moles-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"synckit": minor
---

feat: add more env variables support
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Perform async work synchronously in Node.js using `worker_threads`, or `child_pr
- [Usage](#usage)
- [Install](#install)
- [API](#api)
- [Env variables](#env-variables)
- [TypeScript](#typescript)
- [Benchmark](#benchmark)
- [Changelog](#changelog)
Expand Down Expand Up @@ -70,6 +71,12 @@ You must make sure:
1. if `worker_threads` is enabled (by default), the `result` is serialized by [`Structured Clone Algorithm`](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
2. if `child_process` is used, the `result` is serialized by `JSON.stringify`

### Env variables

1. `SYNCKIT_WORKER_THREADS`: whether or not enable `worker_threads`, it's enabled by default, set as `0` to disable
2. `SYNCKIT_BUFFER_SIZE`: `bufferSize` to create `SharedArrayBuffer` for `worker_threads` (default as `1024`), or `maxBuffer` for `child_process` (no default)
3. `SYNCKIT_TIMEOUT`: `timeout` for performing the async job (no default)

### TypeScript

If you want to use `ts-node` for worker file (a `.ts` file), it is supported out of box!
Expand All @@ -80,7 +87,7 @@ If you want to integrate with [tsconfig-paths](https://www.npmjs.com/package/tsc

## Benchmark

It is about 20x faster than [`sync-threads`](https://github.com/lambci/sync-threads) but 3x slower than native for reading the file content itself 1000 times during runtime. See [Benchmark](./benchmarks/benchmark.txt) for more details.
It is about 20x faster than [`sync-threads`](https://github.com/lambci/sync-threads) but 3x slower than native for reading the file content itself 1000 times during runtime, and 18x faster than `sync-threads` but 4x slower than native for total time. See [Benchmark](./benchmarks/benchmark.txt) for more details.

You can try it with running `yarn benchmark` by yourself. [Here](./benchmarks/benchmark.js) is the benchmark source code.

Expand Down
69 changes: 37 additions & 32 deletions benchmarks/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,38 @@ const syncFn3 = require('./native')

const nativeLoadTime = performance.now() - nativeLoadStartTime

const RUN_TIMES = +process.env.RUN_TIMES || 1000

const synckitRunStartTime = performance.now()

let i = RUN_TIMES

while (i-- > 0) {
syncFn1()
}

const synckitRuntime = performance.now() - synckitRunStartTime

const syncThreadsRunStartTime = performance.now()

i = RUN_TIMES

while (i-- > 0) {
syncFn2()
}

const syncThreadsRuntime = performance.now() - syncThreadsRunStartTime

const nativeRunStartTime = performance.now()

i = RUN_TIMES

while (i-- > 0) {
syncFn3()
}

const nativeRuntime = performance.now() - nativeRunStartTime

class Benchmark {
/**
* @param {number} synckit
Expand Down Expand Up @@ -65,43 +97,16 @@ class Benchmark {
}
}

const RUN_TIMES = +process.env.RUN_TIMES || 1000

const synckitRunStartTime = performance.now()

let i = RUN_TIMES

while (i-- > 0) {
syncFn1()
}

const synckitRuntime = performance.now() - synckitRunStartTime

const syncThreadsRunStartTime = performance.now()

i = RUN_TIMES

while (i-- > 0) {
syncFn2()
}

const syncThreadsRuntime = performance.now() - syncThreadsRunStartTime

const nativeRunStartTime = performance.now()

i = RUN_TIMES

while (i-- > 0) {
syncFn3()
}

const nativeRuntime = performance.now() - nativeRunStartTime

console.table({
'load time': new Benchmark(
synckitLoadTime,
syncThreadsLoadTime,
nativeLoadTime,
),
'run time': new Benchmark(synckitRuntime, syncThreadsRuntime, nativeRuntime),
'total time': new Benchmark(
synckitLoadTime + synckitRuntime,
syncThreadsLoadTime + syncThreadsRuntime,
nativeLoadTime + nativeRuntime,
),
})
13 changes: 7 additions & 6 deletions benchmarks/benchmark.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
$ node benchmarks/benchmark
┌───────────┬────────────┬──────────────┬───────────┬───────────────────┬──────────────────┐
│ (index) │ synckit │ sync-threads │ native │ perf sync-threads │ perf native │
├───────────┼────────────┼──────────────┼───────────┼───────────────────┼──────────────────┤
│ load time │ '24.71ms' │ '1.34ms' │ '0.22ms' │ '18.41x slower' │ '113.89x slower' │
│ run time │ '198.46ms' │ '4347.89ms' │ '57.51ms' │ '21.91x faster' │ '3.45x slower' │
└───────────┴────────────┴──────────────┴───────────┴───────────────────┴──────────────────┘
┌────────────┬────────────┬──────────────┬───────────┬───────────────────┬──────────────────┐
│ (index) │ synckit │ sync-threads │ native │ perf sync-threads │ perf native │
├────────────┼────────────┼──────────────┼───────────┼───────────────────┼──────────────────┤
│ load time │ '30.19ms' │ '1.61ms' │ '0.26ms' │ '18.80x slower' │ '118.01x slower' │
│ run time │ '216.49ms' │ '4546.67ms' │ '90.49ms' │ '21.00x faster' │ '2.39x slower' │
│ total time │ '246.68ms' │ '4548.27ms' │ '90.75ms' │ '18.44x faster' │ '2.72x slower' │
└────────────┴────────────┴──────────────┴───────────┴───────────────────┴──────────────────┘
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@
"benchmark:export": "yarn benchmark > benchmarks/benchmark.txt",
"build": "run-p build:*",
"build:r": "r -f es2015",
"build:ts": "tsc -P src",
"build:ts": "tsc -p src",
"lint": "run-p lint:*",
"lint:es": "eslint . --cache -f friendly --max-warnings 10",
"lint:tsc": "tsc --noEmit",
"prepare": "simple-git-hooks && yarn-deduplicate --strategy fewer || exit 0",
"prerelease": "npm run build",
"pretest": "yarn build",
"pretest": "yarn build:ts",
"release": "clean-publish && changeset publish",
"test": "jest",
"test-worker": "ts-node test/test-worker-ts && node test/test-worker",
Expand Down Expand Up @@ -81,10 +81,12 @@
]
},
"typeCoverage": {
"atLeast": 100,
"atLeast": 98.63,
"cache": true,
"detail": true,
"ignoreNested": true,
"ignoreAsAssertion": true,
"ignoreNonNullAssertion": true,
"showRelativePath": true,
"strict": true,
"update": true
}
Expand Down
50 changes: 38 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,32 @@ export * from './types'
*/
export const tmpdir = fs.realpathSync(_tmpdir())

export const useWorkerThreads = !['0', 'false'].includes(
process.env.SYNCKIT_WORKER_THREADS!,
)
const { SYNCKIT_WORKER_THREADS, SYNCKIT_BUFFER_SIZE, SYNCKIT_TIMEOUT } =
process.env

export const useWorkerThreads =
!SYNCKIT_WORKER_THREADS || !['0', 'false'].includes(SYNCKIT_WORKER_THREADS)

export const DEFAULT_BUFFER_SIZE = SYNCKIT_BUFFER_SIZE
? +SYNCKIT_BUFFER_SIZE
: undefined

export const DEFAULT_TIMEOUT = SYNCKIT_TIMEOUT ? +SYNCKIT_TIMEOUT : undefined

export const DEFAULT_WORKER_BUFFER_SIZE = DEFAULT_BUFFER_SIZE || 1024

const syncFnCache = new Map<string, AnyFn>()

export function createSyncFn<T extends AnyAsyncFn>(
workerPath: string,
bufferSize?: number,
timeout?: number,
): Syncify<T>
export function createSyncFn<T>(workerPath: string, bufferSize?: number) {
export function createSyncFn<T>(
workerPath: string,
bufferSize?: number,
timeout = DEFAULT_TIMEOUT,
) {
if (!path.isAbsolute(workerPath)) {
throw new Error('`workerPath` must be absolute')
}
Expand All @@ -59,14 +74,19 @@ export function createSyncFn<T>(workerPath: string, bufferSize?: number) {
const syncFn = (useWorkerThreads ? startWorkerThread : startChildProcess)<T>(
resolvedWorkerPath,
bufferSize,
timeout,
)

syncFnCache.set(workerPath, syncFn)

return syncFn
}

function startChildProcess<T>(workerPath: string) {
function startChildProcess<T>(
workerPath: string,
bufferSize = DEFAULT_BUFFER_SIZE,
timeout?: number,
) {
const executor = workerPath.endsWith('.ts') ? 'ts-node' : 'node'

return (...args: unknown[]): T => {
Expand All @@ -79,13 +99,15 @@ function startChildProcess<T>(workerPath: string) {
try {
execSync(command, {
stdio: 'inherit',
maxBuffer: bufferSize,
timeout,
})
const { result, error } = JSON.parse(
fs.readFileSync(filename, 'utf8'),
) as DataMessage<T>

if (error) {
throw typeof error === 'object' && error && 'message' in error
throw typeof error === 'object' && 'message' in error!
? // eslint-disable-next-line unicorn/error-message
Object.assign(new Error(), error)
: error
Expand All @@ -98,7 +120,11 @@ function startChildProcess<T>(workerPath: string) {
}
}

function startWorkerThread<T>(workerPath: string, bufferSize = 1024) {
function startWorkerThread<T>(
workerPath: string,
bufferSize = DEFAULT_WORKER_BUFFER_SIZE,
timeout?: number,
) {
const { port1: mainPort, port2: workerPort } = new MessageChannel()

const isTs = workerPath.endsWith('.ts')
Expand Down Expand Up @@ -126,10 +152,10 @@ function startWorkerThread<T>(workerPath: string, bufferSize = 1024) {
const msg: MainToWorkerMessage = { sharedBuffer, id, args }
worker.postMessage(msg)

const status = Atomics.wait(sharedBufferView, 0, 0)
const status = Atomics.wait(sharedBufferView, 0, 0, timeout)

/* istanbul ignore if */
if (status !== 'ok' && status !== 'not-equal') {
if (!['ok', 'not-equal'].includes(status)) {
throw new Error('Internal error: Atomics.wait() failed: ' + status)
}

Expand All @@ -149,7 +175,7 @@ function startWorkerThread<T>(workerPath: string, bufferSize = 1024) {
// MessagePort doesn't copy the properties of Error objects. We still want
// error objects to have extra properties such as "warnings" so implement the
// property copying manually.
throw Object.assign(error, properties)
throw typeof error === 'object' ? Object.assign(error, properties) : error
}

return result!
Expand All @@ -168,7 +194,7 @@ export const runAsWorker = async <T extends AnyAsyncFn>(fn: T) => {
let msg: DataMessage<T>
try {
msg = { result: (await fn(...args)) as T }
} catch (err) {
} catch (err: unknown) {
msg = {
error:
err instanceof Error
Expand All @@ -192,7 +218,7 @@ export const runAsWorker = async <T extends AnyAsyncFn>(fn: T) => {
let msg: WorkerToMainMessage<T>
try {
msg = { id, result: (await fn(...args)) as T }
} catch (err) {
} catch (err: unknown) {
const error = err as Error
msg = { id, error, properties: { ...error } }
}
Expand Down

0 comments on commit 2809da0

Please sign in to comment.