[[!meta title="Solution proposal for the io select/poll problem"]] ## The situation 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]]. ## 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). First of all, begin with the obvious part: ### What do we have? We have to bring together **n times** * open functions * 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 ### How to connect them properly? I assume that every ***conn_object*** knows best, which function to use for opening and handling and which type of event is interesting. Thus if we create a special function ***conn_open*** for every ***conn_object*** that * 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: connection_entry = conn_open(); 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 { int fd; void (*handler)(struct connection_list *); int type; }; 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 need to initialise the list and run the **conn_open** function of every **conn_object**: struct connection_list list = init_connection_list(); connection_add(&list, a_conn_open()); connection_add(&list, b_conn_open()); Having done this, our main loop now looks pretty simple, doesn't it? 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) { exec_handler(event->fd, &connection_list); } } The function ***exec_handler*** would search for the registered handler of the changed file descriptor, which could look like this: 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]]