From ee8ea28aae2d27e37aa6bdecdaa61d851fcf494a Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 1 May 2010 23:37:24 +0200 Subject: [PATCH] add all thoughts and publish the idea Signed-off-by: Nico Schottelius --- ...oposal-for-the-io-select-poll-problem.mdwn | 151 ++++++++++++------ 1 file changed, 105 insertions(+), 46 deletions(-) diff --git a/blog/solution-proposal-for-the-io-select-poll-problem.mdwn b/blog/solution-proposal-for-the-io-select-poll-problem.mdwn index bedae13e..48e783a8 100644 --- a/blog/solution-proposal-for-the-io-select-poll-problem.mdwn +++ b/blog/solution-proposal-for-the-io-select-poll-problem.mdwn @@ -1,94 +1,153 @@ [[!meta title="Solution proposal for the io select/poll problem]] ## The situation -If you have used select(2) or poll(2) more than time, you may have noticed -the regular pattern that arrives again and again: - * The main task of a program is to listen and react on multiple input/output connections. - * For each i/o connection (file descriptor, **fd**), you have - * a function that opens the **fd** (Let's call it ***conn_open***.) - * another function to be executed if an event happens on the **fd** (***conn_handle***) - * After ***conn_handle*** has been called, the number of connections may have changed: - ***conn_handle*** may - * add (think of accept(2)) - * or remove (think of close(2)) connections - * ***conn_open*** and ***conn_handle*** are closely related (**conn_object**) +If you have used select(2) or poll(2) more than once, you may have noticed +the regular pattern that comes up again and again: + + * The main task of a program is to listen and react on multiple input/output connections. + * For each i/o connection (file descriptor), you have + * a function that opens the file descriptor (Let's call it ***conn_open***.) and + * another function to be executed if an event happens on the file descriptor + (***conn_handle***). + * After ***conn_handle*** has been called, the number of connections may have changed: + ***conn_handle*** may + * add (think of accept(2)) + * or remove (think of close(2)) connections. + * ***conn_open*** and ***conn_handle*** are closely related and belong to the + same "object" or code (***conn_object***). ## The problem Each and every time this situation occurs, you have to (re-)write code to handle that case. I have seen it in some applications I have -been writing, for instance [[software/ceofhack]] or -[[software/fui]]. +been writing, for instance [[software/ceofhack]] or [[software/fui]]. ## The solution proposal Write a solution to the problem [once and only once](http://c2.com/xp/OnceAndOnlyOnce.html), so you and me -[don't reapeat ourselves](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself)? +[don't reapeat ourselves](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself). First of all, begin with the obvious part: ### What do we have? -We have to bring together n times +We have to bring together **n times** * open functions - * file descriptors + * file descriptors on which the following events can happen: + * data is ready to be read from the file descriptor + * data can be written to the file descriptor + * an error occured on the file descriptor * handle functions Furthermore, we have - * one main loop that listens for events + * **one** main loop that listens for events ### How to connect them properly? -If every **conn_object** supports the following interface, it's -very easy to write a generic main loop: +I assume that every ***conn_object*** knows best, which function to use for +opening and handling and which type of event is interesting. - conn_object.open(connection_list) +Thus if we create a special function ***conn_open*** +for every ***conn_object*** that -Where ***connection_list*** consists of ***connection_entries*** like this: + * returns this information to the caller, the caller can create + * a list containing the needed information and + * loop over the event list and call the corresponding handler. +The ***conn_open*** function may look like this: + + conn_open(&connection_list) + +Where ***connection_list*** is a list of ***connection_entries*** like this: + + struct connection_list { + struct connection_entry *next; + } list; + + enum type { + IN, + OUT, + ERR + }; + struct connection_entry { - fd, - handler, - type, - } + int fd; + void (*handler)(struct connection_list *); + int type; + }; -The type attribute describes, which events to wait for: - - * input - * output - * errors - -The ***conn_object.open*** function can add or remove entries from the list. -Whether the list is an array, hash, or whatever may be implementation specific. +The ***conn.open*** function can add or remove entries from the list. +Whether the list is an array, linked list, hash or whatever may be implementation specific. ### The main loop -Before launching the main listener loop, we probably need to initialise -the list, but afterwards it's all up to the handlers: +Before launching the main listener loop, we need to initialise +the list and run the **conn_open** function of every **conn_object**: - connection_list = init_connection_list() +struct connection_list list = init_connection_list(); - # connect all conn_objects like this, which implicits opening the fd - conn_object.open(connection_list) +a_conn_open(&connection_list); +b_conn_open(&connection_list); - while(true) { - events_list = wait_for_event(connection_list) +Having done this, our main loop now looks pretty simple, doesn't it? - for(every_event_in(event_list)) { - call_corresponding_handler_for_event_from_connection_list(fd, connection_list) - } + while(1) { + /* create poll or select list from connection list, whatever you prefer */ + poll_or_select_struct = connection_list_topoll_or_select(&connection_list); + + changed_events = poll_or_select(&poll_or_select_struct); + + for(event = changed_events; event != NULL; event = event->next) { + find_handler(event->fd, &connection_list); + } } -Firstly we pass the **fd**, because one handler may be registered for more -one connection. +The function ***exec_handler*** would search for the registered handler +of the changed file descriptor, which could look like this: -Secondly we add the connection_list, because the handler may decide to add + conn_handle(fd, &connection_list) + +Firstly we pass the **fd**, because one handler may be registered for more +than one connection. +Secondly we add the ***connection_list***, because the handler may add more connections or remove itself. +Although I'm mainly speaking about ***poll*** and ***select***, the idea also +applies to [kqueue](http://people.freebsd.org/~jlemon/papers/kqueue.pdf), +[epoll](http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html) +and co. +## Further thoughts + +### Combine handle and open functions + +For a second I thought the functions ***conn_handle*** and +***conn_open*** could be merged into a single function, +which would get a negative file descriptor, if called the first +time. +But as this means the ***conn_handle*** would need to check every +time it is called whether the to call the open part or not, +this is probably not a good idea. + +### Creating an implementation + +I am currently actively (!) working on [[fui|software/fui]] and +think about creating an implementation in ruby and if it works +fine, another one in C for [[ceofhack|software/ceofhack]]. + +### Getting feedback + +I would appreciate any feedback regading this idea, whether the +problem is no problem at all, has been solved before or the idea +may be a solution for your problem, too: You can contact me for +this special post at +**nico-io-poll-select-idea** (near) **schottelius.org**. + + +[[!tag programming unix]]