Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/abort listener execution #484

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 33 additions & 21 deletions examples/abort.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,58 @@
const workerpool = require("..");

var workerCount = 0;

// create a worker pool
const pool = workerpool.pool(__dirname + "/workers/cleanupAbort.js", {
// maximum time to wait for worker to cleanup it's resources
// on termination before forcefully stopping the worker
workerTerminateTimeout: 1000,
onCreateWorker: (args) => {
onCreateWorker: function (args) {
console.log("New worker created");
workerCount += 1;
}
},
onAbortResolution: function(args) {
console.log("abort operation concluded for task:", args.id);
console.log("is worker terminating", args.isTerminating);
},
onTerminateWorker: function() {
console.log("worker terminated");
},
maxWorkers: 1,
});

function add (a, b) {
return a + b;
}

const main = async () => {
const cleanedUpTask = pool.exec('asyncTimeout', []).timeout(1_000).catch((err) => {
console.log("task timeout");
let abortResolverSuccess;
await pool.exec('asyncTimeout', [], {
onAbortStart: async function(args) {
console.log("abort operation started");
abortResolverSuccess = args.taskResolver.promise;
}
}).timeout(100).catch((err) => {
console.log("timeout occured: ", err.message);
console.log("worker count ", workerCount);
return pool.exec(add, [1, 2]).then((sum) => {
console.log('add result', sum);
console.log("worker count: ", workerCount);
});
});
await cleanedUpTask;

const canceledTask = pool.exec('asyncAbortHandlerNeverResolves').cancel().catch((err) => {
await abortResolverSuccess.catch((err) => {
console.log("", err);
});

console.log("pool status after abort operation:", pool.stats());


let abortResolverFailure;
await pool.exec('asyncAbortHandlerNeverResolves', [], {
onAbortStart: function(args) {
console.log("abort operation started");
abortResolverFailure = args.taskResolver.promise;
}
}).cancel().catch((err) => {
console.log("task canceled");
console.log("cancel occured: ", err.message);
console.log("worker count ", workerCount);
return pool.exec(add, [1, 2]).then((sum) => {
console.log('add result', sum);
console.log("worker count: ", workerCount);
});
});

await canceledTask;
console.log("final pool stats", pool.stats());
// we dont need to terminate the pool, since all workers should be terminated by this point even though there is a handler.
}


main();
3 changes: 3 additions & 0 deletions src/Pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ function Pool(script, options) {
this.onCreateWorker = options.onCreateWorker || (() => null);
/** @readonly */
this.onTerminateWorker = options.onTerminateWorker || (() => null);
/** @readonly */
this.onAbortResolution = options.onAbortResolution || (() => null);

/** @readonly */
this.emitStdStreams = options.emitStdStreams || false
Expand Down Expand Up @@ -430,6 +432,7 @@ Pool.prototype._createWorkerHandler = function () {
workerType: this.workerType,
workerTerminateTimeout: this.workerTerminateTimeout,
emitStdStreams: this.emitStdStreams,
onAbortResolution: this.onAbortResolution
});
}

Expand Down
34 changes: 27 additions & 7 deletions src/WorkerHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ function WorkerHandler(script, _options) {
this.workerOpts = options.workerOpts;
this.workerThreadOpts = options.workerThreadOpts
this.workerTerminateTimeout = options.workerTerminateTimeout;
this.onAbortResolution = options.onAbortResolution || (() => null);

// The ready message is only sent if the worker.add method is called (And the default script is not used)
if (!script) {
Expand Down Expand Up @@ -312,15 +313,19 @@ function WorkerHandler(script, _options) {
if (response.method === CLEANUP_METHOD_ID) {
var trackedTask = me.tracking[response.id];
if (trackedTask !== undefined) {
delete me.tracking[id];
if (response.error) {
clearTimeout(trackedTask.timeoutId);
trackedTask.resolver.reject(objectToError(response.error))
trackedTask.resolver.reject(objectToError(response.error));
} else {
me.tracking && clearTimeout(trackedTask.timeoutId);
trackedTask.resolver.resolve(trackedTask.result);
trackedTask.resolver.resolve(trackedTask.result);
me.onAbortResolution({
id,
isTerminating: false,
})
}
}
delete me.tracking[id];
}
}
});
Expand Down Expand Up @@ -431,7 +436,9 @@ WorkerHandler.prototype.exec = function(method, params, resolver, options) {
me.tracking[id] = {
id,
resolver: Promise.defer(),
options: options,
options: options ?? {
onAbortStart: () => {}
},
};

// remove this task from the queue. It is already rejected (hence this
Expand All @@ -442,9 +449,19 @@ WorkerHandler.prototype.exec = function(method, params, resolver, options) {
delete me.tracking[id];

var promise = me.terminateAndNotify(true)
.then(function() {
.then(function(args) {
me.onAbortResolution({
error: args,
id,
isTerminating: true
});
throw err;
}, function(err) {
me.onAbortResolution({
error: err,
id,
isTerminating: true
});
throw err;
});

Expand All @@ -455,7 +472,10 @@ WorkerHandler.prototype.exec = function(method, params, resolver, options) {
id,
method: CLEANUP_METHOD_ID
});

me.tracking[id].options.onAbortStart({
id,
taskResolver: me.tracking[id].resolver,
});

/**
* Sets a timeout to reject the cleanup operation if the message sent to the worker
Expand Down Expand Up @@ -484,7 +504,7 @@ WorkerHandler.prototype.exec = function(method, params, resolver, options) {
* @return {boolean} Returns true if the worker is busy
*/
WorkerHandler.prototype.busy = function () {
return this.cleaning || Object.keys(this.processing).length > 0;
return this.cleaning || Object.keys(this.processing).length > 0 || Object.keys(this.tracking).length > 0;
};

/**
Expand Down
10 changes: 5 additions & 5 deletions src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ worker.cleanup = function(requestId) {
error: convertError(new Error('Worker terminating')),
});

// If there are no handlers registered, reject the promise with an error as we want the handler to be notified
// If there are no handlers registered, as we want the handler to be notified
// that cleanup should begin and the handler should be GCed.
return new Promise(function(resolve) { resolve(); });
}
Expand Down Expand Up @@ -236,10 +236,10 @@ worker.cleanup = function(requestId) {
// - Reject if one or more handlers reject
// Upon one of the above cases a message will be sent to the handler with the result of the handler execution
// which will either kill the worker if the result contains an error, or
return Promise.all([
settlePromise,
timeoutPromise
]).then(function() {
return new Promise(function (resolve, reject) {
settlePromise.then(resolve, reject);
timeoutPromise.then(resolve, reject);
}).then(function() {
worker.send({
id: requestId,
method: CLEANUP_METHOD_ID,
Expand Down
Loading
Loading