execProcessKillPid.mjs

import treeKill from 'tree-kill'
import genPm from './genPm.mjs'
import getErrorMessage from './getErrorMessage.mjs'
import getTrace from './getTrace.mjs'


/**
 * 強制關閉指定PID程序及其子孫程序(跨平台,Windows/Linux/macOS)
 *
 * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/execProcessKillPid.test.mjs Github}
 * @memberOf wsemi
 * @param {Number|String} pid 輸入程序pid
 * @param {Object} [opt={}] 輸入設定物件,預設{}
 * @param {Function} [opt.onTrace=null] 輸入觀察回調函數,於內部事件發生當下同步呼叫,傳入一個凍結之事件物件{ type, ... },供測試以事件(而非等待時間)同步與診斷;type為'start'時為即將呼叫tree-kill(含pid為傳給tree-kill之數字,非有限數字者為null),'done'時為tree-kill回報成功而即將resolve(含pid),'fail'時為tree-kill回報失敗而即將reject(含pid;msg為reject訊息中冒號後之錯誤訊息字串);事件皆於resolve或reject之前通知;回調不帶接收者呼叫,其拋錯與回傳值皆忽略,不影響本函數;opt或onTrace不可讀(getter拋錯、已撤銷之Proxy)時視同未給回調,預設null
 * @returns {Promise} 回傳Promise,resolve回傳成功訊息`pid[N] killed`,reject回傳錯誤訊息字串`pid[N] kill failed: 訊息`;Windows下訊息含taskkill之輸出,其為系統字碼頁而可能無法正確顯示
 * @example
 * //need test in nodejs
 *
 * if (true) {
 *     let pid = 123456
 *     execProcessKillPid(pid)
 *         .then(function(data) {
 *             console.log('then', data)
 *         })
 *         .catch(function(data) {
 *             console.log('catch', data)
 *         })
 * }
 *
 */
function execProcessKillPid(pid, opt = {}) {

    // //execProcess, 執行windows指令taskkill去中止pid, 不論是哪種mode都有機率觸發「ERROR: The process "{pid}" not found.」
    // let cmd = 'taskkill'
    // let args = ['/pid', cstr(pid), '/T', '/F']
    // let r = await execProcess(cmd, args) //預設spawn
    // // let r = await execProcess(cmd, args, { mode: 'spawn' })
    // // let r = await execProcess(cmd, args, { mode: 'exec' })
    // // let r = await execProcess(cmd, args, { mode: 'exec', useChcp:true })
    // // let r = await execProcess(cmd, args, { mode: 'execFile' })
    // // console.log('r', r)

    //pm
    let pm = genPm()

    //trace, 觀察回調(opt.onTrace), 未給時為null; opt不可讀(getter拋錯、已撤銷之Proxy)時視同未給, 回調屬觀察用途不得改變本函數既有行為
    let trace = null
    try {
        trace = getTrace(opt)
    }
    catch (err) {
        trace = null
    }

    //treeKill, Windows內部呼叫「taskkill /pid PID /T /F」, Linux/macOS則以ps/pgrep遞迴尋找子pid後逐一發signal
    //失敗時tree-kill給Error物件(pid非數字、taskkill失敗等), 轉為錯誤訊息字串, 與resolve之訊息格式對稱
    let npid = Number(pid)
    let tpid = null //事件用之pid, 非有限數字(NaN、Infinity)為null
    if (trace) {
        tpid = Number.isFinite(npid) ? npid : null
        trace('start', { pid: tpid })
    }
    treeKill(npid, 'SIGKILL', (err) => {
        if (err) {
            let msg = getErrorMessage(err)
            if (trace) {
                trace('fail', { pid: tpid, msg })
            }
            pm.reject(`pid[${pid}] kill failed: ${msg}`)
        }
        else {
            if (trace) {
                trace('done', { pid: tpid })
            }
            pm.resolve(`pid[${pid}] killed`)
        }
    })

    return pm
}


export default execProcessKillPid