nodejs/benchmark/async_hooks/async-resource-vs-destroy.js
Anna Henningsen f5ed5fe068
benchmark: fix async-resource benchmark
In the benchmark, because it performs asynchronous operations before
writing its HTTP replies, the underlying socket can be closed by the
peer before the response is written. Since 28e6626ce7020, that means
that attempting to `.end()` the HTTP response results in an uncaught
exception, breaking the benchmark.

Fix that by checking whether the response object has been destroyed
or not before attempting to call `.end()`.

https://github.com/nodejs/node/issues/33591

PR-URL: https://github.com/nodejs/node/pull/33642
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
2020-06-06 17:36:52 +02:00

187 lines
3.9 KiB
JavaScript

'use strict';
const { promisify } = require('util');
const { readFile } = require('fs');
const sleep = promisify(setTimeout);
const read = promisify(readFile);
const common = require('../common.js');
const {
createHook,
executionAsyncResource,
executionAsyncId,
AsyncLocalStorage
} = require('async_hooks');
const { createServer } = require('http');
const bench = common.createBenchmark(main, {
type: ['async-resource', 'destroy', 'async-local-storage'],
asyncMethod: ['callbacks', 'async'],
path: '/',
connections: 500,
duration: 5,
n: [1e6]
});
function buildCurrentResource(getServe) {
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init });
const cls = Symbol('cls');
hook.enable();
return {
server,
close
};
function getCLS() {
const resource = executionAsyncResource();
if (!resource[cls]) {
return null;
}
return resource[cls].state;
}
function setCLS(state) {
const resource = executionAsyncResource();
if (!resource[cls]) {
resource[cls] = { state };
} else {
resource[cls].state = state;
}
}
function init(asyncId, type, triggerAsyncId, resource) {
const cr = executionAsyncResource();
if (cr !== null) {
resource[cls] = cr[cls];
}
}
function close() {
hook.disable();
server.close();
}
}
function buildDestroy(getServe) {
const transactions = new Map();
const server = createServer(getServe(getCLS, setCLS));
const hook = createHook({ init, destroy });
hook.enable();
return {
server,
close
};
function getCLS() {
const asyncId = executionAsyncId();
return transactions.has(asyncId) ? transactions.get(asyncId) : null;
}
function setCLS(value) {
const asyncId = executionAsyncId();
transactions.set(asyncId, value);
}
function init(asyncId, type, triggerAsyncId, resource) {
transactions.set(asyncId, getCLS());
}
function destroy(asyncId) {
transactions.delete(asyncId);
}
function close() {
hook.disable();
server.close();
}
}
function buildAsyncLocalStorage(getServe) {
const asyncLocalStorage = new AsyncLocalStorage();
const server = createServer((req, res) => {
asyncLocalStorage.run({}, () => {
getServe(getCLS, setCLS)(req, res);
});
});
return {
server,
close
};
function getCLS() {
const store = asyncLocalStorage.getStore();
if (store === undefined) {
return null;
}
return store.state;
}
function setCLS(state) {
const store = asyncLocalStorage.getStore();
if (store === undefined) {
return;
}
store.state = state;
}
function close() {
asyncLocalStorage.disable();
server.close();
}
}
function getServeAwait(getCLS, setCLS) {
return async function serve(req, res) {
setCLS(Math.random());
await sleep(10);
await read(__filename);
if (res.destroyed) return;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
};
}
function getServeCallbacks(getCLS, setCLS) {
return function serve(req, res) {
setCLS(Math.random());
setTimeout(() => {
readFile(__filename, () => {
if (res.destroyed) return;
res.setHeader('content-type', 'application/json');
res.end(JSON.stringify({ cls: getCLS() }));
});
}, 10);
};
}
const types = {
'async-resource': buildCurrentResource,
'destroy': buildDestroy,
'async-local-storage': buildAsyncLocalStorage
};
const asyncMethods = {
'callbacks': getServeCallbacks,
'async': getServeAwait
};
function main({ type, asyncMethod, connections, duration, path }) {
const { server, close } = types[type](asyncMethods[asyncMethod]);
server
.listen(common.PORT)
.on('listening', () => {
bench.http({
path,
connections,
duration
}, () => {
close();
});
});
}