Implement queue work and listen commands
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
e098a5edb7
commit
16e5fa00aa
@ -0,0 +1,64 @@
|
|||||||
|
import {Directive, OptionDefinition} from '../../Directive'
|
||||||
|
import {Inject, Injectable} from '../../../di'
|
||||||
|
import {Bus, PushedToQueue, Queue} from '../../../support/bus'
|
||||||
|
import {Queueables} from '../../../service/Queueables'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ListenDirective extends Directive {
|
||||||
|
@Inject()
|
||||||
|
protected readonly queue!: Queue
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly queueables!: Queueables
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly bus!: Bus
|
||||||
|
|
||||||
|
getDescription(): string {
|
||||||
|
return 'listen for jobs pushed to the queue and attempt to execute them'
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeywords(): string | string[] {
|
||||||
|
return 'queue-listen'
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptions(): OptionDefinition[] {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(): Promise<void> {
|
||||||
|
this.info('Subscribing to queue events...')
|
||||||
|
await this.bus.subscribe(PushedToQueue, async () => {
|
||||||
|
// A new job has been pushed to the queue, so try to pop it and execute it.
|
||||||
|
// We may get undefined if some other worker is running and picked up this job first.
|
||||||
|
await this.tryExecuteJob()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.info('Setting periodic poll...')
|
||||||
|
const handle = setInterval(async () => {
|
||||||
|
await this.tryExecuteJob()
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
|
this.info('Listening for jobs...')
|
||||||
|
await this.untilInterrupt()
|
||||||
|
|
||||||
|
this.info('Shutting down...')
|
||||||
|
clearInterval(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async tryExecuteJob(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const job = await this.queue.pop()
|
||||||
|
if ( !job ) {
|
||||||
|
return // Some other worker already picked up this job
|
||||||
|
}
|
||||||
|
|
||||||
|
this.info(`Executing: ${job.constructor?.name || 'unknown job'}`)
|
||||||
|
await job.execute()
|
||||||
|
this.success('Execution finished.')
|
||||||
|
} catch (e: unknown) {
|
||||||
|
this.error('Failed to execute job.')
|
||||||
|
this.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
import {Event} from './types'
|
||||||
|
import {Container, StaticInstantiable} from '../../di'
|
||||||
|
import {ErrorWithContext} from '../../util'
|
||||||
|
|
||||||
|
export function getEventName<T extends Event>(eventKey: StaticInstantiable<T>): string {
|
||||||
|
const protoName = eventKey.prototype.eventName
|
||||||
|
if ( protoName ) {
|
||||||
|
return protoName
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const inst = Container.getContainer().make<T>(eventKey)
|
||||||
|
if ( inst.eventName ) {
|
||||||
|
return inst.eventName
|
||||||
|
}
|
||||||
|
} catch (e: unknown) {} // eslint-disable-line no-empty
|
||||||
|
|
||||||
|
let stringParseName = eventKey.toString()
|
||||||
|
.split('\n')
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x.startsWith('this.eventName = \'') || x.startsWith('this.eventName = "'))?.[0]
|
||||||
|
?.split('=')?.[1]
|
||||||
|
?.trim()
|
||||||
|
|
||||||
|
if ( stringParseName ) {
|
||||||
|
stringParseName = stringParseName.endsWith(';') ? stringParseName.slice(1, -2) : stringParseName.slice(1, -1)
|
||||||
|
if ( stringParseName ) {
|
||||||
|
return stringParseName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ErrorWithContext('Unable to determine eventName from eventKey', {
|
||||||
|
eventKey,
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import {Queueable} from '../types'
|
||||||
|
import {Awaitable} from '../../../util'
|
||||||
|
import {Inject, Injectable} from '../../../di'
|
||||||
|
import {Logging} from '../../../service/Logging'
|
||||||
|
import {CanonicalItemClass} from '../../CanonicalReceiver'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export abstract class BaseJob extends CanonicalItemClass implements Queueable {
|
||||||
|
@Inject()
|
||||||
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
abstract execute(): Awaitable<void>
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import {PushedToQueue} from './PushedToQueue'
|
||||||
|
import {Queueable, SerialPayload, ShouldQueue} from '../../types'
|
||||||
|
import {PushingToQueue} from './PushingToQueue'
|
||||||
|
import {BaseSerializer} from '../../serial/BaseSerializer'
|
||||||
|
import {JSONState} from '../../../../util'
|
||||||
|
import {ObjectSerializer} from '../../serial/decorators'
|
||||||
|
|
||||||
|
export type QueueEvent = PushedToQueue<ShouldQueue<Queueable>> | PushingToQueue<ShouldQueue<Queueable>>
|
||||||
|
|
||||||
|
export interface QueueEventSerialPayload extends JSONState {
|
||||||
|
eventName: '@extollo/lib.PushedToQueue' | '@extollo/lib.PushingToQueue'
|
||||||
|
itemPayload: SerialPayload<Queueable, JSONState>
|
||||||
|
queueName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectSerializer()
|
||||||
|
export class QueueEventSerializer extends BaseSerializer<QueueEvent, QueueEventSerialPayload> {
|
||||||
|
protected async decodeSerial(serial: QueueEventSerialPayload): Promise<QueueEvent> {
|
||||||
|
const item = await this.getSerialization().decode(serial.itemPayload)
|
||||||
|
|
||||||
|
if ( serial.eventName === '@extollo/lib.PushedToQueue' ) {
|
||||||
|
return new PushedToQueue(item as ShouldQueue<Queueable>, serial.queueName)
|
||||||
|
} else {
|
||||||
|
return new PushingToQueue(item as ShouldQueue<Queueable>, serial.queueName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async encodeActual(actual: QueueEvent): Promise<QueueEventSerialPayload> {
|
||||||
|
return {
|
||||||
|
eventName: actual.eventName,
|
||||||
|
queueName: actual.queueName,
|
||||||
|
itemPayload: await this.getSerialization().encode(actual.item),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getName(): string {
|
||||||
|
return '@extollo/lib.QueueEventSerializer'
|
||||||
|
}
|
||||||
|
|
||||||
|
matchActual(some: QueueEvent): boolean {
|
||||||
|
return some instanceof PushedToQueue || some instanceof PushingToQueue
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue