X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;ds=inline;f=ucw%2Fmainloop.h;h=0bf4e2edfadc10ea4577ecd41b8bf22470db0c7a;hb=342e0c3edeacf4eecd03da36c879ca817c64a0f3;hp=b7824db8222a266781e6c517961b000aaa7bb7a6;hpb=ad920945145a18895ef391511c92ef42e0e4c3d7;p=libucw.git diff --git a/ucw/mainloop.h b/ucw/mainloop.h index b7824db8..0bf4e2ed 100644 --- a/ucw/mainloop.h +++ b/ucw/mainloop.h @@ -17,9 +17,10 @@ * Conventions * ----------- * - * The description of structures contain some fields marked as `[*]`. - * These are the only ones that are user defined. The rest is for - * internal use and you must initialize it to zeroes. + * The descriptions of structures contain some fields marked with `[*]`. + * These are the only ones that are intended to be manipulated by the user. + * The remaining fields serve for internal use only and you must initialize them + * to zeroes. ***/ /*** @@ -27,42 +28,44 @@ * Time manipulation * ----------------- * - * This part allows you to know the current time and request + * This part allows you to get the current time and request * to have your function called when the time comes. ***/ -extern timestamp_t main_now; /** Current time in milliseconds since UNIX epoch. See @main_get_time(). **/ +extern timestamp_t main_now; /** Current time in milliseconds since the UNIX epoch. See @main_get_time(). **/ extern ucw_time_t main_now_seconds; /** Current time in seconds since the epoch. **/ -extern clist main_timer_list, main_file_list, main_hook_list, main_process_list; +extern timestamp_t main_idle_time; /** Total time in milliseconds spent in the poll() call. **/ +extern clist main_file_list, main_hook_list, main_hook_done_list, main_process_list; /** * This is a description of a timer. - * You fill it with a handler function, any user-defined data and - * add it using @timer_add(). + * You fill in a handler function, any user-defined data you wish to pass + * to the handler, and then you invoke @timer_add(). * - * The handler() function must add it again or delete it with - * @timer_del(). + * The handler() function must either call @timer_del() to delete the timer, + * or call @timer_add() with a different expiration time. **/ struct main_timer { cnode n; timestamp_t expires; + uns index; void (*handler)(struct main_timer *tm); /* [*] Function to be called when the timer expires. */ void *data; /* [*] Data for use by the handler */ }; /** - * Adds a new timer into the mainloop to be watched and called, - * when it expires. It can be used to modify an already running - * timer. + * Adds a new timer into the mainloop to be watched and called + * when it expires. It can also be used to modify an already running + * timer. It is permitted (and usual) to call this function from the + * timer's handler itself if you want the timer to trigger again. * - * The @expire parameter is absolute -- you may use - * <>, if you need it relative to now. + * The @expire parameter is absolute, just add <> if you need a relative timer. **/ void timer_add(struct main_timer *tm, timestamp_t expires); /** - * Removes a timer from the watched ones. You need to call this, when - * the timer expires and you do not want to use it any more. It can be - * used to remove a still active timer too. + * Removes a timer from the active ones. It is permitted (and usual) to call + * this function from the timer's handler itself if you want to deactivate + * the timer. **/ void timer_del(struct main_timer *tm); @@ -80,12 +83,38 @@ void main_get_time(void); * ---------------------------- * * You can let the mainloop watch over a set of file descriptors - * for changes. + * for a changes. * - * //TODO: This probably needs some example how the handlers can be - * //used, describe the use of this part of module. + * It supports two ways of use. With the first one, you provide + * low-level handlers for reading and writing (`read_handler` and + * `write_handler`). They will be called every time the file descriptor + * is ready to be read from or written to. + * + * Return non-zero if you want to get the handler called again right now (you + * handled a block of data and expect more). If you return `0`, the hook will + * be called again in the next iteration, if it is still ready to be read/written. + * + * This way is suitable for listening sockets, interactive connections, where + * you need to parse everything that comes right away and similar cases. + * + * The second way is to ask mainloop to read or write a buffer of data. You + * provide a `read_done` or `write_done` handler respectively and call @file_read() + * or @file_write(). This is handy for data connections where you need to transfer + * data between two endpoints or for binary connections where the size of message + * is known in advance. + * + * It is possible to combine both methods, but it may be tricky to do it right. + * + * Both ways use `error_handler` to notify you about errors. ***/ +/** + * If you want mainloop to watch a file descriptor, fill at last `fd` into this + * structure. To get any useful information from the mainloop, provide some handlers + * too. + * + * After that, insert it into the mainloop by calling @file_add(). + **/ struct main_file { cnode n; int fd; /* [*] File descriptor */ @@ -97,34 +126,106 @@ struct main_file { 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 (*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 + * (of course for a different file descriptor than the one of the handler). + **/ void file_add(struct main_file *fi); +/** + * Tells the mainloop the file has changed its state. Call it whenever you + * change any of the handlers. + * + * Can be called only on active files (only the ones added by @file_add()). + **/ void file_chg(struct main_file *fi); +/** + * Removes a file from the watched set. You have to call this on closed files + * too, since the mainloop does not handle close in any way. + * + * Can be called from a handler. + **/ void file_del(struct main_file *fi); +/** + * Asks the mainloop to read @len bytes of data from @fi into @buf. + * It cancels any previous unfinished read requested this way and overwrites + * `read_handler`. + * + * When the read is done, read_done() handler is called. If an EOF occurred, + * `rpos < rlen` (eg. not all data were read). + * + * Can be called from a handler. + * + * 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); +/** + * Requests that the mainloop writes @len bytes of data from @buf to @fi. + * Cancels any previous unfinished write and overwrites `write_handler`. + * + * When it is written, write_done() handler is called. + * + * Can be called from a handler. + * + * 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); +/** + * Sets a timer for a file @fi. 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 + * your task to reset it whenever your handler is called. + * + * 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 + * not trigger). + * + * The use-cases for this are mainly sockets or pipes, when: + * + * - You want to drop inactive connections (no data come or go for a given time, not + * incomplete messages). + * - 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); -void file_close_all(void); /* Close all known main_file's; frequently used after fork() */ +/** + * Closes all file descriptors known to mainloop. Often used between fork() + * and exec(). + **/ +void file_close_all(void); /*** * [[hooks]] * Loop hooks * ---------- * - * The hooks can be called whenever the mainloop perform an iteration. - * You can shutdown the mainloop from within them or request next call - * only when the loop is idle (for background operations). + * The hooks are called whenever the mainloop perform an iteration. + * You can shutdown the mainloop from within them or request an iteration + * to happen without sleeping (just poll, no waiting for events). ***/ /** @@ -132,6 +233,8 @@ void file_close_all(void); /* Close all known main_file's; frequently used aft * * The handler() must return one value from * <>. + * + * Fill with the hook and data and pass it to @hook_add(). **/ struct main_hook { cnode n; @@ -142,20 +245,29 @@ struct main_hook { /** * Return value of the hook handler(). * Specifies what should happen next. + * + * - `HOOK_IDLE` -- Let the loop sleep until something happens, call after that. + * - `HOOK_RETRY` -- Force the loop to perform another iteration without sleeping. + * This will cause calling of all the hooks again soon. + * - `HOOK_DONE` -- The loop will terminate if all hooks return this. + * - `HOOK_SHUTDOWN` -- Shuts down the loop. **/ enum main_hook_return { - HOOK_IDLE, /* Call again when the main loop becomes idle again */ - HOOK_RETRY, /* Call again as soon as possible */ - HOOK_DONE = -1, /* Shut down the main loop if all hooks return this value */ - HOOK_SHUTDOWN = -2 /* Shut down the main loop immediately */ + HOOK_IDLE, + HOOK_RETRY, + HOOK_DONE = -1, + HOOK_SHUTDOWN = -2 }; /** * Inserts a new hook into the loop. + * The hook will be scheduled at least once before next sleep. + * May be called from inside a hook handler too. **/ void hook_add(struct main_hook *ho); /** * Removes an existing hook from the loop. + * May be called from inside a hook handler (to delete itself or other hook). **/ void hook_del(struct main_hook *ho);