refactor(plugin-workflow): add logs and try/catch for preparing (#3236)
This commit is contained in:
		
							parent
							
								
									2289bb7418
								
							
						
					
					
						commit
						2f892f00d7
					
				| @ -25,9 +25,11 @@ import UpdateInstruction from './instructions/UpdateInstruction'; | ||||
| 
 | ||||
| import type { ExecutionModel, JobModel, WorkflowModel } from './types'; | ||||
| 
 | ||||
| type ID = number | string; | ||||
| 
 | ||||
| type Pending = [ExecutionModel, JobModel?]; | ||||
| 
 | ||||
| type ID = number | string; | ||||
| type CachedEvent = [WorkflowModel, any, { context?: any }]; | ||||
| 
 | ||||
| export default class WorkflowPlugin extends Plugin { | ||||
|   instructions: Registry<Instruction> = new Registry(); | ||||
| @ -37,7 +39,7 @@ export default class WorkflowPlugin extends Plugin { | ||||
|   private ready = false; | ||||
|   private executing: Promise<void> | null = null; | ||||
|   private pending: Pending[] = []; | ||||
|   private events: [WorkflowModel, any, { context?: any }][] = []; | ||||
|   private events: CachedEvent[] = []; | ||||
| 
 | ||||
|   private loggerCache: LRUCache<string, Logger>; | ||||
| 
 | ||||
| @ -221,7 +223,9 @@ export default class WorkflowPlugin extends Plugin { | ||||
|       }); | ||||
| 
 | ||||
|       this.ready = false; | ||||
|       await this.prepare(); | ||||
|       if (this.events.length) { | ||||
|         await this.prepare(); | ||||
|       } | ||||
|       if (this.executing) { | ||||
|         await this.executing; | ||||
|       } | ||||
| @ -244,15 +248,22 @@ export default class WorkflowPlugin extends Plugin { | ||||
|   } | ||||
| 
 | ||||
|   public trigger(workflow: WorkflowModel, context: object, options: { context?: any } = {}): void { | ||||
|     const logger = this.getLogger(workflow.id); | ||||
|     if (!this.ready) { | ||||
|       logger.warn(`app is not ready, event of workflow ${workflow.id} will be ignored`); | ||||
|       logger.debug(`ignored event data:`, { data: context }); | ||||
|       return; | ||||
|     } | ||||
|     // `null` means not to trigger
 | ||||
|     if (!this.ready || context == null) { | ||||
|     if (context == null) { | ||||
|       logger.warn(`workflow ${workflow.id} event data context is null, event will be ignored`); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.events.push([workflow, context, options]); | ||||
| 
 | ||||
|     this.getLogger(workflow.id).info(`new event triggered, now events: ${this.events.length}`); | ||||
|     this.getLogger(workflow.id).debug(`event data:`, { | ||||
|     logger.info(`new event triggered, now events: ${this.events.length}`); | ||||
|     logger.debug(`event data:`, { | ||||
|       data: context, | ||||
|     }); | ||||
| 
 | ||||
| @ -268,7 +279,9 @@ export default class WorkflowPlugin extends Plugin { | ||||
|     if (!job.execution) { | ||||
|       job.execution = await job.getExecution(); | ||||
|     } | ||||
| 
 | ||||
|     this.getLogger(job.execution.workflowId).info( | ||||
|       `execution (${job.execution.id}) resuming from job (${job.id}) added to pending list`, | ||||
|     ); | ||||
|     this.pending.push([job.execution, job]); | ||||
|     this.dispatch(); | ||||
|   } | ||||
| @ -277,14 +290,9 @@ export default class WorkflowPlugin extends Plugin { | ||||
|     return new Processor(execution, { ...options, plugin: this }); | ||||
|   } | ||||
| 
 | ||||
|   private prepare = async () => { | ||||
|     const [event] = this.events; | ||||
|     if (!event) { | ||||
|       return; | ||||
|     } | ||||
|   private async createExecution(event: CachedEvent): Promise<ExecutionModel | null> { | ||||
|     const [workflow, context, options] = event; | ||||
| 
 | ||||
|     let valid = true; | ||||
|     if (options.context?.executionId) { | ||||
|       // NOTE: no transaction here for read-uncommitted execution
 | ||||
|       const existed = await workflow.countExecutions({ | ||||
| @ -298,55 +306,69 @@ export default class WorkflowPlugin extends Plugin { | ||||
|           `workflow ${workflow.id} has already been triggered in same execution (${options.context.executionId}), and newly triggering will be skipped.`, | ||||
|         ); | ||||
| 
 | ||||
|         valid = false; | ||||
|         return null; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (valid) { | ||||
|       const execution = await this.db.sequelize.transaction(async (transaction) => { | ||||
|         const execution = await workflow.createExecution( | ||||
|           { | ||||
|             context, | ||||
|     const execution = await this.db.sequelize.transaction(async (transaction) => { | ||||
|       const execution = await workflow.createExecution( | ||||
|         { | ||||
|           context, | ||||
|           key: workflow.key, | ||||
|           status: EXECUTION_STATUS.QUEUEING, | ||||
|         }, | ||||
|         { transaction }, | ||||
|       ); | ||||
| 
 | ||||
|       await workflow.increment(['executed', 'allExecuted'], { transaction }); | ||||
|       // NOTE: https://sequelize.org/api/v6/class/src/model.js~model#instance-method-increment
 | ||||
|       if (this.db.options.dialect !== 'postgres') { | ||||
|         await workflow.reload({ transaction }); | ||||
|       } | ||||
| 
 | ||||
|       await (<typeof WorkflowModel>workflow.constructor).update( | ||||
|         { | ||||
|           allExecuted: workflow.allExecuted, | ||||
|         }, | ||||
|         { | ||||
|           where: { | ||||
|             key: workflow.key, | ||||
|             status: EXECUTION_STATUS.QUEUEING, | ||||
|           }, | ||||
|           { transaction }, | ||||
|         ); | ||||
|           transaction, | ||||
|         }, | ||||
|       ); | ||||
| 
 | ||||
|         await workflow.increment(['executed', 'allExecuted'], { transaction }); | ||||
|         // NOTE: https://sequelize.org/api/v6/class/src/model.js~model#instance-method-increment
 | ||||
|         if (this.db.options.dialect !== 'postgres') { | ||||
|           await workflow.reload({ transaction }); | ||||
|         } | ||||
|       execution.workflow = workflow; | ||||
| 
 | ||||
|         await (<typeof WorkflowModel>workflow.constructor).update( | ||||
|           { | ||||
|             allExecuted: workflow.allExecuted, | ||||
|           }, | ||||
|           { | ||||
|             where: { | ||||
|               key: workflow.key, | ||||
|             }, | ||||
|             transaction, | ||||
|           }, | ||||
|         ); | ||||
|       return execution; | ||||
|     }); | ||||
| 
 | ||||
|         execution.workflow = workflow; | ||||
|     this.getLogger(workflow.id).info(`execution of workflow ${workflow.id} created as ${execution.id}`); | ||||
| 
 | ||||
|         return execution; | ||||
|       }); | ||||
| 
 | ||||
|       this.getLogger(workflow.id).debug(`execution of workflow ${workflow.id} created as ${execution.id}`, { | ||||
|         data: execution.context, | ||||
|       }); | ||||
| 
 | ||||
|       // NOTE: cache first execution for most cases
 | ||||
|       if (!this.executing && !this.pending.length) { | ||||
|         this.pending.push([execution]); | ||||
|       } | ||||
|     // NOTE: cache first execution for most cases
 | ||||
|     if (!this.executing && !this.pending.length) { | ||||
|       this.pending.push([execution]); | ||||
|     } | ||||
| 
 | ||||
|     this.events.shift(); | ||||
|     return execution; | ||||
|   } | ||||
| 
 | ||||
|   private prepare = async () => { | ||||
|     const event = this.events.shift(); | ||||
|     if (!event) { | ||||
|       this.getLogger('dispatcher').warn(`events queue is empty, no need to prepare`); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const logger = this.getLogger(event[0].id); | ||||
|     logger.info(`preparing execution for event`); | ||||
| 
 | ||||
|     try { | ||||
|       await this.createExecution(event); | ||||
|     } catch (err) { | ||||
|       logger.error(`failed to create execution: ${err.message}`, err); | ||||
|       // this.events.push(event); // NOTE: retry will cause infinite loop
 | ||||
|     } | ||||
| 
 | ||||
|     if (this.events.length) { | ||||
|       await this.prepare(); | ||||
| @ -356,7 +378,13 @@ export default class WorkflowPlugin extends Plugin { | ||||
|   }; | ||||
| 
 | ||||
|   private dispatch() { | ||||
|     if (!this.ready || this.executing) { | ||||
|     if (!this.ready) { | ||||
|       this.getLogger('dispatcher').warn(`app is not ready, new dispatching will be ignored`); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (this.executing) { | ||||
|       this.getLogger('dispatcher').warn(`workflow executing is not finished, new dispatching will be ignored`); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -104,7 +104,7 @@ export default class Processor { | ||||
|   public async start() { | ||||
|     const { execution } = this; | ||||
|     if (execution.status !== EXECUTION_STATUS.STARTED) { | ||||
|       throw new Error(`execution was ended with status ${execution.status}`); | ||||
|       throw new Error(`execution was ended with status ${execution.status} before, could not be started again`); | ||||
|     } | ||||
|     await this.prepare(); | ||||
|     if (this.nodes.length) { | ||||
| @ -118,7 +118,7 @@ export default class Processor { | ||||
|   public async resume(job: JobModel) { | ||||
|     const { execution } = this; | ||||
|     if (execution.status !== EXECUTION_STATUS.STARTED) { | ||||
|       throw new Error(`execution was ended with status ${execution.status}`); | ||||
|       throw new Error(`execution was ended with status ${execution.status} before, could not be resumed`); | ||||
|     } | ||||
|     await this.prepare(); | ||||
|     const node = this.nodesMap.get(job.nodeId); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user