* with GPLv3. If not then please let me know. -Ian Jackson.)
*/
+/*
+ * todo
+ *
+ * don't mind reconnecting if we just disconnected due to idle
+ * some weird disconnection event still investigating
+ */
+
/*
* Newsfeeds file entries should look like this:
* host.name.of.site[/exclude,exclude,...]\
static void filemon_start(InputFile *ipf);
static void filemon_stop(InputFile *ipf);
-static void filemon_callback(InputFile *ipf);
+static void tailing_make_readable(InputFile *ipf);
static void vconnfail(Conn *conn, const char *fmt, va_list al) PRINTF(2,0);
static void connfail(Conn *conn, const char *fmt, ...) PRINTF(2,3);
/* when changing defaults, remember to update the manpage */
static const char *sitename, *remote_host;
-static const char *feedfile, *path_cli;
+static const char *feedfile, *path_run, *path_cli, *path_cli_dir;
static int quiet_multiple=0;
static int become_daemon=1, try_filemon=1;
static int try_stream=1;
/* all these are initialised to seconds, and converted to periods in main */
static int reconnect_delay_periods=1000;
static int flushfail_retry_periods=1000;
-static int backlog_retry_minperiods=50;
+static int backlog_retry_minperiods=100;
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 int recentact_thresh=3;
+static int recentact_periods=1000;
static double max_bad_data_ratio= 1; /* conv'd from percentage by main */
static int max_bad_data_initial= 30;
oop_read *rd; /* non-0: reading; 0: constructing, or had EOF */
off_t offset;
- int skippinglong, paused;
+ int skippinglong, paused, fake_readable;
ArticleList queue;
long inprogress; /* includes queue.count and also articles in conns */
static char *path_lock, *path_flushing, *path_defer, *path_dump;
static char *globpat_backlog;
static pid_t self_pid;
+static int *recentact_perperiod;
+static int recentact_circptr;
+static int recentact_total;
/* statemc_init initialises */
static StateMachineState sms;
memset(&sa,0,sizeof(sa));
int maxlen= sizeof(sa.un.sun_path);
+ if (!path_cli) {
+ info("control command line disabled");
+ return;
+ }
+
int pathlen= strlen(path_cli);
- if (pathlen > maxlen)
- NOCLI("cli socket path %s too long (%d>%d)", path_cli, pathlen, maxlen);
+ if (pathlen > maxlen) {
+ warn("no cli listener, because cli socket path %s too long (%d>%d)",
+ path_cli, pathlen, maxlen);
+ return;
+ }
+
+ if (path_cli_dir) {
+ int r= mkdir(path_cli_dir, 0700);
+ if (r && errno!=EEXIST)
+ NOCLI("create cli socket directory %s", path_cli_dir);
+ }
int r= unlink(path_cli);
if (r && errno!=ENOENT)
cli_master= socket(PF_UNIX, SOCK_STREAM, 0);
if (cli_master<0) NOCLI("create new cli master socket");
- int sl= strlen(sa.un.sun_path) + offsetof(struct sockaddr_un, sun_path);
+ int sl= pathlen + offsetof(struct sockaddr_un, sun_path);
sa.un.sun_family= AF_UNIX;
memcpy(sa.un.sun_path, path_cli, pathlen);
/*========== management of connections ==========*/
+static void reconnect_blocking_event(void) {
+ until_connect= reconnect_delay_periods;
+}
+
static void conn_closefd(Conn *conn, const char *msgprefix) {
int r= close_perhaps(&conn->fd);
if (r) info("C%d %serror closing socket: %s",
conn->fd, msgprefix, strerror(errno));
}
+static int conn_busy(Conn *conn) {
+ return
+ conn->waiting.count ||
+ conn->priority.count ||
+ conn->sent.count ||
+ conn->xmitu;
+}
+
static void conn_dispose(Conn *conn) {
if (!conn) return;
if (conn->rd) {
}
conn_closefd(conn,"");
free(conn);
- until_connect= reconnect_delay_periods;
}
static void *conn_exception(oop_source *lp, int fd,
for (i=0, d=conn->xmitd; i<conn->xmitu; i++, d++)
xmit_free(d);
+ LIST_REMOVE(conns,conn);
+
char *m= xvasprintf(fmt,al);
- warn("C%d connection failed (requeueing " RCI_TRIPLE_FMT_BASE "): %s",
- conn->fd, RCI_TRIPLE_VALS_BASE(requeue, /*nothing*/), m);
+ warn("C%d (now %d) connection failed requeueing " RCI_TRIPLE_FMT_BASE ": %s",
+ conn->fd, conns.count, RCI_TRIPLE_VALS_BASE(requeue, /*nothing*/), m);
free(m);
- LIST_REMOVE(conns,conn);
+ reconnect_blocking_event();
conn_dispose(conn);
check_assign_articles();
}
va_end(al);
}
+static void conn_idle_close(Conn *conn, const char *why) {
+ static const char quitcmd[]= "QUIT\r\n";
+ int todo= sizeof(quitcmd)-1;
+ const char *p= quitcmd;
+ for (;;) {
+ int r= write(conn->fd, p, todo);
+ if (r<0) {
+ if (isewouldblock(errno))
+ connfail(conn, "blocked writing QUIT to idle connection");
+ else
+ connfail(conn, "failed to write QUIT to idle connection: %s",
+ strerror(errno));
+ break;
+ }
+ assert(r<=todo);
+ todo -= r;
+ if (!todo) {
+ conn->quitting= 1;
+ conn->since_activity= 0;
+ debug("C%d is idle (%s), quitting", conn->fd, why);
+ break;
+ }
+ }
+}
+
+/*
+ * For our last connection, we also shut it down if we have had
+ * less than K in the last L
+ */
static void check_idle_conns(void) {
Conn *conn;
+
+ int veryrecentact= recentact_perperiod[recentact_circptr];
+ recentact_circptr++;
+ recentact_circptr %= recentact_periods;
+ recentact_total -= recentact_perperiod[recentact_circptr];
+ recentact_perperiod[recentact_circptr]= 0;
+
FOR_CONN(conn)
conn->since_activity++;
+
search_again:
FOR_CONN(conn) {
if (conn->since_activity <= need_activity_periods) continue;
else if (conn->xmitu)
connfail(conn,"peer has been sending responses"
" before receiving our commands!");
- else {
- static const char quitcmd[]= "QUIT\r\n";
- int todo= sizeof(quitcmd)-1;
- const char *p= quitcmd;
- for (;;) {
- int r= write(conn->fd, p, todo);
- if (r<0) {
- if (isewouldblock(errno))
- connfail(conn, "blocked writing QUIT to idle connection");
- else
- connfail(conn, "failed to write QUIT to idle connection: %s",
- strerror(errno));
- break;
- }
- assert(r<=todo);
- todo -= r;
- if (!todo) {
- conn->quitting= 1;
- conn->since_activity= 0;
- debug("C%d is idle, quitting", conn->fd);
- break;
- }
- }
- }
+ else
+ conn_idle_close(conn, "no activity");
+
goto search_again;
}
+
+ conn= LIST_HEAD(conns);
+ if (!veryrecentact &&
+ conns.count==1 &&
+ recentact_total+veryrecentact < recentact_thresh &&
+ !conn_busy(conn))
+ conn_idle_close(conn, "low volume");
}
/*---------- making new connections ----------*/
&peer_rd_err, conn);
if (r) sysdie("oop_rd_read for peer (fd=%d)",conn->fd);
- notice("C%d connected %s", conn->fd, conn->stream ? "streaming" : "plain");
LIST_ADDHEAD(conns, conn);
+ notice("C%d (now %d) connected %s",
+ conn->fd, conns.count, conn->stream ? "streaming" : "plain");
connect_attempt_discard();
check_assign_articles();
x:
conn_dispose(conn);
connect_attempt_discard();
+ reconnect_blocking_event();
return OOP_CONTINUE;
}
assert(!connecting_fdpass_sock);
info("starting connection attempt");
+ int ok_reconnect_delay_periods= reconnect_delay_periods;
+ reconnect_blocking_event();
int socks[2];
int r= socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
connecting_fdpass_sock= socks[0];
xsetnonblock(connecting_fdpass_sock, 1);
on_fd_read_except(connecting_fdpass_sock, connchild_event);
+
+ if (!conns.count)
+ reconnect_delay_periods= ok_reconnect_delay_periods;
}
/*---------- assigning articles to conns, and transmitting ----------*/
Article *art= dequeue(0);
if (!art) break;
LIST_ADDTAIL(use->waiting, art);
+ recentact_perperiod[recentact_circptr]++;
spare--;
}
conn_maybe_write(use);
} else if (allow_connect_start()) {
- until_connect= reconnect_delay_periods;
connect_start();
break;
} else {
assert(rs > 0);
int done;
- for (done=0; rs && done<conn->xmitu; done++) {
+ for (done=0; rs; ) {
+ assert(done<conn->xmitu);
struct iovec *vp= &conn->xmit[done];
XmitDetails *dp= &conn->xmitd[done];
- if (rs > vp->iov_len) {
+ if (rs >= vp->iov_len) {
rs -= vp->iov_len;
- xmit_free(dp);
+ xmit_free(dp); /* vp->iov_len -= vp->iov_len, etc. */
+ done++;
} else {
vp->iov_base= (char*)vp->iov_base + rs;
vp->iov_len -= rs;
+ break; /* rs -= rs */
}
}
int newu= conn->xmitu - done;
return OOP_CONTINUE;
}
- int conn_busy=
- conn->waiting.count ||
- conn->priority.count ||
- conn->sent.count ||
- conn->xmitu;
+ int busy= conn_busy(conn);
if (conn->quitting) {
- if (code!=205 && code!=503) {
+ if (code!=205 && code!=400) {
connfail(conn, "peer gave unexpected response to QUIT: %s", sani);
} else {
- notice("C%d idle connection closed by us", conn->fd);
- assert(!conn_busy);
LIST_REMOVE(conns,conn);
+ notice("C%d (now %d) idle connection closed by us",
+ conn->fd, conns.count);
+ assert(!busy);
conn_dispose(conn);
}
return OOP_CONTINUE;
default: PEERBADMSG("peer sent unexpected message");
case 400:
- if (conn_busy)
+ if (busy)
PEERBADMSG("peer timed us out or stopped accepting articles");
- notice("C%d idle connection closed by peer", conn->fd);
LIST_REMOVE(conns,conn);
+ notice("C%d (now %d) idle connection closed by peer",
+ conns.count, conn->fd);
conn_dispose(conn);
return OOP_CONTINUE;
static void *tailing_rable_call_time(oop_source *loop, struct timeval tv,
void *user) {
+ /* lifetime of ipf here is OK because destruction will cause
+ * on_cancel which will cancel this callback */
InputFile *ipf= user;
+
+ if (!ipf->fake_readable) return OOP_CONTINUE;
+
+ /* we just keep calling readable until our caller (oop_rd)
+ * has called try_read, and try_read has found EOF so given EAGAIN */
+ loop->on_time(loop, OOP_TIME_NOW, tailing_rable_call_time, ipf);
+
return ipf->readable_callback(loop, &ipf->readable,
ipf->readable_callback_user);
}
ipf->readable_callback= 0;
}
-static void tailing_queue_readable(InputFile *ipf) {
- /* lifetime of ipf here is OK because destruction will cause
- * on_cancel which will cancel this callback */
+static void tailing_make_readable(InputFile *ipf) {
+ if (!ipf || !ipf->readable_callback) /* so callers can be naive */
+ return;
+ ipf->fake_readable= 1;
loop->on_time(loop, OOP_TIME_NOW, tailing_rable_call_time, ipf);
}
ipf->readable_callback= cb;
ipf->readable_callback_user= user;
filemon_start(ipf);
-
- tailing_queue_readable(ipf);
+ tailing_make_readable(ipf);
return 0;
}
ssize_t r= read(ipf->fd, buffer, length);
if (r==-1) {
if (errno==EINTR) continue;
+ ipf->fake_readable= 0;
return r;
}
if (!r) {
if (ipf==main_input_file) {
errno=EAGAIN;
+ ipf->fake_readable= 0;
return -1;
} else if (ipf==flushing_input_file) {
assert(ipf->rd);
abort();
}
}
- tailing_queue_readable(ipf);
return r;
}
}
}
InputFile *ipf= filemon_inotify_wd2ipf[iev.wd];
/*debug("filemon inotify readable read %p wd=%d", ipf, iev.wd);*/
- filemon_callback(ipf);
+ tailing_make_readable(ipf);
}
return OOP_CONTINUE;
}
ipf->filemon= 0;
}
-static void filemon_callback(InputFile *ipf) {
- if (ipf && ipf->readable_callback) /* so filepoll() can be naive */
- ipf->readable_callback(loop, &ipf->readable, ipf->readable_callback_user);
-}
-
/*---------- interface to start and stop an input file ----------*/
static const oop_rd_style feedfile_rdstyle= {
case INNDCOMMCHILD_ESTATUS_NONESUCH:
notice("feed has been dropped by innd, finishing up");
flushing_input_file= main_input_file;
- tailing_queue_readable(flushing_input_file);
+ tailing_make_readable(flushing_input_file);
/* we probably previously returned EAGAIN from our fake read method
* when in fact we were at EOF, so signal another readable event
* so we actually see the EOF */
case 0:
/* as above */
flushing_input_file= main_input_file;
- tailing_queue_readable(flushing_input_file);
+ tailing_make_readable(flushing_input_file);
main_input_file= open_input_file(feedfile);
if (!main_input_file)
- die("flush succeeded but feedfile %s does not exist!", feedfile);
+ die("flush succeeded but feedfile %s does not exist!"
+ " (this probably means feedfile does not correspond"
+ " to site %s in newsfeeds)", feedfile, sitename);
if (flushing_input_file) {
- SMS(SEPARATED, max_separated_periods, "recovery flush complete");
+ SMS(SEPARATED, max_separated_periods, "flush complete");
} else {
close_defer();
- SMS(NORMAL, spontaneous_flush_periods, "flush complete");
+ SMS(NORMAL, spontaneous_flush_periods, "recovery flush complete");
}
return OOP_CONTINUE;
}
static void filepoll(void) {
- filemon_callback(main_input_file);
- filemon_callback(flushing_input_file);
+ tailing_make_readable(main_input_file);
+ tailing_make_readable(flushing_input_file);
}
static char *debug_report_ipf(InputFile *ipf) {
{0,"no-filemon", 0, &try_filemon, op_setint, 0 },
{'C',"inndconf", "F", &inndconffile, op_string },
{'P',"port", "PORT", &port, op_integer },
-{0,"cli", 0, &path_cli, op_string },
+{0,"chdir", "DIR", &path_run, op_string },
+{0,"cli", "DIR/|PATH", &path_cli, op_string },
{0,"help", 0, 0, help },
{0,"max-connections", "N", &max_connections, op_integer },
{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,"low-volume-thresh", "PERIOD", &recentact_thresh, op_integer },
+{0,"low-volume-window", "PERIOD", &recentact_periods, op_seconds },
{0,"max-bad-input-data-ratio","PERCENT", &max_bad_data_ratio, op_double },
{0,"max-bad-input-data-init", "PERCENT", &max_bad_data_initial, op_integer },
*store /= period_seconds;
}
-static void assemble_path(const char **path_io, const char *suffix,
- const char *what) {
- const char *const specified= *path_io;
- if (!specified[0]) badusage("%s, if specified, must be nonempty", what);
- if (specified[strlen(specified)-1]=='/')
- *path_io= xasprintf("%s%s%s", specified, sitename, suffix);
+static int path_ends_slash(const char *specified) {
+ int l= strlen(specified);
+ assert(l);
+ return specified[l-1] == '/';
}
int main(int argc, char **argv) {
sitename= *argv++;
if (!sitename) badusage("need site name argument");
- remote_host= *argv++;
+
+ if (*argv) remote_host= *argv++;
+ else remote_host= sitename;
+
if (*argv) badusage("too many non-option arguments");
/* defaults */
convert_to_periods_rndup(&spontaneous_flush_periods);
convert_to_periods_rndup(&max_separated_periods);
convert_to_periods_rndup(&need_activity_periods);
+ convert_to_periods_rndup(&recentact_periods);
if (max_bad_data_ratio < 0 || max_bad_data_ratio > 100)
badusage("bad input data ratio must be between 0..100");
max_bad_data_ratio *= 0.01;
-
- if (!feedfile) feedfile= xasprintf("%s/%s",innconf->pathoutgoing,sitename);
- else assemble_path(&feedfile, "", "feed filename");
- if (!path_cli) path_cli= xasprintf("%s_cli", feedfile);
- else assemble_path(&path_cli, "%s_cli", "cli socket path");
+ if (!path_run)
+ path_run= innconf->pathrun;
+
+ if (!feedfile) feedfile= sitename;
+ if (!feedfile[0]) badusage("feed filename, if specified, must be nonempty");
+ if (path_ends_slash(feedfile))
+ feedfile= xasprintf("%s%s", feedfile, sitename);
+ if (feedfile[0] != '/')
+ feedfile= xasprintf("%s/%s", innconf->pathoutgoing, feedfile);
+
+ if (!path_cli) {
+ path_cli_dir= "innduct";
+ } else if (!path_cli[0] || !strcmp(path_cli,"none")) {
+ path_cli= 0; /* ok, don't then */
+ } else if (path_ends_slash(path_cli)) {
+ path_cli_dir= xasprintf("%.*s", strlen(path_cli)-1, path_cli);
+ }
+ if (path_cli_dir)
+ path_cli= xasprintf("%s/%s", path_cli_dir, sitename);
if (max_queue_per_ipf<0)
max_queue_per_ipf= max_queue_per_conn * 2;
if (strchr(feedfile, c))
badusage("feed filename may not contain metacharacter %c",c);
+ int i;
+ recentact_perperiod= xcalloc(sizeof(*recentact_perperiod),recentact_periods);
+ for (i=0; i<recentact_periods; i++) {
+ recentact_perperiod[i]= recentact_thresh;
+ recentact_total += recentact_thresh;
+ }
+
/* set things up */
path_lock= xasprintf("%s_lock", feedfile);
self_pid= getpid();
if (self_pid==-1) sysdie("getpid");
+ r= chdir(path_run);
+ if (r) sysdie("could not chdir to pathrun %s", path_run);
+
statemc_lock();
init_signals();