2021-03-01 18:14:13 +08:00
|
|
|
import { Injectable } from '@nestjs/common';
|
|
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
|
|
import { PipelineTask } from './pipeline-task.entity';
|
|
|
|
import { Repository } from 'typeorm';
|
|
|
|
import { CreatePipelineTaskInput } from './dtos/create-pipeline-task.input';
|
|
|
|
import { RedisService } from 'nestjs-redis';
|
|
|
|
import { Pipeline } from '../pipelines/pipeline.entity';
|
|
|
|
import { InjectQueue } from '@nestjs/bull';
|
|
|
|
import { PIPELINE_TASK_QUEUE } from './pipeline-tasks.constants';
|
|
|
|
import { Queue } from 'bull';
|
2021-03-03 17:24:22 +08:00
|
|
|
import { LockFailedException } from '../commons/exceptions/lock-failed.exception';
|
2021-03-01 18:14:13 +08:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class PipelineTasksService {
|
|
|
|
constructor(
|
|
|
|
@InjectRepository(PipelineTask)
|
|
|
|
private readonly repository: Repository<PipelineTask>,
|
|
|
|
@InjectRepository(Pipeline)
|
|
|
|
private readonly pipelineRepository: Repository<Pipeline>,
|
|
|
|
@InjectQueue(PIPELINE_TASK_QUEUE)
|
|
|
|
private readonly queue: Queue<PipelineTask>,
|
2021-03-06 12:23:55 +08:00
|
|
|
private readonly redis: RedisService,
|
2021-03-01 18:14:13 +08:00
|
|
|
) {}
|
|
|
|
async addTask(dto: CreatePipelineTaskInput) {
|
|
|
|
const pipeline = await this.pipelineRepository.findOneOrFail({
|
|
|
|
where: { id: dto.pipelineId },
|
|
|
|
relations: ['project'],
|
|
|
|
});
|
|
|
|
const task = await this.repository.save(this.repository.create(dto));
|
|
|
|
task.pipeline = pipeline;
|
|
|
|
|
2021-03-05 18:12:34 +08:00
|
|
|
const tasksKey = this.getRedisTokens(pipeline)[1];
|
2021-03-01 18:14:13 +08:00
|
|
|
const redis = this.redis.getClient();
|
2021-03-05 18:12:34 +08:00
|
|
|
await redis.lpush(tasksKey, JSON.stringify(task));
|
2021-03-03 17:24:22 +08:00
|
|
|
await this.doNextTask(pipeline);
|
2021-03-12 23:00:12 +08:00
|
|
|
return task;
|
2021-03-01 18:14:13 +08:00
|
|
|
}
|
|
|
|
|
2021-03-15 13:30:52 +08:00
|
|
|
async findTaskById(id: string) {
|
|
|
|
return await this.repository.findOneOrFail({ id });
|
|
|
|
}
|
|
|
|
|
2021-03-20 14:30:26 +08:00
|
|
|
async listTasksByPipelineId(pipelineId: string) {
|
|
|
|
return await this.repository.find({ pipelineId });
|
|
|
|
}
|
|
|
|
|
2021-03-02 16:28:37 +08:00
|
|
|
async doNextTask(pipeline: Pipeline) {
|
2021-03-03 17:24:22 +08:00
|
|
|
const [lckKey, tasksKey] = this.getRedisTokens(pipeline);
|
2021-03-01 18:14:13 +08:00
|
|
|
const redis = this.redis.getClient();
|
2021-03-03 17:24:22 +08:00
|
|
|
|
2021-03-05 18:12:34 +08:00
|
|
|
const unLck = await new Promise<() => Promise<void>>(
|
|
|
|
async (resolve, reject) => {
|
|
|
|
const lckValue = Date.now().toString();
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
if (
|
|
|
|
await redis
|
|
|
|
.set(lckKey, 0, 'EX', lckValue, 'NX')
|
|
|
|
.then(() => true)
|
|
|
|
.catch(() => false)
|
|
|
|
) {
|
|
|
|
resolve(async () => {
|
|
|
|
if ((await redis.get(lckKey)) === lckValue) {
|
|
|
|
await redis.del(lckKey);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
2021-03-03 17:24:22 +08:00
|
|
|
}
|
2021-03-05 18:12:34 +08:00
|
|
|
reject(new LockFailedException(lckKey));
|
|
|
|
},
|
|
|
|
);
|
2021-03-03 17:24:22 +08:00
|
|
|
|
|
|
|
const task = JSON.parse(
|
2021-03-05 18:12:34 +08:00
|
|
|
(await redis.rpop(tasksKey).finally(() => unLck())) ?? 'null',
|
2021-03-03 17:24:22 +08:00
|
|
|
);
|
2021-03-02 16:28:37 +08:00
|
|
|
if (task) {
|
2021-03-03 17:24:22 +08:00
|
|
|
await this.queue.add(task);
|
2021-03-01 18:14:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 17:12:06 +08:00
|
|
|
async updateTask(task: PipelineTask) {
|
|
|
|
return await this.repository.save(task);
|
|
|
|
}
|
|
|
|
|
2021-03-01 18:14:13 +08:00
|
|
|
getRedisTokens(pipeline: Pipeline): [string, string] {
|
|
|
|
return [`pipeline-${pipeline.id}:lck`, `pipeline-${pipeline.id}:tasks`];
|
|
|
|
}
|
|
|
|
}
|