Refactor ITask into messages
This commit is contained in:
parent
d74faf7c0f
commit
8396323ea1
@ -1,33 +1,29 @@
|
|||||||
import { ITask } from './ITask'
|
import { IRequest } from './ITask'
|
||||||
import cp from 'child_process'
|
import cp from 'child_process'
|
||||||
import { LinkedList } from './LinkedList'
|
import { LinkedList } from './LinkedList'
|
||||||
|
|
||||||
export interface IExecutor<T, R> {
|
export interface IExecutor<T, R> {
|
||||||
execute(task: ITask<T>): Promise<R>
|
execute(task: IRequest<T>): Promise<R>
|
||||||
shutdown(): void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExecutorFactory<T, R> = () => IExecutor<T, R>
|
export type ExecutorFactory<T, R> = () => IExecutor<T, R>
|
||||||
|
|
||||||
export class PromiseExecutor<T, R> implements IExecutor<T, R> {
|
export class PromiseExecutor<T, R> implements IExecutor<T, R> {
|
||||||
constructor(readonly execute: (task: ITask<T>) => Promise<R>) {}
|
constructor(readonly execute: (task: IRequest<T>) => Promise<R>) {}
|
||||||
shutdown() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubprocessExecutor<T, R> implements IExecutor<T, R> {
|
class SubprocessExecutor<T, R> implements IExecutor<T, R> {
|
||||||
process: cp.ChildProcess
|
process: cp.ChildProcess
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected sourceFile: string, protected taskQueue: LinkedList<ITask<T>>,
|
protected sourceFile: string, protected taskQueue: LinkedList<IRequest<T>>,
|
||||||
) {
|
) {
|
||||||
this.process = cp.fork(sourceFile)
|
this.process = cp.fork(sourceFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(task: ITask<T>): Promise<R> {
|
async execute(task: IRequest<T>): Promise<R> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.process.on('status_' + task.id, message => {
|
this.process.once('response_' + task.id, message => {
|
||||||
if (message.error) {
|
if (message.error) {
|
||||||
reject(message.error)
|
reject(message.error)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,9 +1,18 @@
|
|||||||
export interface ITask<T> {
|
export interface IRequest<T> {
|
||||||
readonly id: number
|
id: number
|
||||||
readonly definition: T
|
params: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IResult<T> {
|
export interface ISuccessMessage<T> {
|
||||||
readonly id: number
|
id: number
|
||||||
readonly result: T
|
result: T
|
||||||
|
type: 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IErrorMessage {
|
||||||
|
id: number
|
||||||
|
error: Error
|
||||||
|
type: 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TResponse<T> = ISuccessMessage<T> | IErrorMessage
|
||||||
|
|||||||
25
packages/tasq/src/Messenger.ts
Normal file
25
packages/tasq/src/Messenger.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {IExecutor} from './Executor'
|
||||||
|
import { IRequest, TResponse, IErrorMessage } from './ITask'
|
||||||
|
|
||||||
|
export class Messenger<T, R> {
|
||||||
|
constructor(readonly executor: IExecutor<T, R>) {
|
||||||
|
if (!process.send) {
|
||||||
|
throw new Error('Messenger can only be used from a forked subprocess')
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('message', async (request: IRequest<T>) => {
|
||||||
|
try {
|
||||||
|
const result: R = await this.executor.execute(request)
|
||||||
|
const response: TResponse<R> = {id: request.id, result, type: 'success'}
|
||||||
|
process.send!('response_' + request.id, response)
|
||||||
|
} catch (error) {
|
||||||
|
const response: IErrorMessage = {id: request.id, error, type: 'error'}
|
||||||
|
process.send!('response_' + request.id, response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(code: number) {
|
||||||
|
process.exit(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,8 +13,8 @@ describe('TaskManager', () => {
|
|||||||
const te = new TaskManager<number, void>(
|
const te = new TaskManager<number, void>(
|
||||||
1,
|
1,
|
||||||
() => new PromiseExecutor(async task => {
|
() => new PromiseExecutor(async task => {
|
||||||
await delay(task.definition)
|
await delay(task.params)
|
||||||
results.push(task.definition)
|
results.push(task.params)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
te.post(10)
|
te.post(10)
|
||||||
@ -28,8 +28,8 @@ describe('TaskManager', () => {
|
|||||||
const te = new TaskManager<number, void>(
|
const te = new TaskManager<number, void>(
|
||||||
2,
|
2,
|
||||||
() => new PromiseExecutor(async task => {
|
() => new PromiseExecutor(async task => {
|
||||||
await delay(task.definition)
|
await delay(task.params)
|
||||||
results.push(task.definition)
|
results.push(task.params)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
te.post(100) // worker1
|
te.post(100) // worker1
|
||||||
@ -45,8 +45,8 @@ describe('TaskManager', () => {
|
|||||||
const te = new TaskManager<number, void>(
|
const te = new TaskManager<number, void>(
|
||||||
2,
|
2,
|
||||||
() => new PromiseExecutor(async task => {
|
() => new PromiseExecutor(async task => {
|
||||||
await delay(task.definition)
|
await delay(task.params)
|
||||||
results.push(task.definition)
|
results.push(task.params)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -67,9 +67,9 @@ describe('TaskManager', () => {
|
|||||||
const te = new TaskManager<IParams, number>(
|
const te = new TaskManager<IParams, number>(
|
||||||
2,
|
2,
|
||||||
() => new PromiseExecutor(async task => {
|
() => new PromiseExecutor(async task => {
|
||||||
const {definition} = task
|
const {params} = task
|
||||||
await delay(definition.delay)
|
await delay(params.delay)
|
||||||
return definition.a + definition.b
|
return params.a + params.b
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -91,8 +91,8 @@ describe('TaskManager', () => {
|
|||||||
it('does not fail on error', async () => {
|
it('does not fail on error', async () => {
|
||||||
const tm = new TaskManager<number, void>(2,
|
const tm = new TaskManager<number, void>(2,
|
||||||
() => new PromiseExecutor(async task => {
|
() => new PromiseExecutor(async task => {
|
||||||
await delay(task.definition)
|
await delay(task.params)
|
||||||
if (task.definition % 2 === 0) {
|
if (task.params % 2 === 0) {
|
||||||
throw new Error('Test error: ' + task.id)
|
throw new Error('Test error: ' + task.id)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -3,11 +3,7 @@ import {LinkedList} from './LinkedList'
|
|||||||
import {Deferred} from './Deferred'
|
import {Deferred} from './Deferred'
|
||||||
import { Worker } from './Worker'
|
import { Worker } from './Worker'
|
||||||
import { ExecutorFactory } from './Executor'
|
import { ExecutorFactory } from './Executor'
|
||||||
|
import { IRequest } from './ITask'
|
||||||
interface ITask<T> {
|
|
||||||
id: number
|
|
||||||
definition: T
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ITaskEventHandler {
|
interface ITaskEventHandler {
|
||||||
success: () => void
|
success: () => void
|
||||||
@ -20,7 +16,7 @@ export interface ITaskManager<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class TaskManager<T, R> implements ITaskManager<T> {
|
export class TaskManager<T, R> implements ITaskManager<T> {
|
||||||
protected taskQueue = new LinkedList<ITask<T>>()
|
protected taskQueue = new LinkedList<IRequest<T>>()
|
||||||
protected workers: Set<Promise<void>> = new Set()
|
protected workers: Set<Promise<void>> = new Set()
|
||||||
protected deferredTasks = new Map<number, Deferred<R>>()
|
protected deferredTasks = new Map<number, Deferred<R>>()
|
||||||
|
|
||||||
@ -32,11 +28,11 @@ export class TaskManager<T, R> implements ITaskManager<T> {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async post(definition: T) {
|
async post(params: T) {
|
||||||
const id = this.getNextTaskId()
|
const id = this.getNextTaskId()
|
||||||
this.taskQueue.push({
|
this.taskQueue.push({
|
||||||
id,
|
id,
|
||||||
definition,
|
params,
|
||||||
})
|
})
|
||||||
|
|
||||||
const deferred = new Deferred<R>()
|
const deferred = new Deferred<R>()
|
||||||
@ -59,16 +55,16 @@ export class TaskManager<T, R> implements ITaskManager<T> {
|
|||||||
const promise = new Worker(
|
const promise = new Worker(
|
||||||
this.createExecutor(),
|
this.createExecutor(),
|
||||||
this.taskQueue,
|
this.taskQueue,
|
||||||
(err, result) => {
|
response => {
|
||||||
const deferred = this.deferredTasks.get(result.id)
|
const deferred = this.deferredTasks.get(response.id)
|
||||||
if (!deferred) {
|
if (!deferred) {
|
||||||
throw new Error('No deferred found for task id:' + result.id)
|
throw new Error('No deferred found for task id:' + response.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (err) {
|
if (response.type === 'error') {
|
||||||
deferred.reject(err)
|
deferred.reject(response.error)
|
||||||
} else {
|
} else {
|
||||||
deferred.resolve(result.result)
|
deferred.resolve(response.result)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import cp from 'child_process'
|
import cp from 'child_process'
|
||||||
import { LinkedList } from './LinkedList'
|
import { LinkedList } from './LinkedList'
|
||||||
import { IExecutor } from './Executor'
|
import { IExecutor } from './Executor'
|
||||||
import { IResult, ITask } from './ITask'
|
import { IRequest, TResponse } from './ITask'
|
||||||
|
|
||||||
export interface IWorker<T> {
|
export interface IWorker<T> {
|
||||||
start(): Promise<void>
|
start(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ICallback<R> = (err: Error | undefined, result: IResult<R>) => void
|
export type ICallback<R> = (result: TResponse<R>) => void
|
||||||
|
|
||||||
export class Worker<T, R> implements IWorker<T> {
|
export class Worker<T, R> implements IWorker<T> {
|
||||||
constructor(
|
constructor(
|
||||||
protected executor: IExecutor<T, R>,
|
protected executor: IExecutor<T, R>,
|
||||||
protected taskQueue: LinkedList<ITask<T>>,
|
protected taskQueue: LinkedList<IRequest<T>>,
|
||||||
protected callback: ICallback<R>,
|
protected callback: ICallback<R>,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@ -22,14 +22,16 @@ export class Worker<T, R> implements IWorker<T> {
|
|||||||
while (task !== undefined) {
|
while (task !== undefined) {
|
||||||
try {
|
try {
|
||||||
const result = await this.executor.execute(task)
|
const result = await this.executor.execute(task)
|
||||||
this.callback(undefined, {
|
this.callback({
|
||||||
id: task.id,
|
id: task.id,
|
||||||
result,
|
result,
|
||||||
|
type: 'success',
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.callback(err, {
|
this.callback({
|
||||||
id: task.id,
|
id: task.id,
|
||||||
result: {} as any,
|
error: err,
|
||||||
|
type: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
task = this.taskQueue.shift()
|
task = this.taskQueue.shift()
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import {ITask} from '../ITask'
|
import { Messenger } from '../Messenger'
|
||||||
|
import { IExecutor } from '../Executor'
|
||||||
|
import { IRequest } from '../ITask'
|
||||||
|
|
||||||
process.on('message', async (task: ITask<[number, number]>) => {
|
const executor = new (class implements IExecutor<[number, number], number> {
|
||||||
await new Promise(delay => {
|
async execute(task: IRequest<[number, number]>) {
|
||||||
delay(task.definition)
|
await new Promise(resolve => {
|
||||||
process.send!('status_' + task.id, {
|
setTimeout(resolve, 1)
|
||||||
id: task.id,
|
|
||||||
result: task.definition[0] + task.definition[1],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
return task.params[0] + task.params[1]
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
export const messenger = new Messenger(executor)
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
process.on('message', async task => {
|
|
||||||
process.send('message',
|
|
||||||
})
|
|
||||||
Loading…
x
Reference in New Issue
Block a user