diff --git a/docker-compose.yml b/docker-compose.yml index 6fc0b71..316fea4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: server: image: zhandongliulab/marrvel-server + restart: unless-stopped working_dir: /MARRVEL/server volumes: - "${LOCAL_BASE}/.env:/MARRVEL/server/.env" diff --git a/server/utils/liftover.js b/server/utils/liftover.js index 16c9a1d..c2747b1 100644 --- a/server/utils/liftover.js +++ b/server/utils/liftover.js @@ -1,85 +1,97 @@ const fs = require('fs'); const config = require('../config'); +const cleanupTempFile = (filePath) => { + try { + fs.unlinkSync(filePath); + } catch (error) { + if (error.code !== 'ENOENT') { + console.error(`Error cleaning up temp file ${filePath}: ${error.message}`); + } + } +}; + exports.liftover = async (chr, pos, fromOrg, fromDb, toOrg, toDb, minMatch, isMultiRegionAllowed, minQuery, minChain, minBlocks, isThickFudgeSet) => { - // generate input BED file with random name - const inputBed = `/tmp/${Math.random().toString(36).substring(2, 15)}.bed`; - fs.writeFileSync(inputBed, `chr${chr}\t${pos}\t${pos}\n`); - // generate output BED file with random name - const outputBed = `/tmp/${Math.random().toString(36).substring(2, 15)}.bed`; - // generate unlifted BED file with random name - const unliftedBed = `/tmp/${Math.random().toString(36).substring(2, 15)}.bed`; - if (!config.liftoverCmdTool[fromOrg][fromDb][toOrg][toDb]) { throw new Error('liftOver chain file is not configured properly'); } - const cmdArgs = [ - inputBed, - config.liftoverCmdTool[fromOrg][fromDb][toOrg][toDb], - outputBed, - unliftedBed - ]; - if (minBlocks) { - cmdArgs.push(`-minBlocks=${minBlocks}`); - } - if (isThickFudgeSet) { - cmdArgs.push('-fudgeThick'); - } - if (minMatch) { - cmdArgs.push(`-minMatch=${minMatch}`); - } - if (isMultiRegionAllowed) { - cmdArgs.push('-multiple'); - } - if (minQuery) { - cmdArgs.push(`-minSizeQ=${minQuery}`); - } - if (minChain) { - cmdArgs.push(`-minChainT=${minChain}`); - } - // run liftOver command line tool - try { - await runLiftover(cmdArgs); - } catch (error) { - console.error(`Error occurred while running liftOver: ${error.message}`); - return { - message: 'Error occurred while running liftOver' - }; - } - // read output BED file - let lifted = null; + // generate input/output/unlifted BED files with random names + const inputBed = `/tmp/${Math.random().toString(36).substring(2, 15)}.bed`; + const outputBed = `/tmp/${Math.random().toString(36).substring(2, 15)}.bed`; + const unliftedBed = `/tmp/${Math.random().toString(36).substring(2, 15)}.bed`; + try { - const output = fs.readFileSync(outputBed, 'utf8'); - lifted = (output.split('\n')[0] || '').split('\t'); - } catch (error) { - console.error(`Error reading output BED file: ${error.message}`); - return { - message: 'Error reading output BED file' - }; - } - if (!lifted || lifted.length < 2) { + fs.writeFileSync(inputBed, `chr${chr}\t${pos}\t${pos}\n`); + + const cmdArgs = [ + inputBed, + config.liftoverCmdTool[fromOrg][fromDb][toOrg][toDb], + outputBed, + unliftedBed + ]; + if (minBlocks) { + cmdArgs.push(`-minBlocks=${minBlocks}`); + } + if (isThickFudgeSet) { + cmdArgs.push('-fudgeThick'); + } + if (minMatch) { + cmdArgs.push(`-minMatch=${minMatch}`); + } + if (isMultiRegionAllowed) { + cmdArgs.push('-multiple'); + } + if (minQuery) { + cmdArgs.push(`-minSizeQ=${minQuery}`); + } + if (minChain) { + cmdArgs.push(`-minChainT=${minChain}`); + } + + // run liftOver command line tool + try { + await runLiftover(cmdArgs); + } catch (error) { + console.error(`Error occurred while running liftOver: ${error.message}`); + return { + message: 'Error occurred while running liftOver' + }; + } + // read output BED file + let lifted = null; + try { + const output = fs.readFileSync(outputBed, 'utf8'); + lifted = (output.split('\n')[0] || '').split('\t'); + } catch (error) { + console.error(`Error reading output BED file: ${error.message}`); + return { + message: 'Error reading output BED file' + }; + } + if (!lifted || lifted.length < 2) { + return { + message: 'No lifted position found' + }; + } return { - message: 'No lifted position found' + inputChr: chr, + inputPos: pos, + chr: lifted[0].replace('chr', ''), + pos: parseInt(lifted[1]) }; + } finally { + cleanupTempFile(inputBed); + cleanupTempFile(outputBed); + cleanupTempFile(unliftedBed); } - // clean up temporary files - fs.unlinkSync(inputBed); - fs.unlinkSync(outputBed); - fs.unlinkSync(unliftedBed); - return { - inputChr: chr, - inputPos: pos, - chr: lifted[0].replace('chr', ''), - pos: parseInt(lifted[1]) - }; }; const runLiftover = (args) => { return new Promise((resolve, reject) => { const spawn = require('child_process').spawn; - const child = spawn(config.liftoverCmdTool.path, args); + const child = spawn(config.liftoverCmdTool.path, args, { timeout: 15000, killSignal: 'SIGKILL' }); let stdout = ''; let stderr = ''; child.stdout.on('data', (data) => { @@ -88,7 +100,10 @@ const runLiftover = (args) => { child.stderr.on('data', (data) => { stderr += data.toString(); }); - child.on('close', (code) => { + child.on('close', (code, signal) => { + if (signal) { + return reject(new Error(`liftOver process was killed (signal: ${signal})`)); + } if (code !== 0) { return reject(new Error(`liftOver process exited with code ${code}: ${stderr}`)); } diff --git a/server/utils/transvar.js b/server/utils/transvar.js index 6801af1..b3f12f3 100644 --- a/server/utils/transvar.js +++ b/server/utils/transvar.js @@ -24,7 +24,7 @@ const appendGene = (data) => { const executeTransvar = (option) => { return new Promise((resolve, reject) => { const { spawn } = require('child_process'); - const proc = spawn(transvarPath, option); + const proc = spawn(transvarPath, option, { timeout: 30000, killSignal: 'SIGKILL' }); let stdout = ''; const stderr = []; @@ -37,7 +37,11 @@ const executeTransvar = (option) => { stderr.push(err); }); - proc.on('close', (code) => { + proc.on('close', (code, signal) => { + if (signal) { + reject(new Error(`transvar process was killed (signal: ${signal})`)); + return; + } resolve({ code, stdout,