b76b97f1ca85ee2dbe085ead79e433bcda17a8d1 larrym Fri Oct 21 11:40:01 2011 -0700 prevent timeouts while uploading large custom tracks (redmine #3002) diff --git src/hg/hgCustom/hgCustom.c src/hg/hgCustom/hgCustom.c index c4a40c5..cd71ed5 100644 --- src/hg/hgCustom/hgCustom.c +++ src/hg/hgCustom/hgCustom.c @@ -6,30 +6,32 @@ #include "cart.h" #include "cheapcgi.h" #include "web.h" #include "htmshell.h" #include "hdb.h" #include "hui.h" #include "hCommon.h" #include "customTrack.h" #include "customFactory.h" #include "portable.h" #include "errCatch.h" #if (defined USE_BAM && defined KNETFILE_HOOKS) #include "knetUdc.h" #include "udc.h" #endif//def USE_BAM && KNETFILE_HOOKS +#include "jsHelper.h" +#include <signal.h> static long loadTime = 0; static char const rcsid[] = "$Id: hgCustom.c,v 1.142 2010/05/28 23:04:56 kuhn Exp $"; void usage() /* Explain usage and exit. */ { errAbort( "hgCustom - Custom track management CGI\n" "usage:\n" " hgCustom <CGI settings>\n" ); } @@ -154,33 +156,37 @@ { char *dataUrl = NULL; char buf[1024]; boolean gotClade = FALSE; boolean isUpdateForm = FALSE; if (ct) { isUpdateForm = TRUE; dataUrl = ctDataUrl(ct); } else /* add form needs clade for assembly menu */ gotClade = hGotClade(); +jsIncludeFile("jquery.js", NULL); +jsIncludeFile("hgCustom.js", NULL); +jsIncludeFile("utils.js", NULL); + /* main form */ printf("<FORM ACTION=\"%s\" METHOD=\"%s\" " - " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\">\n", + " ENCTYPE=\"multipart/form-data\" NAME=\"mainForm\" onsubmit=\"$('input[name=Submit]').attr('disabled', 'disabled');\" >\n", hgCustomName(), cartUsualString(cart, "formMethod", "POST")); cartSaveSession(cart); if (!isUpdateForm) { /* Print clade, genome and assembly */ /* NOTE: this uses an additional, hidden form (orgForm), below */ char *onChangeDb = "onchange=\"document.orgForm.db.value = document.mainForm.db.options[document.mainForm.db.selectedIndex].value; document.orgForm.submit();\""; char *onChangeOrg = "onchange=\"document.orgForm.org.value = document.mainForm.org.options[document.mainForm.org.selectedIndex].value; document.orgForm.db.value = 0; document.orgForm.submit();\""; char *onChangeClade = "onchange=\"document.orgForm.clade.value = document.mainForm.clade.options[document.mainForm.clade.selectedIndex].value; document.orgForm.org.value = 0; document.orgForm.db.value = 0; document.orgForm.submit();\""; if (hIsGsidServer()) { printf("<span style='color:red;'>The Custom Track function and its documentation is currently under development ...</span><BR><BR>\n"); } @@ -228,31 +234,32 @@ /* first rows for update form are for track and browser line entry */ if (isUpdateForm) { /* row for instructions */ cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); if (dataUrl) puts("Configuration:"); else { puts("Edit configuration:"); } cgiTableFieldEnd(); cgiTableField(" "); puts("<TD ALIGN='RIGHT'>"); - cgiMakeSubmitButton(); + cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); + printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); cgiTableField(" "); cgiTableRowEnd(); /* row for text entry box */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (dataUrl) { /* can't update via pasting if loaded from URL */ cgiMakeTextAreaDisableable(hgCtConfigLines, cartUsualString(cart, hgCtConfigLines, customTrackUserConfig(ct)), CONFIG_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); } else @@ -293,62 +300,66 @@ else cgiTableField("Paste URLs or data:"); if (isUpdateForm && dataUrl) cgiTableField(" "); else { puts("<TD ALIGN='RIGHT'>"); puts("Or upload: "); cgiMakeFileEntry(hgCtDataFile); cgiTableFieldEnd(); } if (!isUpdateForm) { cgiSimpleTableFieldStart(); - cgiMakeSubmitButton(); + cgiMakeButtonWithOnClick("Submit", "Submit", NULL, "return submitClick(this);"); + printf("<img id='loadingImg' src='../images/loading.gif' />\n"); cgiTableFieldEnd(); } cgiTableRowEnd(); /* next row - text entry box for data, and clear button */ cgiSimpleTableRowStart(); puts("<TD COLSPAN=2>"); if (dataUrl) { /* can't update via pasting if loaded from URL */ safef(buf, sizeof buf, "Replace data at URL: %s", ctDataUrl(ct)); cgiMakeTextAreaDisableable(hgCtDataText, buf, TEXT_ENTRY_ROWS, TEXT_ENTRY_COLS, TRUE); } else { int rows = (isUpdateForm ? TEXT_ENTRY_ROWS - CONFIG_ENTRY_ROWS : TEXT_ENTRY_ROWS); cgiMakeTextArea(hgCtDataText, cartUsualString(cart, hgCtDataText, ""), rows, TEXT_ENTRY_COLS); } cgiTableFieldEnd(); cgiSimpleTableFieldStart(); cgiSimpleTableStart(); cgiSimpleTableRowStart(); cgiSimpleTableFieldStart(); if (!(isUpdateForm && dataUrl)) + { + printf("<span id='loadingMsg'></span>\n"); makeClearButton(hgCtDataText); + } cgiTableFieldEnd(); cgiTableRowEnd(); cgiTableEnd(); cgiTableFieldEnd(); cgiTableRowEnd(); /* extra space */ cgiSimpleTableRowStart(); cgiDown(0.7); cgiTableRowEnd(); /* next row - label for description text entry */ cgiSimpleTableRowStart(); cgiTableField("Optional track documentation: "); @@ -1021,30 +1032,46 @@ { struct customTrack *ct = NULL; for (ct = ctList; ct != NULL; ct = ct->next) if (sameString(track, ct->tdb->track)) return ct; return NULL; } boolean customTrackHasConfig(char *text) /* determine if there are track or browser lines in text */ { text = skipLeadingSpaces(text); return startsWith("track ", text) || startsWith("browser ", text); } +int timerCounter; +#define TIMER_INTERVAL 10 + +static void timer(int sig) +{ +// Per HTML 4.01 spec (http://www.w3.org/TR/html401/struct/global.html#h-7.1): +// +// White space (spaces, newlines, tabs, and comments) may appear before or after each section [including the DOCTYPE]. +// +// So we print out comments periodically to keep this process from being killed by apache or the user's web browser. + +printf("<!-- processing (%d seconds) -->\n", timerCounter++ * TIMER_INTERVAL); +fflush(stdout); +alarm(TIMER_INTERVAL); +} + void doMiddle(struct cart *theCart) /* create web page */ { char *ctFileName = NULL; struct slName *browserLines = NULL; struct customTrack *replacedCts = NULL; char *err = NULL, *warn = NULL; char *selectedTable = NULL; struct customTrack *ct = NULL; boolean ctUpdated = FALSE; char *initialDb = NULL; long thisTime = clock1000(); cart = theCart; @@ -1102,30 +1129,40 @@ the variable */ selectedTable = cloneString(cartString(cart, hgCtTable)); if (isNotEmpty(selectedTable)) { ctList = customTracksParseCart(database, cart, NULL, NULL); ct = ctFromList(ctList, selectedTable); } if (ct) doUpdateCustom(ct, NULL); else doAddCustom(NULL); } else { /* get new and existing custom tracks from cart and decide what to do */ + + // setup a timer to periodically print out something to stdout to make sure apache or the web browser doesn't time us out (see redmine #3002). + // e.g. see http://stackoverflow.com/questions/5547166/how-to-avoid-cgi-timeout + struct sigaction *act; + AllocVar(act); + act->sa_handler = timer; + act->sa_flags = SA_RESTART; + sigaction(SIGALRM, act, NULL); + alarm(TIMER_INTERVAL); + char *customText = fixNewData(cart); /* save input so we can display if there's an error */ char *savedCustomText = saveLines(cloneString(customText), SAVED_LINE_COUNT); char *trackConfig = cartOptionalString(cart, hgCtConfigLines); char *savedConfig = cloneString(trackConfig); struct dyString *dsWarn = dyStringNew(0); char *fileName = cartOptionalString(cart, hgCtDataFileName); boolean hasData = (isNotEmpty(customText) || isNotEmpty(fileName)); if (cartVarExists(cart, hgCtUpdatedTrack) && hasData) { /* from 'update' screen */ /* prepend config to data for parser */ struct dyString *dsTrack = dyStringNew(0);