Commit
1e1dfc86 (v4.3.23) attempted to do this, but it's flawed.
Server shutdown makes the player command loops terminate. Each player
thread then flushes buffered output and closes the server's end of the
connection. The client eventually gets EOF, and closes its end of the
connection. All is well.
However, if the client sends more input after the server closed its
end of the connection, but before it completed receiving server
output, it gets ECONNRESET, and the remaining output is lost.
Instead of closing the server's end of the connection, we need to shut
down its transmission direction, then wait for the client to close its
end, by receiving client input until EOF. Do that in io_close().
The output flushing in player_login() is now superfluous. Remove it.
Make shutdwn() wait for the io_close() to complete instead of output
queues to drain. Without that, we could still close the server's end
of the connection prematurely, through program termination. Change
player_delete() to keep the player in Players until after io_close()
completes, so that shutdwn() can detect completion.
void
io_close(struct iop *iop)
{
-
+ char buf[IO_BUFSIZE];
+ int ret;
+
+ while (io_output(iop, 1) > 0) ;
+ shutdown(iop->fd, SHUT_WR);
+ while (empth_select(iop->fd, EMPTH_FD_READ, &iop->input_timeout) > 0) {
+ ret = read(iop->fd, buf, sizeof(buf));
+ if (ret <= 0)
+ break;
+ }
if (iop->input)
ioq_destroy(iop->input);
if (iop->output)
if (wait)
ef_make_stale();
- if (!ioq_qsize(iop->output))
- return 0;
-
if ((iop->flags & IO_WRITE) == 0)
return -1;
if (iop->flags & IO_ERROR)
return -1;
+ if (!ioq_qsize(iop->output))
+ return 0;
+
if (wait) {
res = empth_select(iop->fd, EMPTH_FD_WRITE, NULL);
if (res == 0)
{
struct player *back;
- back = (struct player *)lp->queue.q_back;
- if (back)
- emp_remque(&lp->queue);
if (lp->iop) {
/* it's a real player */
io_close(lp->iop);
lp->iop = NULL;
}
+ back = (struct player *)lp->queue.q_back;
+ if (back)
+ emp_remque(&lp->queue);
free(lp);
/* XXX may need to free bigmap here */
return back;
}
player->state = PS_SHUTDOWN;
pr_id(player, C_EXIT, "so long...\n");
- while (io_output(player->iop, 1) > 0) ;
player_delete(player);
empth_exit();
/*NOTREACHED*/
{
struct player *p;
time_t now;
- int i, queues_drained;
+ int i;
logerror("Shutdown commencing (cleaning up threads.)");
now = time(NULL);
empth_yield();
- for (i = 1; i <= 3; i++) {
- queues_drained = 1;
- for (p = player_next(NULL); p; p = player_next(p)) {
- if (io_outputwaiting(p->iop))
- queues_drained = 0;
- }
- if (queues_drained)
- break;
- logerror("Waiting for player output to drain\n");
+ for (i = 1; i <= 3 && player_next(NULL); i++) {
+ logerror("Waiting for player threads to terminate\n");
empth_sleep(now + i);
}
- for (p = player_next(NULL); p; p = player_next(p)) {
- if (io_outputwaiting(p->iop))
- logerror("Output for player %d lost", p->cnum);
- }
+ for (p = player_next(NULL); p; p = player_next(p))
+ logerror("Player %d lingers, output might be lost", p->cnum);
if (sig)
logerror("Server shutting down on signal %d", sig);