Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 修复 file read timeout 问题 #407

Merged
merged 2 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions packages/feflow-cli/src/core/resident/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import path from 'path';
import { spawn } from 'child_process';
import chalk from 'chalk';
import lockFile from 'lockfile';
import inquirer from 'inquirer';
import semver from 'semver';
import Table from 'easy-table';
Expand All @@ -19,7 +20,9 @@ import {
UPDATE_KEY,
UPDATE_LOCK,
FEFLOW_HOME,
FEFLOW_UPDATE_BEAT_PROCESS,
} from '../../shared/constant';
import { isProcessExist } from '../../shared/process';
import { safeDump } from '../../shared/yaml';
import { createPm2Process, ErrProcCallback } from './pm2';

Expand Down Expand Up @@ -168,13 +171,44 @@ async function checkLock(updateData: UpdateData) {
return isUpdateData(currUpdateData) && currUpdateData.update_lock?.pid !== process.pid;
}

/**
* 如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁
*
* 当心跳进程意外退出unlock没有被调用时会存在心跳和更新两个unlock文件
* 当这两个unlock文件存在并且没有心跳进程正常运行时会导致主流程报file read timeout错误
* 这个函数的作用是为了解决这个问题,如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁
*
* @param ctx Feflow
*/
async function ensureFilesUnlocked(ctx: Feflow) {
const beatLockPath = path.join(FEFLOW_HOME, BEAT_LOCK);
const updateLockPath = path.join(FEFLOW_HOME, UPDATE_LOCK);
try {
const isPsExist = await isProcessExist(FEFLOW_UPDATE_BEAT_PROCESS);
ctx.logger.debug('fefelow-update-beat-process is exist:', isPsExist);
if (lockFile.checkSync(beatLockPath) && !isPsExist) {
ctx.logger.debug('beat file unlock');
lockFile.unlockSync(beatLockPath);
}
if (lockFile.checkSync(updateLockPath) && !isPsExist) {
ctx.logger.debug('update file unlock');
lockFile.unlockSync(updateLockPath);
}
} catch (e) {
ctx.logger.error('unlock beat or update file fail', e);
}
}

export async function checkUpdate(ctx: Feflow) {
const dbFilePath = path.join(ctx.root, UPDATE_COLLECTION);
const autoUpdate = ctx.args['auto-update'] || String(ctx.config?.autoUpdate) === 'true';
const nowTime = new Date().getTime();
let latestVersion = '';
let cacheValidate = false;

// 如果更新和心跳文件是上锁的状态并且心跳进程不存在时先解锁
await ensureFilesUnlocked(ctx);

if (!updateFile) {
const updateLockPath = path.join(ctx.root, UPDATE_LOCK);
updateFile = new LockFile(dbFilePath, updateLockPath, ctx.logger);
Expand Down Expand Up @@ -207,7 +241,7 @@ export async function checkUpdate(ctx: Feflow) {
const lastBeatTime = parseInt(heartBeatData, 10);

cacheValidate = nowTime - lastBeatTime <= BEAT_GAP;
ctx.logger.debug(`heart-beat process cache validate ${cacheValidate}`);
ctx.logger.debug(`heart-beat process cache validate ${cacheValidate}, ${cacheValidate ? 'not' : ''} launch update-beat-process`);
// 子进程心跳停止了
if (!cacheValidate) {
// todo:进程检测,清理一下僵死的进程(兼容不同系统)
Expand All @@ -218,7 +252,7 @@ export async function checkUpdate(ctx: Feflow) {
}
} else {
// init
ctx.logger.debug('init heart-beat for update detective');
ctx.logger.debug('init heart-beat for update detective, launch update-beat-process');
await Promise.all([
// 初始化心跳数据
heartFile.update(BEAT_KEY, String(nowTime)),
Expand Down
3 changes: 3 additions & 0 deletions packages/feflow-cli/src/shared/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@ export const UPDATE_LOCK = 'update.lock';
export const SILENT_ARG = '--slient';

export const DISABLE_ARG = '--disable-check';

// 心跳进程名字
export const FEFLOW_UPDATE_BEAT_PROCESS = 'feflow-update-beat-process';
87 changes: 87 additions & 0 deletions packages/feflow-cli/src/shared/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 查询进程的代码参考https://github.com/neekey/ps
import { spawn } from 'child_process';
import os from 'os';

/**
* End of line.
* Basically, the EOL should be:
* - windows: \r\n
* - *nix: \n
* try to get every possibilities covered.
*/
const EOL = /(\r\n)|(\n\r)|\n|\r/;
const SystemEOL = os.EOL;
const IS_WIN = process.platform === 'win32';

function lookUpAllProcess(): Promise<string> {
return new Promise((resolve, reject) => {
if (IS_WIN) {
const CMD = spawn('cmd');
let stdout = '';
let stderr = '';

CMD.stdout.on('data', (data) => {
stdout += data.toString();
});

CMD.stderr.on('data', (data) => {
stderr += data.toString();
});

CMD.on('exit', () => {
let beginRow: any;
const stdoutArr = stdout.split(EOL);

// Find the line index for the titles
stdoutArr.forEach((out: string | string[], index: any) => {
if (out && typeof beginRow === 'undefined' && out.indexOf('CommandLine') === 0) {
beginRow = index;
}
});

// get rid of the start (copyright) and the end (current pwd)
stdoutArr.splice(stdout.length - 1, 1);
stdoutArr.splice(0, beginRow);

if (stderr) {
return reject(stderr);
}
return resolve(stdoutArr.join(SystemEOL));
});

CMD.stdin.write('wmic process get ProcessId,ParentProcessId,CommandLine \n');
CMD.stdin.end();
} else {
const child = spawn('ps', ['aux']);
let stdout = '';
let stderr = '';

child.stdout.on('data', (data) => {
stdout += data.toString();
});

child.stderr.on('data', (data) => {
stderr += data.toString();
});

child.on('exit', () => {
if (stderr) {
return reject(stderr);
}
return resolve(stdout);
});
}
});
};

// 判断某个进程是否存在
export async function isProcessExist(psName: string) {
let psInfo: string;
try {
psInfo = await lookUpAllProcess();
const pattern = new RegExp(`${psName}`);
return pattern.test(psInfo);
} catch (e) {
console.error(e);
}
};