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;
 }