add all thoughts and publish the idea

Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
This commit is contained in:
Nico Schottelius 2010-05-01 23:37:24 +02:00
parent b00837dd91
commit ee8ea28aae

View file

@ -1,94 +1,153 @@
[[!meta title="Solution proposal for the io select/poll problem]] [[!meta title="Solution proposal for the io select/poll problem]]
## The situation ## 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. If you have used select(2) or poll(2) more than once, you may have noticed
* For each i/o connection (file descriptor, **fd**), you have the regular pattern that comes up again and again:
* 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***) * The main task of a program is to listen and react on multiple input/output connections.
* After ***conn_handle*** has been called, the number of connections may have changed: * For each i/o connection (file descriptor), you have
***conn_handle*** may * a function that opens the file descriptor (Let's call it ***conn_open***.) and
* add (think of accept(2)) * another function to be executed if an event happens on the file descriptor
* or remove (think of close(2)) connections (***conn_handle***).
* ***conn_open*** and ***conn_handle*** are closely related (**conn_object**) * 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 ## The problem
Each and every time this situation occurs, you have to (re-)write 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 code to handle that case. I have seen it in some applications I have
been writing, for instance [[software/ceofhack]] or been writing, for instance [[software/ceofhack]] or [[software/fui]].
[[software/fui]].
## The solution proposal ## The solution proposal
Write a solution to the problem Write a solution to the problem
[once and only once](http://c2.com/xp/OnceAndOnlyOnce.html), [once and only once](http://c2.com/xp/OnceAndOnlyOnce.html),
so you and me 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: First of all, begin with the obvious part:
### What do we have? ### What do we have?
We have to bring together n times We have to bring together **n times**
* open functions * 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 * handle functions
Furthermore, we have Furthermore, we have
* one main loop that listens for events * **one** main loop that listens for events
### How to connect them properly? ### How to connect them properly?
If every **conn_object** supports the following interface, it's I assume that every ***conn_object*** knows best, which function to use for
very easy to write a generic main loop: 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 { struct connection_entry {
fd, int fd;
handler, void (*handler)(struct connection_list *);
type, int type;
} };
The type attribute describes, which events to wait for: 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.
* 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 main loop ### The main loop
Before launching the main listener loop, we probably need to initialise Before launching the main listener loop, we need to initialise
the list, but afterwards it's all up to the handlers: 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 a_conn_open(&connection_list);
conn_object.open(connection_list) b_conn_open(&connection_list);
while(true) { Having done this, our main loop now looks pretty simple, doesn't it?
events_list = wait_for_event(connection_list)
for(every_event_in(event_list)) { while(1) {
call_corresponding_handler_for_event_from_connection_list(fd, connection_list) /* 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 The function ***exec_handler*** would search for the registered handler
one connection. 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. 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]]