Index: src/setup.c
==================================================================
--- src/setup.c
+++ src/setup.c
@@ -917,10 +917,19 @@
   @ to this many bytes, uncompressed.  If the client requires more data
   @ than this, then the client will issue multiple HTTP requests.
   @ Values below 1 million are not recommended.  5 million is a
   @ reasonable number.</p>
 
+  @ <hr />
+  entry_attribute("Download time limit", 11, "max-download-time", "mxdwnt",
+                  "30");
+
+  @ <p>Fossil tries to spend less than this many seconds gathering
+  @ the out-bound data of sync, clone, and pull packets.
+  @ If the client request takes longer, a partial reply is given similar
+  @ to the download packet limit. 30s is a reasonable default.</p>
+
   @ <hr />
   onoff_attribute(
       "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript",
       "auto-hyperlink", "autohyperlink", 1);
   @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users

Index: src/xfer.c
==================================================================
--- src/xfer.c
+++ src/xfer.c
@@ -18,10 +18,12 @@
 ** This file contains code to implement the file transfer protocol.
 */
 #include "config.h"
 #include "xfer.h"
 
+#include <time.h>
+
 /*
 ** This structure holds information about the current state of either
 ** a client or a server that is participating in xfer.
 */
 typedef struct Xfer Xfer;
@@ -40,10 +42,11 @@
   int nDeltaRcvd;     /* Number of deltas received */
   int nDanglingFile;  /* Number of dangling deltas received */
   int mxSend;         /* Stop sending "file" with pOut reaches this size */
   u8 syncPrivate;     /* True to enable syncing private content */
   u8 nextIsPrivate;   /* If true, next "file" received is a private */
+  time_t maxTime;     /* Time when this transfer should be finished */
 };
 
 
 /*
 ** The input blob contains a UUID.  Convert it into a record ID.
@@ -393,11 +396,12 @@
   }
   if( uuid_is_shunned(blob_str(pUuid)) ){
     blob_reset(&uuid);
     return;
   }
-  if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
+  if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) || 
+       pXfer->mxSend<=blob_size(pXfer->pOut) ){
     const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
     blob_appendf(pXfer->pOut, zFormat, pUuid);
     pXfer->nIGotSent++;
     blob_reset(&uuid);
     return;
@@ -867,10 +871,11 @@
   }
   blob_zero(&xfer.err);
   xfer.pIn = &g.cgiIn;
   xfer.pOut = cgi_output_blob();
   xfer.mxSend = db_get_int("max-download", 5000000);
+  xfer.maxTime = time(NULL) + db_get_int("max-download-time", 30);
   g.xferPanic = 1;
 
   db_begin_transaction();
   db_multi_exec(
      "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
@@ -1034,11 +1039,12 @@
         if( iVers>=3 ){
           cgi_set_content_type("application/x-fossil-uncompressed");
         }
         blob_is_int(&xfer.aToken[2], &seqno);
         max = db_int(0, "SELECT max(rid) FROM blob");
-        while( xfer.mxSend>blob_size(xfer.pOut) && seqno<=max ){
+        while( xfer.mxSend>blob_size(xfer.pOut) && seqno<=max){
+          if( time(NULL) >= xfer.maxTime ) break;
           if( iVers>=3 ){
             send_compressed_file(&xfer, seqno);
           }else{
             send_file(&xfer, seqno, 0, 1);
           }
@@ -1328,10 +1334,11 @@
   socket_global_init();
   memset(&xfer, 0, sizeof(xfer));
   xfer.pIn = &recv;
   xfer.pOut = &send;
   xfer.mxSend = db_get_int("max-upload", 250000);
+  xfer.maxTime = -1;
   if( syncFlags & SYNC_PRIVATE ){
     g.perm.Private = 1;
     xfer.syncPrivate = 1;
   }