changeset 164:2660a5684515 HEAD

Added io_buffer_read_blocking() which can be used to read data blockingly, but with a specified timeout. Client fds are now nonblocking and use it with APPEND fixing a possible infinite wait if client didn't send any data.
author Timo Sirainen <tss@iki.fi>
date Fri, 06 Sep 2002 20:27:11 +0300
parents aeb4e8d48aa2
children 628b12562e4b
files src/imap/client.c src/lib-storage/index/index-save.c src/lib/iobuffer.c src/lib/iobuffer.h
diffstat 4 files changed, 129 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap/client.c	Fri Sep 06 18:11:31 2002 +0300
+++ b/src/imap/client.c	Fri Sep 06 20:27:11 2002 +0300
@@ -1,6 +1,7 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
+#include "network.h"
 #include "iobuffer.h"
 #include "commands.h"
 
@@ -12,6 +13,10 @@
 /* If we can't send a buffer in a minute, disconnect the client */
 #define CLIENT_OUTPUT_TIMEOUT (60*1000)
 
+/* If we don't soon receive expected data from client while processing
+   a command, disconnect the client */
+#define CLIENT_CMDINPUT_TIMEOUT CLIENT_OUTPUT_TIMEOUT
+
 /* Disconnect client when it sends too many bad commands */
 #define CLIENT_MAX_BAD_COMMANDS 20
 
@@ -23,12 +28,25 @@
 
 static void client_input(Client *client);
 
-static void client_send_timeout(Client *client)
+static void client_output_timeout(void *context,
+				  Timeout timeout __attr_unused__)
 {
+	Client *client = context;
+
 	io_buffer_close(client->inbuf);
 	io_buffer_close(client->outbuf);
 }
 
+static void client_input_timeout(void *context,
+				 Timeout timeout __attr_unused__)
+{
+	Client *client = context;
+
+	client_send_line(my_client, "* BYE Disconnected for inactivity "
+			 "while waiting for command data.");
+	io_buffer_close(client->outbuf);
+}
+
 Client *client_create(int hin, int hout, int socket, MailStorage *storage)
 {
 	Client *client;
@@ -39,9 +57,18 @@
 					 MAX_INBUF_SIZE);
 	client->outbuf = io_buffer_create(hout, default_pool, 0, 0);
 
-	io_buffer_set_send_blocking(client->outbuf, 4096,
-				    CLIENT_OUTPUT_TIMEOUT,
-				    (TimeoutFunc) client_send_timeout, client);
+	/* always use nonblocking I/O */
+	net_set_nonblock(hin, TRUE);
+	net_set_nonblock(hout, TRUE);
+
+	/* set timeout for sending data */
+	io_buffer_set_blocking(client->outbuf, 4096, CLIENT_OUTPUT_TIMEOUT,
+			       client_output_timeout, client);
+
+	/* set timeout for reading expected data (eg. APPEND). This is
+	   different from the actual idle time. */
+	io_buffer_set_blocking(client->inbuf, 0, CLIENT_CMDINPUT_TIMEOUT,
+			       client_input_timeout, client);
 
 	client->inbuf->file = !socket;
 	client->outbuf->file = !socket;
--- a/src/lib-storage/index/index-save.c	Fri Sep 06 18:11:31 2002 +0300
+++ b/src/lib-storage/index/index-save.c	Fri Sep 06 20:27:11 2002 +0300
@@ -47,9 +47,7 @@
 	last_cr = FALSE;
 
 	while (data_size > 0) {
-		/* FIXME: we're using nonblocking I/O, meaning if there's no
-		   data we'll eat all CPU! */
-		ret = io_buffer_read(buf);
+		ret = io_buffer_read_blocking(buf, (unsigned int)-1);
 		if (ret < 0) {
 			mail_storage_set_critical(storage,
 						  "Error reading mail: %m");
--- a/src/lib/iobuffer.c	Fri Sep 06 18:11:31 2002 +0300
+++ b/src/lib/iobuffer.c	Fri Sep 06 20:27:11 2002 +0300
@@ -32,6 +32,18 @@
 
 #include <unistd.h>
 
+typedef struct {
+	IOLoop ioloop;
+	IOBuffer *outbuf;
+
+	const char *data;
+	unsigned int size;
+	IOBuffer *inbuf;
+
+	int timeout;
+	int last_block;
+} IOBufferBlockContext;
+
 static unsigned int mmap_pagesize = 0;
 static unsigned int mmap_pagemask = 0;
 
@@ -183,18 +195,17 @@
 	buf->max_buffer_size = max_size;
 }
 
-void io_buffer_set_send_blocking(IOBuffer *buf, unsigned int max_size,
-				 int timeout_msecs, TimeoutFunc timeout_func,
-				 void *context)
+void io_buffer_set_blocking(IOBuffer *buf, unsigned int max_size,
+			    int timeout_msecs, TimeoutFunc timeout_func,
+			    void *context)
 {
-	i_assert(!buf->receive);
-
-	buf->transmit = TRUE;
 	buf->timeout_msecs = timeout_msecs;
 	buf->timeout_func = timeout_func;
 	buf->timeout_context = context;
 	buf->blocking = max_size > 0;
-	buf->max_buffer_size = max_size;
+
+	if (max_size != 0)
+		buf->max_buffer_size = max_size;
 }
 
 static int my_write(int fd, const void *buf, unsigned int size)
@@ -263,18 +274,6 @@
         return TRUE;
 }
 
