From: Timo Sirainen Date: Thu, 3 Apr 2014 16:53:13 +0000 (+0300) Subject: Added istream-timeout, which triggers I/O event and fails with ETIMEDOUT after the... X-Git-Tag: 2.2.13.rc1~174 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=854b4074ac77a138b3983d72510b4d8779d15040;p=thirdparty%2Fdovecot%2Fcore.git Added istream-timeout, which triggers I/O event and fails with ETIMEDOUT after the timeout. --- diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 054996b608..a6e9e53f2a 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -70,6 +70,7 @@ liblib_la_SOURCES = \ istream-seekable.c \ istream-sized.c \ istream-tee.c \ + istream-timeout.c \ ioloop.c \ ioloop-iolist.c \ ioloop-notify-none.c \ @@ -197,6 +198,7 @@ headers = \ istream-seekable.h \ istream-sized.h \ istream-tee.h \ + istream-timeout.h \ ioloop.h \ ioloop-iolist.h \ ioloop-private.h \ diff --git a/src/lib/istream-timeout.c b/src/lib/istream-timeout.c new file mode 100644 index 0000000000..874ac7cb1d --- /dev/null +++ b/src/lib/istream-timeout.c @@ -0,0 +1,123 @@ +/* Copyright (c) 2014 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "time-util.h" +#include "istream-private.h" +#include "istream-timeout.h" + +struct timeout_istream { + struct istream_private istream; + + struct timeout *to; + struct timeval last_read_timestamp; + + unsigned int timeout_msecs; + bool update_timestamp; +}; + +static void i_stream_timeout_close(struct iostream_private *stream, + bool close_parent) +{ + struct timeout_istream *tstream = (struct timeout_istream *)stream; + + if (tstream->to != NULL) + timeout_remove(&tstream->to); + if (close_parent) + i_stream_close(tstream->istream.parent); +} + +static void i_stream_timeout_switch_ioloop(struct istream_private *stream) +{ + struct timeout_istream *tstream = (struct timeout_istream *)stream; + + if (tstream->to != NULL) + tstream->to = io_loop_move_timeout(&tstream->to); +} + +static void i_stream_timeout(struct timeout_istream *tstream) +{ + unsigned int msecs; + int diff; + + timeout_remove(&tstream->to); + + diff = timeval_diff_msecs(&ioloop_timeval, &tstream->last_read_timestamp); + if (diff < (int)tstream->timeout_msecs) { + /* we haven't reached the read timeout yet, update it */ + if (diff < 0) + diff = 0; + tstream->to = timeout_add(tstream->timeout_msecs - diff, + i_stream_timeout, tstream); + return; + } + + msecs = tstream->timeout_msecs % 1000; + io_stream_set_error(&tstream->istream.iostream, + "Read timeout in %u%s s after %"PRIuUOFF_T" bytes", + tstream->timeout_msecs/1000, + msecs == 0 ? "" : t_strdup_printf(".%u", msecs), + tstream->istream.istream.v_offset); + tstream->istream.istream.stream_errno = ETIMEDOUT; + + i_stream_set_input_pending(tstream->istream.parent, TRUE); +} + +static ssize_t +i_stream_timeout_read(struct istream_private *stream) +{ + struct timeout_istream *tstream = (struct timeout_istream *)stream; + ssize_t ret; + + i_stream_seek(stream->parent, stream->parent_start_offset + + stream->istream.v_offset); + + ret = i_stream_read_copy_from_parent(&stream->istream); + if (ret < 0) { + /* failed */ + } else if (tstream->to == NULL) { + /* first read. add the timeout here instead of in init + in case the stream is created long before it's actually + read from. */ + tstream->to = tstream->timeout_msecs == 0 ? NULL : + timeout_add(tstream->timeout_msecs, + i_stream_timeout, tstream); + tstream->update_timestamp = TRUE; + tstream->last_read_timestamp = ioloop_timeval; + } else if (ret > 0 && tstream->to != NULL) { + /* we read something, reset the timeout */ + timeout_reset(tstream->to); + /* make sure we get called again on the next ioloop run. + this updates the timeout to the timestamp where we actually + would have wanted to start waiting for more data (so if + there is long-running code outside the ioloop it's not + counted) */ + tstream->update_timestamp = TRUE; + tstream->last_read_timestamp = ioloop_timeval; + i_stream_set_input_pending(&stream->istream, TRUE); + } else if (tstream->update_timestamp) { + tstream->update_timestamp = FALSE; + tstream->last_read_timestamp = ioloop_timeval; + } + return ret; +} + +struct istream * +i_stream_create_timeout(struct istream *input, unsigned int timeout_msecs) +{ + struct timeout_istream *tstream; + + tstream = i_new(struct timeout_istream, 1); + tstream->timeout_msecs = timeout_msecs; + tstream->istream.max_buffer_size = input->real_stream->max_buffer_size; + tstream->istream.stream_size_passthrough = TRUE; + + tstream->istream.read = i_stream_timeout_read; + tstream->istream.switch_ioloop = i_stream_timeout_switch_ioloop; + tstream->istream.iostream.close = i_stream_timeout_close; + + tstream->istream.istream.blocking = input->blocking; + tstream->istream.istream.seekable = input->seekable; + return i_stream_create(&tstream->istream, input, + i_stream_get_fd(input)); +} diff --git a/src/lib/istream-timeout.h b/src/lib/istream-timeout.h new file mode 100644 index 0000000000..a26318a9dd --- /dev/null +++ b/src/lib/istream-timeout.h @@ -0,0 +1,9 @@ +#ifndef ISTREAM_TIMEOUT_H +#define ISTREAM_TIMEOUT_H + +/* Return ETIMEDOUT error if read() doesn't return anything for timeout_msecs. + If timeout_msecs=0, there is no timeout. */ +struct istream * +i_stream_create_timeout(struct istream *input, unsigned int timeout_msecs); + +#endif