static void check_assign_articles(void);
static void queue_check_input_done(void);
+static void check_reading_pause_resume(InputFile *ipf);
static void statemc_check_flushing_done(void);
static void statemc_check_backlog_done(void);
static void inputfile_reading_start(InputFile *ipf);
static void inputfile_reading_stop(InputFile *ipf);
+static void inputfile_reading_pause(InputFile *ipf);
+static void inputfile_reading_resume(InputFile *ipf);
+ /* pause and resume are idempotent, and no-op if not done _reading_start */
static void filemon_start(InputFile *ipf);
static void filemon_stop(InputFile *ipf);
static int target_max_feedfile_size=100000;
static int period_seconds=60;
static int filepoll_seconds=5;
+static int max_queue_per_ipf=-1;
static int connection_setup_timeout=200;
static int inndcomm_flush_timeout=100;
static int backlog_retry_minperiods=50;
static int backlog_spontrescan_periods=300;
static int spontaneous_flush_periods=100000;
+static int max_separated_periods=2000;
static int need_activity_periods=1000;
static double max_bad_data_ratio= 1; /* conv'd from percentage by main */
oop_read *rd; /* non-0: reading; 0: constructing, or had EOF */
off_t offset;
- int skippinglong;
+ int skippinglong, paused;
ArticleList queue;
long inprogress; /* includes queue.count and also articles in conns */
+ long autodefer; /* -1 means not doing autodefer */
int counts[art_MaxState][RCI_max];
int readcount_ok, readcount_blank, readcount_err;
requeue[art->state]++;
if (art->state==art_Unsolicited) art->state= art_Unchecked;
LIST_ADDTAIL(art->ipf->queue,art);
+ check_reading_pause_resume(art->ipf);
}
int i;
static Article *dequeue_from(int peek, InputFile *ipf) {
if (!ipf) return 0;
if (peek) return LIST_HEAD(ipf->queue);
- else return LIST_REMHEAD(ipf->queue);
+
+ Article *art= LIST_REMHEAD(ipf->queue);
+ if (!art) return 0;
+ check_reading_pause_resume(ipf);
+ return art;
}
static Article *dequeue(int peek) {
}
}
+/*---------- expiry, flow control and deferral ----------*/
+
+static void check_reading_pause_resume(InputFile *ipf) {
+ if (ipf->queue.count >= max_queue_per_ipf)
+ inputfile_reading_pause(ipf);
+ else
+ inputfile_reading_resume(ipf);
+}
+
+static void article_defer(Article *art /* not on a queue */, int whichcount) {
+ open_defer();
+ if (fprintf(defer, "%s %s\n", TokenToText(art->token), art->messageid) <0
+ || fflush(defer))
+ sysfatal("write to defer file %s",path_defer);
+ article_done(art, whichcount);
+}
+
static int article_check_expired(Article *art /* must be queued, not conn */) {
ARTHANDLE *artdata= SMretrieve(art->token, RETR_STAT);
if (artdata) { SMfreearticle(artdata); return 0; }
int exp= article_check_expired(art);
if (!exp) break;
}
+ check_reading_pause_resume(ipf);
+}
+
+static void article_autodefer(InputFile *ipf, Article *art) {
+ ipf->autodefer++;
+ article_defer(art,-1);
+}
+
+static int has_article_in(const ArticleList *al, InputFile *ipf) {
+ Article *art;
+ for (art=LIST_HEAD(*al); art; art=LIST_NEXT(art))
+ if (art->ipf == ipf) return 1;
+ return 0;
+}
+
+static void autodefer_input_file_articles(InputFile *ipf) {
+ Article *art;
+ while ((art= LIST_REMHEAD(ipf->queue)))
+ article_autodefer(ipf, art);
+}
+
+static void autodefer_input_file(InputFile *ipf) {
+ ipf->autodefer= 0;
+
+ autodefer_input_file_articles(ipf);
+
+ if (ipf->inprogress) {
+ Conn *walk;
+ FOR_CONN(walk) {
+ if (has_article_in(&walk->waiting, ipf) ||
+ has_article_in(&walk->priority, ipf) ||
+ has_article_in(&walk->sent, ipf))
+ walk->quitting= -1;
+ }
+ while (ipf->inprogress) {
+ FOR_CONN(walk)
+ if (walk->quitting < 0) goto found;
+ abort(); /* where are they ?? */
+
+ found:
+ connfail(walk, "connection is stuck or crawling,"
+ " and we need to finish flush");
+ autodefer_input_file_articles(ipf);
+ }
+ }
+
+ check_reading_pause_resume(ipf);
}
/*========== article transmission ==========*/
}
}
-
/*========== handling responses from peer ==========*/
static const oop_rd_style peer_rd_style= {
}
static void article_done(Article *art, int whichcount) {
- if (!art->missing) art->ipf->counts[art->state][whichcount]++;
+ if (whichcount>=0 && !art->missing)
+ art->ipf->counts[art->state][whichcount]++;
if (whichcount == RC_accepted) update_nocheck(1);
else if (whichcount == RC_unwanted) update_nocheck(0);
code_streaming= 1;
case 436: /* IHAVE says try later */
GET_ARTICLE(0);
- open_defer();
- if (fprintf(defer, "%s %s\n", TokenToText(art->token), art->messageid) <0
- || fflush(defer))
- sysfatal("write to defer file %s",path_defer);
- article_done(art, RC_deferred);
+ article_defer(art, RC_deferred);
break;
}
memset(ipf,0,sizeof(*ipf));
ipf->fd= fd;
- strcpy(ipf->path, path);
+ ipf->autodefer= -1;
LIST_INIT(ipf->queue);
+ strcpy(ipf->path, path);
return ipf;
}
strcpy(art->messageid, space+1);
LIST_ADDTAIL(ipf->queue, art);
- if (ipf==backlog_input_file)
+ if (ipf->autodefer >= 0)
+ article_autodefer(ipf, art);
+ else if (ipf==backlog_input_file)
article_check_expired(art);
if (sms==sm_NORMAL && ipf==main_input_file &&
ipf->offset >= target_max_feedfile_size)
statemc_start_flush("feed file size");
- check_assign_articles();
+ check_assign_articles(); /* may destroy conn but that's OK */
+ check_reading_pause_resume(ipf);
return OOP_CONTINUE;
}
OOP_RD_SHORTREC_LONG,
};
+static void inputfile_reading_resume(InputFile *ipf) {
+ if (!ipf->rd) return;
+ if (!ipf->paused) return;
+
+ int r= oop_rd_read(ipf->rd, &feedfile_rdstyle, MAX_LINE_FEEDFILE,
+ feedfile_got_article,ipf, feedfile_read_err, ipf);
+ if (r) sysdie("unable start reading feedfile %s",ipf->path);
+
+ ipf->paused= 0;
+}
+
+static void inputfile_reading_pause(InputFile *ipf) {
+ if (!ipf->rd) return;
+ if (ipf->paused) return;
+ oop_rd_cancel(ipf->rd);
+ ipf->paused= 1;
+}
+
static void inputfile_reading_start(InputFile *ipf) {
assert(!ipf->rd);
ipf->readable.on_readable= tailing_on_readable;
ipf->rd= oop_rd_new(loop, &ipf->readable, 0,0);
assert(ipf->rd);
- int r= oop_rd_read(ipf->rd, &feedfile_rdstyle, MAX_LINE_FEEDFILE,
- feedfile_got_article,ipf, feedfile_read_err, ipf);
- if (r) sysdie("unable start reading feedfile %s",ipf->path);
+ ipf->paused= 1;
+ inputfile_reading_resume(ipf);
}
static void inputfile_reading_stop(InputFile *ipf) {
assert(ipf->rd);
- oop_rd_cancel(ipf->rd);
+ inputfile_reading_pause(ipf);
oop_rd_delete(ipf->rd);
ipf->rd= 0;
assert(!ipf->filemon); /* we shouldn't be monitoring it now */
flushing_input_file= main_input_file;
main_input_file= open_input_file(feedfile);
if (!main_input_file) die("feedfile vanished during startup");
- SMS(SEPARATED, 0, "found both old and current feed files");
+ SMS(SEPARATED, max_separated_periods,
+ "found both old and current feed files");
} else {
debug("startup: F exists, D ENOENT => Normal");
InputFile *file_f= open_input_file(feedfile);
static int trigger_flush_ok(void) { /* => Flushing,FLUSHING, ret 1; or ret 0 */
switch (sms) {
+
case sm_NORMAL:
statemc_start_flush("periodic"); /* Normal => Flushing; => FLUSHING */
return 1;
+
case sm_FLUSHFAILED:
spawn_inndcomm_flush("retry"); /* Moved => Flushing; => FLUSHING */
return 1;
+
+ case sm_SEPARATED:
+ case sm_DROPPING:
+ warn("took too long to complete old feedfile after flush, autodeferring");
+ assert(flushing_input_file);
+ autodefer_input_file(flushing_input_file);
+ return 1;
+
default:
return 0;
}
char *inprog= completed
? xasprintf("%s","") /* GCC produces a stupid warning for printf("") ! */
: xasprintf(" inprogress=%ld", ipf->inprogress);
+ char *autodefer= ipf->autodefer >= 0
+ ? xasprintf(" autodeferred=%ld", ipf->autodefer)
+ : xasprintf("%s","");
- info("%s %s%s read=%d (+bl=%d,+err=%d)%s"
+ info("%s %s%s read=%d (+bl=%d,+err=%d)%s%s"
" offered=%d (ch=%d,nc=%d) accepted=%d (ch=%d,nc=%d)"
RESULT_COUNTS(RCI_NOTHING, RCI_TRIPLE_FMT)
,
completed?"completed":"processed", what, spec,
- ipf->readcount_ok, ipf->readcount_blank, ipf->readcount_err, inprog,
+ ipf->readcount_ok, ipf->readcount_blank, ipf->readcount_err,
+ inprog, autodefer,
CNT(Unchecked,sent) + CNT(Unsolicited,sent)
, CNT(Unchecked,sent), CNT(Unsolicited,sent),
CNT(Wanted,accepted) + CNT(Unsolicited,accepted)
);
free(inprog);
+ free(autodefer);
#undef CNT
}
notice("flush complete");
SMS(NORMAL, spontaneous_flush_periods, "flush complete");
} else if (sms==sm_DROPPING) {
- SMS(DROPPED, 0, "old flush complete");
+ SMS(DROPPED, max_separated_periods, "old flush complete");
search_backlog_file();
notice("feed dropped, but will continue until backlog is finished");
}
main_input_file= 0;
if (flushing_input_file) {
- SMS(DROPPING, 0, "feed dropped by innd, but must finish last flush");
+ SMS(DROPPING, max_separated_periods,
+ "feed dropped by innd, but must finish last flush");
} else {
close_defer();
SMS(DROPPED, 0, "feed dropped by innd");
die("flush succeeded but feedfile %s does not exist!", feedfile);
if (flushing_input_file) {
- SMS(SEPARATED, spontaneous_flush_periods, "recovery flush complete");
+ SMS(SEPARATED, max_separated_periods, "recovery flush complete");
} else {
close_defer();
SMS(NORMAL, spontaneous_flush_periods, "flush complete");
const char *slash= strrchr(ipf->path,'/');
const char *path= slash ? slash+1 : ipf->path;
- return xasprintf("%p/%s:queue=%d,ip=%ld,off=%ld,fd=%d%s%s",
+ return xasprintf("%p/%s:queue=%d,ip=%ld,autodef=%ld,off=%ld,fd=%d%s%s%s",
ipf, path,
- ipf->queue.count, ipf->inprogress, (long)ipf->offset,
- ipf->fd,
+ ipf->queue.count, ipf->inprogress, ipf->autodefer,
+ (long)ipf->offset, ipf->fd,
ipf->rd ? "" : ",!rd",
- ipf->skippinglong ? "*skiplong" : "");
+ ipf->skippinglong ? "*skiplong" : "",
+ ipf->rd && ipf->paused ? "*paused" : "");
}
static void period(void) {
debug("PERIOD"
" sms=%s[%d] conns=%d until_connect=%d"
- " input_files main:%s flushing:%s backlog:%s"
+ " input_files main:%s flushing:%s backlog:%s[%d]"
" children connecting=%ld inndcomm=%ld"
,
sms_names[sms], until_flush, conns.count, until_connect,
- dipf_main, dipf_flushing, dipf_backlog,
+ dipf_main, dipf_flushing, dipf_backlog, until_backlog_nextscan,
(long)connecting_child, (long)inndcomm_child
);
{0,"max-connections", "N", &max_connections, op_integer },
{0,"max-queue-per-conn", "N", &max_queue_per_conn, op_integer },
+{0,"max-queue-per-file", "N", &max_queue_per_ipf, op_integer },
{0,"feedfile-flush-size","BYTES", &target_max_feedfile_size, op_integer },
{0,"period-interval", "TIME", &period_seconds, op_seconds },
{0,"earliest-deferred-retry","PERIOD", &backlog_retry_minperiods, op_seconds },
{0,"backlog-rescan-interval","PERIOD",&backlog_spontrescan_periods,op_seconds},
{0,"max-flush-interval", "PERIOD", &spontaneous_flush_periods,op_seconds },
+{0,"flush-finish-timeout", "PERIOD", &max_separated_periods, op_seconds },
{0,"idle-timeout", "PERIOD", &need_activity_periods, op_seconds },
{0,"max-bad-input-data-ratio","PERCENT", &max_bad_data_ratio, op_double },
convert_to_periods_rndup(&backlog_retry_minperiods);
convert_to_periods_rndup(&backlog_spontrescan_periods);
convert_to_periods_rndup(&spontaneous_flush_periods);
+ convert_to_periods_rndup(&max_separated_periods);
convert_to_periods_rndup(&need_activity_periods);
if (max_bad_data_ratio < 0 || max_bad_data_ratio > 100)
feedfile= xasprintf("%s%s",feedfile,sitename);
}
+ if (max_queue_per_ipf<0)
+ max_queue_per_ipf= max_queue_per_conn * 2;
+
const char *feedfile_forbidden= "?*[~#";
int c;
while ((c= *feedfile_forbidden++))