add all thoughts and publish the idea
Signed-off-by: Nico Schottelius <nico@kr.ethz.ch>
This commit is contained in:
		
					parent
					
						
							
								b00837dd91
							
						
					
				
			
			
				commit
				
					
						ee8ea28aae
					
				
			
		
					 1 changed files with 105 additions and 46 deletions
				
			
		| 
						 | 
				
			
			@ -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:
 | 
			
		||||
 | 
			
		||||
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, **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***)
 | 
			
		||||
 * 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 (**conn_object**)
 | 
			
		||||
  * 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);
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
one connection.
 | 
			
		||||
 | 
			
		||||
Secondly we add the connection_list, because the handler may decide to add
 | 
			
		||||
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]]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue