修改退出进程逻辑

This commit is contained in:
whyour 2022-12-05 15:26:22 +08:00
parent 23fd595582
commit b95fb9cda4
10 changed files with 76 additions and 125 deletions

View File

@ -170,7 +170,7 @@ export default (app: Router) => {
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().allow(''),
type: Joi.string().optional()
type: Joi.string().optional(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@ -183,7 +183,7 @@ export default (app: Router) => {
};
const filePath = join(config.scriptPath, path, filename);
if (type === 'directory') {
emptyDir(filePath);
emptyDir(filePath);
} else {
fs.unlinkSync(filePath);
}
@ -255,19 +255,19 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
filename: Joi.string().required(),
content: Joi.string().optional().allow(''),
path: Joi.string().optional().allow(''),
pid: Joi.number().optional().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
let { filename, content, path } = req.body;
let { filename, path, pid } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const scriptService = Container.get(ScriptService);
const result = await scriptService.stopScript(filePath);
const result = await scriptService.stopScript(filePath, pid);
res.send(result);
} catch (e) {
return next(e);

View File

@ -4,6 +4,8 @@ import got from 'got';
import iconv from 'iconv-lite';
import { exec } from 'child_process';
import FormData from 'form-data';
import psTreeFun from 'pstree.remy';
import { promisify } from 'util';
export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) {
@ -287,15 +289,15 @@ enum FileType {
interface IFile {
title: string;
key: string;
type: 'directory' | 'file',
type: 'directory' | 'file';
parent: string;
mtime: number;
children?: IFile[],
children?: IFile[];
}
export function dirSort(a: IFile, b: IFile) {
if (a.type !== b.type) return FileType[a.type] < FileType[b.type] ? -1 : 1
else if (a.mtime !== b.mtime) return a.mtime > b.mtime ? -1 : 1
if (a.type !== b.type) return FileType[a.type] < FileType[b.type] ? -1 : 1;
else if (a.mtime !== b.mtime) return a.mtime > b.mtime ? -1 : 1;
}
export function readDirs(
@ -452,3 +454,30 @@ export function parseBody(
return parsed;
}
export function psTree(pid: number): Promise<number[]> {
return new Promise((resolve, reject) => {
psTreeFun(pid, (err: any, pids: number[]) => {
if (err) {
reject(err);
}
resolve(pids.filter((x) => !isNaN(x)));
});
});
}
export async function killTask(pid: number): Promise<number[]> {
const pids = await psTree(pid);
if (pids.length) {
process.kill(pids[0], 2);
} else {
process.kill(pid, 2);
}
}
export async function getPid(name: string) {
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
const execAsync = promisify(exec);
let pid = (await execAsync(taskCommand)).stdout;
return Number(pid);
}

View File

@ -5,13 +5,16 @@ import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
import { exec, execSync, spawn } from 'child_process';
import fs from 'fs';
import cron_parser from 'cron-parser';
import { getFileContentByName, concurrentRun, fileExist } from '../config/util';
import {
getFileContentByName,
concurrentRun,
fileExist,
killTask,
} from '../config/util';
import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
import dayjs from 'dayjs';
import { LOG_END_SYMBOL } from '../config/const';
@Service()
export default class CronService {
@ -315,31 +318,11 @@ export default class CronService {
for (const doc of docs) {
if (doc.pid) {
try {
process.kill(-doc.pid);
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
}
}
const err = await this.killTask(doc.command);
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath));
const endTime = dayjs();
const diffTimeStr = doc.last_execution_time
? ` 耗时 ${endTime.diff(
dayjs(doc.last_execution_time * 1000),
'second',
)} `
: '';
if (logFileExist) {
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)}${diffTimeStr}${LOG_END_SYMBOL}`,
);
}
}
await CrontabModel.update(
@ -348,42 +331,6 @@ export default class CronService {
);
}
public async killTask(name: string) {
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
const execAsync = promisify(exec);
try {
let pid = (await execAsync(taskCommand)).stdout;
if (pid) {
pid = (await execAsync(`pstree -p ${pid}`)).stdout;
} else {
return;
}
let pids = pid.match(/\(\d+/g);
const killLogs = [];
if (pids && pids.length > 0) {
// node 执行脚本时还会有10个子进程但是ps -ef中不存在所以截取前三个
pids = pids.slice(0, 3);
for (const id of pids) {
const c = `kill -9 ${id.slice(1)}`;
try {
const { stdout, stderr } = await execAsync(c);
if (stderr) {
killLogs.push(stderr);
}
if (stdout) {
killLogs.push(stdout);
}
} catch (error: any) {
killLogs.push(error.message);
}
}
}
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
} catch (e) {
return JSON.stringify(e);
}
}
private async runSingle(cronId: number): Promise<number> {
return new Promise(async (resolve: any) => {
const cron = await this.getDb({ id: cronId });

View File

@ -44,12 +44,11 @@ export default class ScheduleService {
async runTask(command: string, callbacks: TaskCallbacks = {}) {
return new Promise(async (resolve, reject) => {
const startTime = dayjs();
await callbacks.onBefore?.(startTime);
const cp = spawn(command, { shell: '/bin/bash' });
try {
const startTime = dayjs();
await callbacks.onBefore?.(startTime);
const cp = spawn(command, { shell: '/bin/bash' });
// TODO:
callbacks.onStart?.(cp, startTime);
@ -100,8 +99,8 @@ export default class ScheduleService {
error,
);
await callbacks.onError?.(JSON.stringify(error));
resolve(null);
}
resolve(cp.pid);
});
}

View File

@ -7,6 +7,7 @@ import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config';
import { LOG_END_SYMBOL } from '../config/const';
import { getPid, killTask } from '../config/util';
@Service()
export default class ScriptService {
@ -42,16 +43,24 @@ export default class ScriptService {
public async runScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const command = `task -l ${relativePath} now`;
this.scheduleService.runTask(command, this.taskCallbacks(filePath));
const pid = this.scheduleService.runTask(
command,
this.taskCallbacks(filePath),
);
return { code: 200 };
return { code: 200, data: pid };
}
public async stopScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const err = await this.cronService.killTask(`task -l ${relativePath} now`);
public async stopScript(filePath: string, pid: number) {
let str = '';
if (!pid) {
const relativePath = path.relative(config.scriptPath, filePath);
pid = await getPid(`task -l ${relativePath} now`);
}
try {
await killTask(pid);
} catch (error) {}
const str = err ? `\n${err}` : '';
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: `${str}\n## 执行结束... ${new Date()

View File

@ -19,9 +19,9 @@ import {
concurrentRun,
fileExist,
createFile,
killTask,
} from '../config/util';
import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
import ScheduleService, { TaskCallbacks } from './schedule';
@ -351,19 +351,16 @@ export default class SubscriptionService {
for (const doc of docs) {
if (doc.pid) {
try {
process.kill(-doc.pid);
await killTask(doc.pid);
} catch (error) {
this.logger.silly(error);
}
}
const command = this.formatCommand(doc);
const err = await this.killTask(command);
const absolutePath = await this.handleLogPath(doc.log_path as string);
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${dayjs().format(
`\n## 执行结束... ${dayjs().format(
'YYYY-MM-DD HH:mm:ss',
)}${LOG_END_SYMBOL}`,
);
@ -375,41 +372,6 @@ export default class SubscriptionService {
);
}
public async killTask(name: string) {
let taskCommand = `ps -ef | grep "${name}" | grep -v grep | awk '{print $1}'`;
const execAsync = promisify(exec);
try {
let pid = (await execAsync(taskCommand)).stdout;
if (pid) {
pid = (await execAsync(`pstree -p ${pid}`)).stdout;
} else {
return;
}
let pids = pid.match(/\(\d+/g);
const killLogs = [];
if (pids && pids.length > 0) {
// node 执行脚本时还会有10个子进程但是ps -ef中不存在所以截取前三个
for (const id of pids) {
const c = `kill -9 ${id.slice(1)}`;
try {
const { stdout, stderr } = await execAsync(c);
if (stderr) {
killLogs.push(stderr);
}
if (stdout) {
killLogs.push(stdout);
}
} catch (error: any) {
killLogs.push(error.message);
}
}
}
return killLogs.length > 0 ? JSON.stringify(killLogs) : '';
} catch (e) {
return JSON.stringify(e);
}
}
private async runSingle(subscriptionId: number) {
const subscription = await this.getDb({ id: subscriptionId });
if (subscription.status !== SubscriptionStatus.queued) {

View File

@ -75,6 +75,7 @@
"nedb": "^1.8.0",
"node-schedule": "^2.1.0",
"nodemailer": "^6.7.2",
"pstree.remy": "^1.1.8",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.25.5",
"serve-handler": "^6.1.3",

View File

@ -58,6 +58,7 @@ specifiers:
nodemailer: ^6.7.2
nodemon: ^2.0.15
prettier: ^2.5.1
pstree.remy: ^1.1.8
qiniu: ^7.4.0
qrcode.react: ^1.0.1
query-string: ^7.1.1
@ -112,6 +113,7 @@ dependencies:
nedb: 1.8.0
node-schedule: 2.1.0
nodemailer: 6.7.8
pstree.remy: 1.1.8
reflect-metadata: 0.1.13
sequelize: 6.25.6_@louislam+sqlite3@15.0.6
serve-handler: 6.1.3
@ -10352,7 +10354,6 @@ packages:
/pstree.remy/1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
dev: true
/public-encrypt/4.0.3:
resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}

View File

@ -49,6 +49,7 @@ const EditModal = ({
const { theme } = useTheme();
const editorRef = useRef<any>(null);
const [isRunning, setIsRunning] = useState(false);
const [currentPid, setCurrentPid] = useState(null);
const cancel = () => {
handleCancel();
@ -94,6 +95,7 @@ const EditModal = ({
.then(({ code, data }) => {
if (code === 200) {
setIsRunning(true);
setCurrentPid(data);
}
});
};
@ -102,13 +104,12 @@ const EditModal = ({
if (!cNode || !cNode.title) {
return;
}
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/stop`, {
data: {
filename: cNode.title,
path: cNode.parent || '',
content,
pid: currentPid,
},
})
.then(({ code, data }) => {

2
typings.d.ts vendored
View File

@ -8,3 +8,5 @@ declare module '*.svg' {
const url: string;
export default url;
}
declare module 'pstree.remy';