31f2090cd11876f61a92260838078077f68a94ee
chmalee
  Thu Sep 24 15:32:25 2026 -0700
Add a test program and wrapper for debugging hung https connections, refs #38366

diff --git src/lib/tests/httpsFerryLeakTest.c src/lib/tests/httpsFerryLeakTest.c
new file mode 100644
index 00000000000..0f478dd6f87
--- /dev/null
+++ src/lib/tests/httpsFerryLeakTest.c
@@ -0,0 +1,76 @@
+/* httpsFerryLeakTest - open a udc file over https, read a little, then errAbort
+ * inside an errCatch without closing the file, and sleep so the https ferry thread
+ * and its socket can be inspected. */
+
+/* Copyright (C) 2026 The Regents of the University of California
+ * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
+
+#include "common.h"
+#include "errCatch.h"
+#include "options.h"
+#include "udc.h"
+
+void usage()
+/* Explain usage and exit */
+{
+errAbort(
+"httpsFerryLeakTest - open an https url with udc, read part of it, errAbort inside\n"
+"an errCatch, then sleep so the leftover https thread and socket can be inspected.\n"
+"usage:\n"
+"   httpsFerryLeakTest URL\n"
+"URL should be a large file (several MB) on an https server.\n"
+"options:\n"
+"   -readSize=N  bytes to read before the errAbort (default 4096)\n"
+"   -sleep=N     seconds to sleep after the errCatch (default 180)\n"
+"   -close       close the udc file before the errAbort\n");
+}
+
+static struct optionSpec options[] = {
+    {"readSize", OPTION_INT},
+    {"sleep", OPTION_INT},
+    {"close", OPTION_BOOLEAN},
+    {NULL, 0},
+};
+
+void openReadAbort(char *url, int readSize, boolean doClose)
+/* Open url with udc, read readSize bytes, optionally close, then errAbort. */
+{
+struct udcFile *udcf = udcFileOpen(url, NULL);
+char *buf = needLargeMem(readSize);
+bits64 sizeRead = udcRead(udcf, buf, readSize);
+fprintf(stderr, "read %llu bytes from %s\n", sizeRead, url);
+freeMem(buf);
+if (doClose)
+    {
+    udcFileClose(&udcf);
+    fprintf(stderr, "closed udc file\n");
+    }
+errAbort("forced errAbort after reading %llu bytes", sizeRead);
+}
+
+int main(int argc, char *argv[])
+/* Process command line. */
+{
+optionInit(&argc, argv, options);
+if (argc != 2)
+    usage();
+char *url = argv[1];
+int readSize = optionInt("readSize", 4096);
+int sleepSeconds = optionInt("sleep", 180);
+boolean doClose = optionExists("close");
+
+udcDisableCache();
+
+struct errCatch *errCatch = errCatchNew();
+if (errCatchStart(errCatch))
+    openReadAbort(url, readSize, doClose);
+errCatchEnd(errCatch);
+if (errCatch->gotError)
+    fprintf(stderr, "caught: %s", errCatch->message->string);
+errCatchFree(&errCatch);
+
+fprintf(stderr, "pid %d sleeping %d seconds\n", (int)getpid(), sleepSeconds);
+sleep(sleepSeconds);
+fprintf(stderr, "done\n");
+return 0;
+}