a74857e73c
Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
153 lines
5 KiB
Markdown
153 lines
5 KiB
Markdown
[[!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]]
|