6f949e90b1ba3de976455fbcf9da21897761d134 markd Fri Oct 29 16:11:58 2021 -0700 add timeout option to pipeline to allow kill long-running pipelines, especially ones run from CGIs diff --git src/lib/tests/pipelineTester.c src/lib/tests/pipelineTester.c index af8c9cb..daddd3f 100644 --- src/lib/tests/pipelineTester.c +++ src/lib/tests/pipelineTester.c @@ -23,59 +23,62 @@ "arguments to pipe together. Within a cmdArgs, single or\n" "double quote maybe used to quote words.\n" "\n" "Options:\n" " -exitCode=n - run with no-abort and expect this error code,\n" " which can be zero.\n" " -write - create a write pipeline\n" " -memApi - test memory buffer API\n" " -pipeData=file - for a read pipeline, data read from the pipeline is copied\n" " to this file for verification. For a write pipeline, data from this\n" " file is written to the pipeline.\n" " -otherEnd=file - file for other end of pipeline\n" " -stderr=file - file for stderr of pipeline\n" " -fdApi - use the file descriptor API\n" " -sigpipe - enable SIGPIPE.\n" - " -maxNumLines - read or write this many lines and close (for testing -sigpipe)\n", + " -maxNumLines=n - read or write this many lines and close (for testing -sigpipe)\n" + " -timeout=n - timeout, in seconds\n", msg); } static struct optionSpec options[] = { {"exitCode", OPTION_INT}, {"write", OPTION_BOOLEAN}, {"memApi", OPTION_BOOLEAN}, {"pipeData", OPTION_STRING}, {"otherEnd", OPTION_STRING}, {"stderr", OPTION_STRING}, {"fdApi", OPTION_BOOLEAN}, {"sigpipe", OPTION_BOOLEAN}, {"maxNumLines", OPTION_INT}, + {"timeout", OPTION_INT}, {NULL, 0}, }; /* options from command line */ boolean noAbort = FALSE; /* don't abort, check exit code */ int expectExitCode = 0; /* expected exit code */ boolean fdApi = FALSE; /* use the file descriptor API */ boolean isWrite = FALSE; /* make a write pipeline */ boolean memApi = FALSE; /* test memory buffer API */ boolean sigpipe = FALSE; /* enable sigpipe */ int maxNumLines = INT_MAX; /* number of lines to read or write */ char *pipeDataFile = NULL; /* use for input or output to the pipeline */ char *otherEndFile = NULL; /* file for other end of pipeline */ char *stderrFile = NULL; /* file for other stderr of pipeline */ +unsigned int timeout = 0; /* timeout to apply */ int countOpenFiles() /* count the number of opens. This is used to make sure no stray * pipes have been left open. */ { int cnt = 0; int fd; struct stat statBuf; for (fd = 0; fd < 64; fd++) { if (fstat(fd, &statBuf) == 0) cnt++; } return cnt; } @@ -194,57 +197,57 @@ else readTest(pl); } int exitCode = pipelineWait(pl); if (exitCode != expectExitCode) errAbort("expected exitCode %d, got %d", expectExitCode, exitCode); } void pipelineTestFd(char ***cmds, unsigned options) /* test for file descriptor API */ { int mode = (isWrite ? O_WRONLY|O_CREAT|O_TRUNC : O_RDONLY); int otherEndFd = mustOpenFd(otherEndFile, mode); int stderrFd = (stderrFile == NULL) ? STDERR_FILENO : mustOpenFd(stderrFile, O_WRONLY|O_CREAT|O_TRUNC); -struct pipeline *pl = pipelineOpenFd(cmds, options, otherEndFd, stderrFd); +struct pipeline *pl = pipelineOpenFd(cmds, options, otherEndFd, stderrFd, timeout); runPipelineTest(pl); pipelineFree(&pl); mustCloseFd(&otherEndFd); if (stderrFile != NULL) mustCloseFd(&stderrFd); } void pipelineTestMem(char ***cmds, unsigned options) /* test for memory buffer API */ { int stderrFd = (stderrFile == NULL) ? STDERR_FILENO : mustOpenFd(stderrFile, O_WRONLY|O_CREAT|O_TRUNC); size_t otherEndBufSize = 0; void *otherEndBuf = loadMemData(otherEndFile, &otherEndBufSize); -struct pipeline *pl = pipelineOpenMem(cmds, options, otherEndBuf, otherEndBufSize, stderrFd); +struct pipeline *pl = pipelineOpenMem(cmds, options, otherEndBuf, otherEndBufSize, stderrFd, timeout); runPipelineTest(pl); pipelineFree(&pl); freeMem(otherEndBuf); if (stderrFile != NULL) mustCloseFd(&stderrFd); } void pipelineTestFName(char ***cmds, unsigned options) /* test for file name API */ { -struct pipeline *pl = pipelineOpen(cmds, options, otherEndFile, stderrFile); +struct pipeline *pl = pipelineOpen(cmds, options, otherEndFile, stderrFile, timeout); runPipelineTest(pl); pipelineFree(&pl); } void pipelineTester(int nCmdsArgs, char **cmdsArgs) /* pipeline tester */ { unsigned options = (isWrite ? pipelineWrite : pipelineRead); if (noAbort) options |= pipelineNoAbort; if (sigpipe) options |= pipelineSigpipe; int startOpenCnt = countOpenFiles(); char ***cmds = splitCmds(nCmdsArgs, cmdsArgs); @@ -266,27 +269,28 @@ /* Process command line. */ { optionInit(&argc, argv, options); if (argc < 2) usage("wrong number of args"); if (optionExists("exitCode")) { noAbort = TRUE; expectExitCode = optionInt("exitCode", 0); } isWrite = optionExists("write"); memApi = optionExists("memApi"); fdApi = optionExists("fdApi"); sigpipe = optionExists("sigpipe"); maxNumLines = optionInt("maxNumLines", INT_MAX); +timeout = optionInt("timeout", 0); if (fdApi && memApi) errAbort("can't specify both -fdApi and -memApi"); pipeDataFile = optionVal("pipeData", NULL); otherEndFile = optionVal("otherEnd", NULL); if (fdApi && (otherEndFile == NULL)) errAbort("-fdApi requires -otherEndFile"); if (memApi && (otherEndFile == NULL)) errAbort("-memApi requires -otherEndFile"); stderrFile = optionVal("stderr", NULL); pipelineTester(argc-1, argv+1); return 0; }