From: Martin Mares Date: Wed, 28 Jul 2010 22:30:10 +0000 (+0200) Subject: Main: Split off block I/O X-Git-Tag: v5.0~129^2~28 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=afcc58361781c05d66fe37ea2bccd53edf8f7aa5;p=libucw.git Main: Split off block I/O Block-based I/O is no longer hard-wired in the mainloop itself, it became a separate module built on the top of main_file. While the timer embedded in main_block_io is somewhat illogical, I have decided to keep it to make porting of old code easier. I plan to add another I/O layer in the future, which will handle more complex buffering capable of reading lines and other variable-size data structures. --- diff --git a/ucw/Makefile b/ucw/Makefile index bfb039a5..c419f1ec 100644 --- a/ucw/Makefile +++ b/ucw/Makefile @@ -1,4 +1,4 @@ -# Makefile for the UCW Library (c) 1997--2009 Martin Mares +# Makefile for the UCW Library (c) 1997--2010 Martin Mares DIRS+=ucw LIBUCW=$(o)/ucw/libucw.pc @@ -23,7 +23,8 @@ LIBUCW_MODS= \ prime primetable random timer randomkey \ bit-ffs bit-fls \ url \ - mainloop exitstatus runcmd sighandler \ + mainloop main-block \ + exitstatus runcmd sighandler \ lizard lizard-safe adler32 \ md5 sha1 sha1-hmac \ base64 base224 \ diff --git a/ucw/main-block.c b/ucw/main-block.c new file mode 100644 index 00000000..7363c2c3 --- /dev/null +++ b/ucw/main-block.c @@ -0,0 +1,134 @@ +/* + * UCW Library -- Main Loop: Block I/O + * + * (c) 2004--2010 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#define LOCAL_DEBUG + +#include "ucw/lib.h" +#include "ucw/mainloop.h" + +#include +#include +#include +#include +#include + +static void +block_io_timer_expired(struct main_timer *tm) +{ + struct main_block_io *bio = tm->data; + timer_del(&bio->timer); + if (bio->error_handler) + bio->error_handler(bio, MFERR_TIMEOUT); +} + +void +block_io_add(struct main_block_io *bio, int fd) +{ + bio->file.fd = fd; + file_add(&bio->file); + bio->timer.handler = block_io_timer_expired; + bio->timer.data = bio; +} + +void +block_io_del(struct main_block_io *bio) +{ + timer_del(&bio->timer); + file_del(&bio->file); +} + +static int +block_io_read_handler(struct main_file *fi) +{ + struct main_block_io *bio = (struct main_block_io *) fi; + + while (bio->rpos < bio->rlen) + { + int l = read(fi->fd, bio->rbuf + bio->rpos, bio->rlen - bio->rpos); + DBG("BIO: FD %d: read %d", fi->fd, l); + if (l < 0) + { + if (errno != EINTR && errno != EAGAIN && bio->error_handler) + bio->error_handler(bio, MFERR_READ); + return 0; + } + else if (!l) + break; + bio->rpos += l; + } + DBG("BIO: FD %d done read %d of %d", fi->fd, bio->rpos, bio->rlen); + fi->read_handler = NULL; + file_chg(fi); + bio->read_done(bio); + return 1; +} + +static int +block_io_write_handler(struct main_file *fi) +{ + struct main_block_io *bio = (struct main_block_io *) fi; + + while (bio->wpos < bio->wlen) + { + int l = write(fi->fd, bio->wbuf + bio->wpos, bio->wlen - bio->wpos); + DBG("BIO: FD %d: write %d", fi->fd, l); + if (l < 0) + { + if (errno != EINTR && errno != EAGAIN && bio->error_handler) + bio->error_handler(bio, MFERR_WRITE); + return 0; + } + bio->wpos += l; + } + DBG("BIO: FD %d done write %d", fi->fd, bio->wpos); + fi->write_handler = NULL; + file_chg(fi); + bio->write_done(bio); + return 1; +} + +void +block_io_read(struct main_block_io *bio, void *buf, uns len) +{ + ASSERT(bio->file.n.next); + if (len) + { + bio->file.read_handler = block_io_read_handler; + bio->rbuf = buf; + bio->rpos = 0; + bio->rlen = len; + } + else + { + bio->file.read_handler = NULL; + bio->rbuf = NULL; + bio->rpos = bio->rlen = 0; + } + file_chg(&bio->file); +} + +void +block_io_write(struct main_block_io *bio, void *buf, uns len) +{ + ASSERT(bio->file.n.next); + if (len) + { + bio->file.write_handler = block_io_write_handler; + bio->wbuf = buf; + bio->wpos = 0; + bio->wlen = len; + } + else + { + bio->file.write_handler = NULL; + bio->wbuf = NULL; + bio->wpos = bio->wlen = 0; + } + file_chg(&bio->file); +} diff --git a/ucw/mainloop.c b/ucw/mainloop.c index 15e71407..7b5d5045 100644 --- a/ucw/mainloop.c +++ b/ucw/mainloop.c @@ -206,15 +206,6 @@ timer_del(struct main_timer *tm) timer_add(tm, 0); } -static void -file_timer_expired(struct main_timer *tm) -{ - struct main_file *fi = tm->data; - timer_del(&fi->timer); - if (fi->error_handler) - fi->error_handler(fi, MFERR_TIMEOUT); -} - void file_add(struct main_file *fi) { @@ -223,8 +214,6 @@ file_add(struct main_file *fi) DBG("MAIN: Adding file %p (fd=%d)", fi, fi->fd); ASSERT(!fi->n.next); clist_add_tail(&m->file_list, &fi->n); - fi->timer.handler = file_timer_expired; - fi->timer.data = fi; m->file_cnt++; m->poll_table_obsolete = 1; if (fcntl(fi->fd, F_SETFL, O_NONBLOCK) < 0) @@ -252,106 +241,12 @@ file_del(struct main_file *fi) DBG("MAIN: Deleting file %p (fd=%d)", fi, fi->fd); ASSERT(fi->n.next); - timer_del(&fi->timer); clist_remove(&fi->n); m->file_cnt--; m->poll_table_obsolete = 1; fi->n.next = fi->n.prev = NULL; } -static int -file_read_handler(struct main_file *fi) -{ - while (fi->rpos < fi->rlen) - { - int l = read(fi->fd, fi->rbuf + fi->rpos, fi->rlen - fi->rpos); - DBG("MAIN: FD %d: read %d", fi->fd, l); - if (l < 0) - { - if (errno != EINTR && errno != EAGAIN && fi->error_handler) - fi->error_handler(fi, MFERR_READ); - return 0; - } - else if (!l) - break; - fi->rpos += l; - } - DBG("MAIN: FD %d done read %d of %d", fi->fd, fi->rpos, fi->rlen); - fi->read_handler = NULL; - file_chg(fi); - fi->read_done(fi); - return 1; -} - -static int -file_write_handler(struct main_file *fi) -{ - while (fi->wpos < fi->wlen) - { - int l = write(fi->fd, fi->wbuf + fi->wpos, fi->wlen - fi->wpos); - DBG("MAIN: FD %d: write %d", fi->fd, l); - if (l < 0) - { - if (errno != EINTR && errno != EAGAIN && fi->error_handler) - fi->error_handler(fi, MFERR_WRITE); - return 0; - } - fi->wpos += l; - } - DBG("MAIN: FD %d done write %d", fi->fd, fi->wpos); - fi->write_handler = NULL; - file_chg(fi); - fi->write_done(fi); - return 1; -} - -void -file_read(struct main_file *fi, void *buf, uns len) -{ - ASSERT(fi->n.next); - if (len) - { - fi->read_handler = file_read_handler; - fi->rbuf = buf; - fi->rpos = 0; - fi->rlen = len; - } - else - { - fi->read_handler = NULL; - fi->rbuf = NULL; - fi->rpos = fi->rlen = 0; - } - file_chg(fi); -} - -void -file_write(struct main_file *fi, void *buf, uns len) -{ - ASSERT(fi->n.next); - if (len) - { - fi->write_handler = file_write_handler; - fi->wbuf = buf; - fi->wpos = 0; - fi->wlen = len; - } - else - { - fi->write_handler = NULL; - fi->wbuf = NULL; - fi->wpos = fi->wlen = 0; - } - file_chg(fi); -} - -void -file_set_timeout(struct main_file *fi, timestamp_t expires) -{ - ASSERT(fi->n.next); - timer_add(&fi->timer, expires); -} - void file_close_all(void) { @@ -584,9 +479,9 @@ main_debug_context(struct main_context *m UNUSED) } msg(L_DEBUG, "\tActive files:"); CLIST_FOR_EACH(struct main_file *, fi, m->file_list) - msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, eh %p, expires %lld, data %p)", - fi, fi->fd, fi->read_handler, fi->write_handler, fi->error_handler, - (long long)(fi->timer.expires ? fi->timer.expires - m->now : 999999), fi->data); + msg(L_DEBUG, "\t\t%p (fd %d, rh %p, wh %p, data %p)", + fi, fi->fd, fi->read_handler, fi->write_handler, fi->data); + // FIXME: Can we display status of block_io requests somehow? msg(L_DEBUG, "\tActive hooks:"); CLIST_FOR_EACH(struct main_hook *, ho, m->hook_done_list) msg(L_DEBUG, "\t\t%p (func %p, data %p)", ho, ho->handler, ho->data); @@ -706,34 +601,34 @@ main_loop(void) #ifdef TEST static struct main_process mp; -static struct main_file fin, fout; +static struct main_block_io fin, fout; static struct main_hook hook; static struct main_timer tm; static struct main_signal sg; static byte rb[16]; -static void dread(struct main_file *fi) +static void dread(struct main_block_io *bio) { - if (fi->rpos < fi->rlen) + if (bio->rpos < bio->rlen) { msg(L_INFO, "Read EOF"); - file_del(fi); + block_io_del(bio); } else { msg(L_INFO, "Read done"); - file_read(fi, rb, sizeof(rb)); + block_io_read(bio, rb, sizeof(rb)); } } -static void derror(struct main_file *fi, int cause) +static void derror(struct main_block_io *bio, int cause) { msg(L_INFO, "Error: %m !!! (cause %d)", cause); - file_del(fi); + block_io_del(bio); } -static void dwrite(struct main_file *fi UNUSED) +static void dwrite(struct main_block_io *bio UNUSED) { msg(L_INFO, "Write done"); } @@ -775,17 +670,15 @@ main(void) log_init(NULL); main_init(); - fin.fd = 0; fin.read_done = dread; fin.error_handler = derror; - file_add(&fin); - file_read(&fin, rb, sizeof(rb)); + block_io_add(&fin, 0); + block_io_read(&fin, rb, sizeof(rb)); - fout.fd = 1; fout.write_done = dwrite; fout.error_handler = derror; - file_add(&fout); - file_write(&fout, "Hello, world!\n", 14); + block_io_add(&fout, 1); + block_io_write(&fout, "Hello, world!\n", 14); hook.handler = dhook; hook_add(&hook); diff --git a/ucw/mainloop.h b/ucw/mainloop.h index 6f67414a..95565d13 100644 --- a/ucw/mainloop.h +++ b/ucw/mainloop.h @@ -188,30 +188,10 @@ struct main_file { int fd; /* [*] File descriptor */ int (*read_handler)(struct main_file *fi); /* [*] To be called when ready for reading/writing; must call file_chg() afterwards */ int (*write_handler)(struct main_file *fi); - void (*error_handler)(struct main_file *fi, int cause); /* [*] Handler to call on errors */ void *data; /* [*] Data for use by the handlers */ - byte *rbuf; /* Read/write pointers for use by file_read/write */ - uns rpos, rlen; - byte *wbuf; - uns wpos, wlen; - void (*read_done)(struct main_file *fi); /* [*] Called when file_read is finished; rpos < rlen if EOF */ - void (*write_done)(struct main_file *fi); /* [*] Called when file_write is finished */ - struct main_timer timer; struct pollfd *pollfd; }; -/** - * Specifies when or why an error happened. This is passed to the error handler. - * `errno` is still set to the original source of error. The only exception - * is `MFERR_TIMEOUT`, in which case `errno` is not set and the only possible - * cause of it is timeout on the file descriptor (see @file_set_timeout). - **/ -enum main_file_err_cause { - MFERR_READ, - MFERR_WRITE, - MFERR_TIMEOUT -}; - /** * Inserts a <> structure into the mainloop to be * watched for activity. You can call this at any time, even inside a handler @@ -233,7 +213,41 @@ void file_chg(struct main_file *fi); **/ void file_del(struct main_file *fi); /** - * Asks the mainloop to read @len bytes of data from @fi into @buf. + * Closes all file descriptors known to mainloop. Often used between fork() + * and exec(). + **/ +void file_close_all(void); + +struct main_block_io { + struct main_file file; + byte *rbuf; /* Read/write pointers for use by file_read/write */ + uns rpos, rlen; + byte *wbuf; + uns wpos, wlen; + void (*read_done)(struct main_block_io *bio); /* [*] Called when file_read is finished; rpos < rlen if EOF */ + void (*write_done)(struct main_block_io *bio); /* [*] Called when file_write is finished */ + void (*error_handler)(struct main_block_io *bio, int cause); /* [*] Handler to call on errors */ + struct main_timer timer; + void *data; /* [*] Data for use by the handlers */ +}; + +void block_io_add(struct main_block_io *bio, int fd); +void block_io_del(struct main_block_io *bio); + +/** + * Specifies when or why an error happened. This is passed to the error handler. + * `errno` is still set to the original source of error. The only exception + * is `MFERR_TIMEOUT`, in which case `errno` is not set and the only possible + * cause of it is timeout on the file descriptor (see @file_set_timeout). + **/ +enum block_io_err_cause { + MFERR_READ, + MFERR_WRITE, + MFERR_TIMEOUT +}; + +/** + * Asks the mainloop to read @len bytes of data from @bio into @buf. * It cancels any previous unfinished read requested this way and overwrites * `read_handler`. * @@ -245,9 +259,9 @@ void file_del(struct main_file *fi); * You can use a call with zero @len to cancel current read, but all read data * will be thrown away. **/ -void file_read(struct main_file *fi, void *buf, uns len); +void block_io_read(struct main_block_io *bio, void *buf, uns len); /** - * Requests that the mainloop writes @len bytes of data from @buf to @fi. + * Requests that the mainloop writes @len bytes of data from @buf to @bio. * Cancels any previous unfinished write and overwrites `write_handler`. * * When it is written, write_done() handler is called. @@ -257,11 +271,11 @@ void file_read(struct main_file *fi, void *buf, uns len); * If you call it with zero @len, it will cancel the previous write, but note * some data may already be written. **/ -void file_write(struct main_file *fi, void *buf, uns len); +void block_io_write(struct main_block_io *bio, void *buf, uns len); /** - * Sets a timer for a file @fi. If the timer is not overwritten or disabled + * Sets a timer for a file @bio. If the timer is not overwritten or disabled * until @expires, the file timeouts and error_handler() is called with - * <>. + * <>. * * The mainloop does not disable or reset it, when something happens, it just * bundles a timer with the file. If you want to watch for inactivity, it is @@ -269,7 +283,7 @@ void file_write(struct main_file *fi, void *buf, uns len); * * The @expires parameter is absolute (add <> if you * need relative). The call and overwrites previously set timeout. Value of `0` - * disables the timeout (the <> will + * disables the timeout (the <> will * not trigger). * * The use-cases for this are mainly sockets or pipes, when: @@ -279,19 +293,14 @@ void file_write(struct main_file *fi, void *buf, uns len); * - You want to enforce answer in a given time (for example authentication). * - You give maximum time for a whole connection. **/ -void file_set_timeout(struct main_file *fi, timestamp_t expires); -/** - * Closes all file descriptors known to mainloop. Often used between fork() - * and exec(). - **/ -void file_close_all(void); +void block_io_set_timeout(struct main_block_io *bio, timestamp_t expires); /*** * [[hooks]] * Loop hooks * ---------- * - * The hooks are called whenever the mainloop perform an iteration. + * The hooks are called whenever the mainloop performs an iteration. * You can shutdown the mainloop from within them or request an iteration * to happen without sleeping (just poll, no waiting for events). ***/