-typedef struct {
-	IOLoop ioloop;
-	IOBuffer *outbuf;
-
-	const char *data;
-	unsigned int size;
-	IOBuffer *inbuf;
-
-	int timeout;
-	int last_block;
-} IOBufferBlockContext;
-
 static void block_loop_send(IOBufferBlockContext *ctx)
 {
 	int ret;
@@ -301,6 +300,8 @@
 		io_loop_stop(ctx->ioloop);
 }
 
+/* this can be called with both io_buffer_ioloop() or
+   io_buffer_read_blocking() */
 static void block_loop_timeout(void *context, Timeout timeout __attr_unused__)
 {
 	IOBufferBlockContext *ctx = context;
@@ -660,6 +661,11 @@
 	return buf->buffer_size;
 }
 
+int io_buffer_read(IOBuffer *buf)
+{
+        return io_buffer_read_max(buf, UINT_MAX);
+}
+
 int io_buffer_read_max(IOBuffer *buf, unsigned int size)
 {
 	int ret;
@@ -714,9 +720,58 @@
         return ret;
 }
 
-int io_buffer_read(IOBuffer *buf)
+static void io_read_data(void *context, int fd __attr_unused__,
+			 IO io __attr_unused__)
+{
+	IOBufferBlockContext *ctx = context;
+
+	if (io_buffer_read_max(ctx->inbuf, ctx->size) != 0) {
+		/* got data / error */
+		io_loop_stop(ctx->ioloop);
+	}
+}
+
+int io_buffer_read_blocking(IOBuffer *buf, unsigned int size)
 {
-        return io_buffer_read_max(buf, UINT_MAX);
+        IOBufferBlockContext ctx;
+	Timeout to;
+	int ret;
+
+	/* first check if we can get some data */
+	ret = io_buffer_read_max(buf, size);
+	if (ret != 0)
+		return ret;
+
+	/* blocking now */
+
+	/* create a new I/O loop */
+	memset(&ctx, 0, sizeof(ctx));
+	ctx.ioloop = io_loop_create();
+	ctx.inbuf = buf;
+	ctx.size = size;
+
+	buf->io = io_add(buf->fd, IO_READ, io_read_data, &ctx);
+	to = buf->timeout_msecs <= 0 ? NULL :
+		timeout_add(buf->timeout_msecs, block_loop_timeout, &ctx);
+
+	io_loop_run(ctx.ioloop);
+
+	if (buf->io != NULL) {
+		io_remove(buf->io);
+		buf->io = NULL;
+	}
+
+	if (to != NULL) {
+		if (ctx.timeout && buf->timeout_func != NULL) {
+			/* call user-given timeout function */
+			buf->timeout_func(buf->timeout_context, to);
+		}
+		timeout_remove(to);
+	}
+
+	io_loop_destroy(ctx.ioloop);
+
+	return buf->pos > buf->skip ? 1 : -1;
 }
 
 void io_buffer_skip(IOBuffer *buf, uoff_t size)
--- a/src/lib/iobuffer.h	Fri Sep 06 18:11:31 2002 +0300
+++ b/src/lib/iobuffer.h	Fri Sep 06 20:27:11 2002 +0300
@@ -70,13 +70,22 @@
 IOBuffer *io_buffer_set_pool(IOBuffer *buf, Pool pool);
 /* Change the maximum size for buffer to grow. */
 void io_buffer_set_max_size(IOBuffer *buf, unsigned int max_size);
-/* Change output buffer's blocking state. When buffer reaches max_size,
-   it will block until all the data has been sent or timeout has been
-   reached. Setting max_size to 0 disables this (default). Setting
-   timeout_msecs to 0 may block infinitely. */
-void io_buffer_set_send_blocking(IOBuffer *buf, unsigned int max_size,
-				 int timeout_msecs, TimeoutFunc timeout_func,
-				 void *context);
+/* Change buffer's blocking state. The blocking state in fd itself isn't
+   changed, and it's not needed to be blocking. This affects two things:
+
+   When buffer reaches max_size, it will block until all the data has been
+   sent or timeout has been reached. Setting max_size to 0 disables this
+   (default). Setting timeout_msecs to 0 may block infinitely, or until
+   socket is closed.
+
+   Sets the timeout for io_buffer_read_blocking(). If max_size is non-zero,
+   it acts the same as io_buffer_set_max_size().
+
+   timeout_func is called with both cases when timeout occurs.
+*/
+void io_buffer_set_blocking(IOBuffer *buf, unsigned int max_size,
+			    int timeout_msecs, TimeoutFunc timeout_func,
+			    void *context);
 
 /* Set TCP_CORK on if supported, ie. don't send out partial frames.
    io_buffer_send_flush() removes the cork. */
@@ -103,6 +112,11 @@
 int io_buffer_read(IOBuffer *buf);
 /* Like io_buffer_read(), but don't read more than specified size. */
 int io_buffer_read_max(IOBuffer *buf, unsigned int size);
+/* Blocking read, doesn't return until at least one byte is read, or until
+   socket is disconnected or timeout has occured. Note that the fd must be
+   nonblocking, or the timeout doesn't work. If you don't want limit size,
+   set it to (unsigned int)-1. Returns 1 if data was found, -1 if error. */
+int io_buffer_read_blocking(IOBuffer *buf, unsigned int size);
 /* Skip forward a number of bytes */
 void io_buffer_skip(IOBuffer *buf, uoff_t size);
 /* Seek to specified position from beginning of file. This works only for