Skip to contentSkip to conversation

epoll interface change and glibc bits ...

RFC Dan Kegel 6 participants 38 messages Nov 18, 2002 – Nov 21, 2002

lore
Share & tools
lore

Dan Kegel

author
#0 lore raw
Quoted 8 lines · Ulrich
Quoted 1 line

epoll does hook f_op->poll() and hence uses the asm/poll.h bits.

It does today. We are talking about "you promise that this will be the case ever after or we'll cut your head off". I have no idea why you're so reluctant since you don't have to maintain any of the user-level bits. And it is not you who has to deal with the fallout of a change when it happens.

Quoted 4 lines

If epoll is so different from poll (and this is what I've been told frmo Davide) then there should be a clear separation of the interfaces and all those arguing to unify the data types and constants better should rethink there understanding.

epoll is not really that different from poll, is it?
It delivers edge-triggered versions of the same events poll uses. Or is there something epoll does I'm not aware of?
- Dan

#1 lore raw
Quoted 9 lines · Dan Kegel
Quoted 8 lines · Ulrich
Quoted 1 line

epoll does hook f_op->poll() and hence uses the asm/poll.h bits.

It does today. We are talking about "you promise that this will be the case ever after or we'll cut your head off". I have no idea why you're so reluctant since you don't have to maintain any of the user-level bits. And it is not you who has to deal with the fallout of a change when it happens.

Quoted 8 lines
Quoted 4 lines

If epoll is so different from poll (and this is what I've been told frmo Davide) then there should be a clear separation of the interfaces and all those arguing to unify the data types and constants better should rethink there understanding.

epoll is not really that different from poll, is it?
It delivers edge-triggered versions of the same events poll uses. Or is there something epoll does I'm not aware of?

The interface ( edge-triggered ) is quite different and we saw in the previous experience how this might lead to confusion for the user. Putting epoll bits inside poll.h will IMHO increase this.

- Davide

author
#2 lore raw
Quoted 18 lines · Davide Libenzi
Quoted 15 lines · Dan Kegel
Quoted 12 lines · Ulrich
Quoted 1 line

epoll does hook f_op->poll() and hence uses the asm/poll.h bits.

It does today. We are talking about "you promise that this will be the case ever after or we'll cut your head off". I have no idea why you're so reluctant since you don't have to maintain any of the user-level bits. And it is not you who has to deal with the fallout of a change when it happens.

If epoll is so different from poll (and this is what I've been told frmo Davide) then there should be a clear separation of the interfaces and all those arguing to unify the data types and constants better should rethink there understanding.

Quoted 8 lines
Quoted 3 lines

epoll is not really that different from poll, is it?
It delivers edge-triggered versions of the same events poll uses. Or is there something epoll does I'm not aware of?

The interface ( edge-triggered ) is quite different and we saw in the previous experience how this might lead to confusion for the user. Putting epoll bits inside poll.h will IMHO increase this.

The only difference is the edge-triggered nature, though, right?
- Dan

#3 lore raw
Quoted 5 lines · Dan Kegel
Quoted 3 lines

The interface ( edge-triggered ) is quite different and we saw in the previous experience how this might lead to confusion for the user. Putting epoll bits inside poll.h will IMHO increase this.

The only difference is the edge-triggered nature, though, right?

Yes, but we have seen that it's enough :)

- Davide

author
#4 lore raw
Quoted 9 lines · Davide Libenzi
Quoted 5 lines · Dan Kegel
Quoted 3 lines

The interface ( edge-triggered ) is quite different and we saw in the previous experience how this might lead to confusion for the user. Putting epoll bits inside poll.h will IMHO increase this.

The only difference is the edge-triggered nature, though, right?

Yes, but we have seen that it's enough :)

I'm not so sure. If the epoll documentation were clear enough (which at the moment, frankly, it isn't), I think there's a good chance users would not be confused
by the difference between level-triggered and edge-triggered events.

I'd be happy to contribute better doc... has the man page for sys_epoll been written yet?
- Dan

#5 lore raw
Quoted 11 lines · Dan Kegel
Quoted 9 lines · Davide Libenzi
Quoted 5 lines · Dan Kegel
Quoted 3 lines

The interface ( edge-triggered ) is quite different and we saw in the previous experience how this might lead to confusion for the user. Putting epoll bits inside poll.h will IMHO increase this.

The only difference is the edge-triggered nature, though, right?

Yes, but we have seen that it's enough :)

Quoted 8 lines

I'm not so sure. If the epoll documentation were clear enough (which at the moment, frankly, it isn't), I think there's a good chance users would not be confused
by the difference between level-triggered and edge-triggered events.

I'd be happy to contribute better doc... has the man page for sys_epoll been written yet?

Yep, by a long time :)

http://www.xmailserver.org/linux-patches/epoll.2
http://www.xmailserver.org/linux-patches/epoll_create.2
http://www.xmailserver.org/linux-patches/epoll_ctl.2
http://www.xmailserver.org/linux-patches/epoll_wait.2

it is going to change though with the latest talks about the interface.

- Davide

author
#6 lore raw
Quoted 9 lines · Davide Libenzi
Quoted 2 lines

I'd be happy to contribute better doc... has the man page for sys_epoll been written yet?

http://www.xmailserver.org/linux-patches/epoll.2
http://www.xmailserver.org/linux-patches/epoll_create.2
http://www.xmailserver.org/linux-patches/epoll_ctl.2
http://www.xmailserver.org/linux-patches/epoll_wait.2

it is going to change though with the latest talks about the interface.

Hmm. Right off the bat, I see a terminology problem. The man page says

.SH NAME
epoll \- edge triggered asynchronous I/O facility

That's going to confuse some users. They might think epoll can actually initiate I/O. Better to say

epoll \- edge triggered I/O readiness notification facility

Second, epoll_ctl(2) doesn't define the meaning of the event mask. It should give the allowed bits and define their meanings. If we use the traditional POLLIN etc, we can say
POLLIN - the fd has become ready for reading POLLOUT - the fd has become ready for writing
Note: If epoll tells you e.g. POLLIN, it means that
poll will tell you the same thing, since poll gives the current status, and epoll gives changes in status.

- Dan

#7 lore raw
Quoted 14 lines · Dan Kegel
Quoted 9 lines · Davide Libenzi
Quoted 2 lines

I'd be happy to contribute better doc... has the man page for sys_epoll been written yet?

http://www.xmailserver.org/linux-patches/epoll.2
http://www.xmailserver.org/linux-patches/epoll_create.2
http://www.xmailserver.org/linux-patches/epoll_ctl.2
http://www.xmailserver.org/linux-patches/epoll_wait.2

it is going to change though with the latest talks about the interface.

Hmm. Right off the bat, I see a terminology problem. The man page says

Quoted 7 lines

.SH NAME
epoll \- edge triggered asynchronous I/O facility

That's going to confuse some users. They might think epoll can actually initiate I/O. Better to say

epoll \- edge triggered I/O readiness notification facility

Yes, maybe sounds better ...

Quoted 10 lines

Second, epoll_ctl(2) doesn't define the meaning of the event mask. It should give the allowed bits and define their meanings. If we use the traditional POLLIN etc, we can say
POLLIN - the fd has become ready for reading POLLOUT - the fd has become ready for writing
Note: If epoll tells you e.g. POLLIN, it means that
poll will tell you the same thing, since poll gives the current status, and epoll gives changes in status.

I will have to change man pages also to fit EPOLL* definitions.

- Davide

author
#8 lore raw
Quoted 13 lines · Davide Libenzi
Quoted 10 lines · Dan Kegel

Second, epoll_ctl(2) doesn't define the meaning of the event mask. It should give the allowed bits and define their meanings. If we use the traditional POLLIN etc, we can say
POLLIN - the fd has become ready for reading POLLOUT - the fd has become ready for writing
Note: If epoll tells you e.g. POLLIN, it means that
poll will tell you the same thing, since poll gives the current status, and epoll gives changes in status.

Quoted 1 line

I will have to change man pages also to fit EPOLL* definitions.

IMHO changing from using POLLIN etc. to EPOLLIN will obscure the essential relationship between epoll and poll (namely, that the epoll bits are the time derivative of the poll bits).

I would prefer to continue using POLLIN, etc.
- Dan

#9 lore raw
Quoted 14 lines · Dan Kegel
Quoted 13 lines · Davide Libenzi
Quoted 10 lines · Dan Kegel

Second, epoll_ctl(2) doesn't define the meaning of the event mask. It should give the allowed bits and define their meanings. If we use the traditional POLLIN etc, we can say
POLLIN - the fd has become ready for reading POLLOUT - the fd has become ready for writing
Note: If epoll tells you e.g. POLLIN, it means that
poll will tell you the same thing, since poll gives the current status, and epoll gives changes in status.

Quoted 8 lines
Quoted 1 line

I will have to change man pages also to fit EPOLL* definitions.

IMHO changing from using POLLIN etc. to EPOLLIN will obscure the essential relationship between epoll and poll (namely, that the epoll bits are the time derivative of the poll bits).

I would prefer to continue using POLLIN, etc.

Dan, at the beginning I had the same thought as yours. Now I sort of changed my mind. The epoll interface is basically seeing the light in these days and even if right now it uses f_op->poll(), tomorrow we don't know. To avoid painful code changes later is IMHO better to define EPOLL* bits right now. They'll be the same of the POLL* bits but will enable epoll to be independent from poll.h bits. At least to the outside world. Same for the event structure.

- Davide

#10 lore raw
In reply to Davide Libenzi
Quoted 7 lines · Davide Libenzi

http://www.xmailserver.org/linux-patches/epoll.2
http://www.xmailserver.org/linux-patches/epoll_create.2
http://www.xmailserver.org/linux-patches/epoll_ctl.2
http://www.xmailserver.org/linux-patches/epoll_wait.2

it is going to change though with the latest talks about the interface.

My 2 cents:

Remove the waitqueue stuff from epoll.2. It has meaning only to linux kernel developers and noone else.

Please add more semantic details of the new facility. It is new and these man pages are the only (and normative) reference. I.e.:

What about adding an fd twice to the epoll-set? Do you get an error, will it override the previous settings for that fd, will it be ignored, or is it registered twice and you get two results for that fd? Can two epoll-sets wait for the same fd? Are events reported to both epoll-fds?

Is the epoll-fd itself poll/epoll/selectable? Can I build cluster of epoll-sets? What happens if the epollfd is put into its own fd set? Can I send the epoll-fd over a unix-socket to another process?

Then, please add more details of how events are generated. You say, that an inactive-to-active transition causes an event. What is the starting point of the collection? (I guess, all transitions between two epoll_wait calls.) There could be a couple of transi- tions on an fd between two epoll_wait calls. Are these events com- bined into a single reported event or is each single edge reported? Does an operation on an fd effect the already collected but not yet reported events?

About epoll_wait: it looks like a "read with timeout" call. Is that really necessary or wouldn't a normal read(2) work as well? Similar for epoll_ctl: couldn't a write(2) to the epoll-fd do the same?

That are the things that come into my mind at the moment. I guess there are more details I've missed...

Ciao, ET.

#37 lore raw
In reply to Dan Kegel
Quoted 5 lines · Dan Kegel

I'm not so sure. If the epoll documentation were clear enough (which at the moment, frankly, it isn't), I think there's a good chance users would not be confused
by the difference between level-triggered and edge-triggered events.

I would argue that it is a good thing that people that believe poll and epoll to be almost exactly the same, find themselves confused.

I might not succeed, but it is certainly how I feel about the issue.

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#11 lore raw
In reply to Edgar Toernig
Quoted 8 lines · Edgar Toernig
Quoted 7 lines · Davide Libenzi

http://www.xmailserver.org/linux-patches/epoll.2
http://www.xmailserver.org/linux-patches/epoll_create.2
http://www.xmailserver.org/linux-patches/epoll_ctl.2
http://www.xmailserver.org/linux-patches/epoll_wait.2

it is going to change though with the latest talks about the interface.

Yes, man pages needs some work after the interface will be fixed. I hope to fix it this week.

Quoted 2 lines

Remove the waitqueue stuff from epoll.2. It has meaning only to linux kernel developers and noone else.

Sigh, I liked it :)

Quoted 4 lines

What about adding an fd twice to the epoll-set? Do you get an error, will it override the previous settings for that fd, will it be ignored, or is it registered twice and you get two results for that fd?

You get EEXIST
Well, there's the remote possibility, trying very badly from two threads, to add the same fd twice. It is an harmless condition though.

Quoted 1 line

Can two epoll-sets wait for the same fd?

Yes. Not suggested though.

Quoted 1 line

Are events reported to both epoll-fds?

Yes.

Quoted 1 line

Is the epoll-fd itself poll/epoll/selectable?

Yes.

Quoted 1 line

Can I build cluster of epoll-sets?

Uh ?!

Quoted 1 line

What happens if the epollfd is put into its own fd set?

You might find your machine a little bit frozen :)
Either 1) I remove the read lock from poll() or 2) I check the condition at insetion time to avoid it. I very much prefer 2)

Quoted 2 lines

Can I send the epoll-fd over a unix-socket to another process?

I'd say yes. SCM_RIGHTS should simply do an in-kernel file* to remote task descriptor mapping.

Quoted 3 lines

Then, please add more details of how events are generated. You say, that an inactive-to-active transition causes an event. What is the starting point of the collection? (I guess, all transitions

The starting point are the bits found at insertion time.

Quoted 3 lines

between two epoll_wait calls.) There could be a couple of transi- tions on an fd between two epoll_wait calls. Are these events com- bined into a single reported event or is each single edge reported?

Yes, they'll be combined.

Quoted 2 lines

Does an operation on an fd effect the already collected but not yet reported events?

You can do two operations on an existing fd. Remove is meaninless for this case. Modify will re-read available bits.

Quoted 3 lines

About epoll_wait: it looks like a "read with timeout" call. Is that really necessary or wouldn't a normal read(2) work as well? Similar for epoll_ctl: couldn't a write(2) to the epoll-fd do the same?

IMHO distinc functions are more clear than magic read/write operations.

- Davide

#12 lore raw
Quoted 10 lines · Davide Libenzi
Quoted 4 lines · Edgar Toernig

What about adding an fd twice to the epoll-set? Do you get an error, will it override the previous settings for that fd, will it be ignored, or is it registered twice and you get two results for that fd?

You get EEXIST
Well, there's the remote possibility, trying very badly from two threads, to add the same fd twice. It is an harmless condition though.

Just IMHO: I would prefer a different behaviour:

int epoll_ctl(int epfd, int fd, int events)

which registers interest for "events" on "fd" and retuns previous registered events for that fd (implies that the fd is removed when "events" is 0) or -1 for error.

If you don't like it, at least an EP_CTL_GET should be added though.

Btw, what errno for an invalid fd (not epfd)?

Quoted 3 lines
Quoted 1 line

Is the epoll-fd itself poll/epoll/selectable?

Yes.

Fine. I guess, only POLLIN/readable is generated.

Quoted 4 lines
Quoted 2 lines

Can I build cluster of epoll-sets?

Uh ?!

The previous "yes" already answers this ;-) What I meant, ie three fd-sets - low, normal, high priority fds - and a fourth set consisting of the three epfds for these sets.

Quoted 5 lines
Quoted 1 line

What happens if the epollfd is put into its own fd set?

You might find your machine a little bit frozen :)
Either 1) I remove the read lock from poll() or 2) I check the condition at insetion time to avoid it. I very much prefer 2)

Hehe, sure. But could become tricky: someone may build a circular chain of epoll-fd-sets.

Quoted 5 lines
Quoted 2 lines

Can I send the epoll-fd over a unix-socket to another process?

I'd say yes. SCM_RIGHTS should simply do an in-kernel file* to remote task descriptor mapping.

And what happens then? Will the set refers to the fds from the sender process or of fds of the receiving process (which may not even have all those fds open)?

Another btw, what happens on close of an fd? Will it get removed from all epoll-fd-sets automatically?

Quoted 5 lines
Quoted 3 lines

Then, please add more details of how events are generated. You say, that an inactive-to-active transition causes an event. What is the starting point of the collection? (I guess, all transitions

The starting point are the bits found at insertion time.

... and then after each epoll_wait call, I assume?

Quoted 5 lines
Quoted 2 lines

Does an operation on an fd effect the already collected but not yet reported events?

You can do two operations on an existing fd. Remove is meaninless for this case. Modify will re-read available bits.

Huh, sorry. I meant read/write/poll style of operations.

Anyway, thanks for the information. I hope they will find their way
into the man-pages ;-) (Ok, they may become more like the posix docs
but IMHO new interfaces should be well documented.)

Ciao, ET.

#13 lore raw
Quoted 7 lines · Edgar Toernig
Quoted 4 lines · Davide Libenzi
Quoted 1 line

What happens if the epollfd is put into its own fd set?

You might find your machine a little bit frozen :)
Either 1) I remove the read lock from poll() or 2) I check the condition at insetion time to avoid it. I very much prefer 2)

Hehe, sure. But could become tricky: someone may build a circular chain of epoll-fd-sets.

This could be an indication that epoll of an epoll fd should not be allowed. This kind of sucks as epoll event hierarchies appear, at least on the surface, very natural, and better than poll()/epoll.

Another option would be to allow a reasonable depth (2? 3?). The extra checking (depth calculation) only needs to be performed if a file is added or removed where f_op == epoll_f_op.

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#14 lore raw
Quoted 9 lines · Mark Mielke
Quoted 7 lines · Edgar Toernig
Quoted 4 lines · Davide Libenzi
Quoted 1 line

What happens if the epollfd is put into its own fd set?

You might find your machine a little bit frozen :)
Either 1) I remove the read lock from poll() or 2) I check the condition at insetion time to avoid it. I very much prefer 2)

Hehe, sure. But could become tricky: someone may build a circular chain of epoll-fd-sets.

Quoted 7 lines

This could be an indication that epoll of an epoll fd should not be allowed. This kind of sucks as epoll event hierarchies appear, at least on the surface, very natural, and better than poll()/epoll.

Another option would be to allow a reasonable depth (2? 3?). The extra checking (depth calculation) only needs to be performed if a file is added or removed where f_op == epoll_f_op.

I will not enable epoll fds to be stored inside another epoll fd. With limited numbers of fds poll scales good, and if you need to create a priority hierarchy, you can use a small poll set of epoll fds. This is a very corner case and I'm not willing to screw up the to to handle something that made 0.1% of users are going to do. It _can_ be done in other ways.

- Davide

#15 lore raw
In reply to Edgar Toernig
Quoted 8 lines · Edgar Toernig
Quoted 3 lines

You get EEXIST
Well, there's the remote possibility, trying very badly from two threads, to add the same fd twice. It is an harmless condition though.

Just IMHO: I would prefer a different behaviour:

int epoll_ctl(int epfd, int fd, int events)

Quoted 7 lines

which registers interest for "events" on "fd" and retuns previous registered events for that fd (implies that the fd is removed when "events" is 0) or -1 for error.

If you don't like it, at least an EP_CTL_GET should be added though.

Btw, what errno for an invalid fd (not epfd)?

I don't like things to happen for magic bits conditions. EPOLL_CTL_ADD requires the same code internally and is more clear for the user. If someone won't shoot me before, I think the final interface will be :

#include <bits/poll.h>

#define EPOLLIN POLLIN
...

#define EPOLL_CTL_ADD 1
#define EPOLL_CTL_DEL 2
#define EPOLL_CTL_MOD 3

struct epoll_fd {
	int fd;
	unsigned short events;
	unsigned short revents;
	__uint64_t obj;
};

int epoll_create(int size);
int epoll_ctl(int epfd, int op, struct epollfd *pfd);
int epoll_wait(int epfd, struct epollfd *events, int maxevents, int timeout);

Ulrich, is it right for you or do you want EPOLL* bits to be directly defined as numbers instead of assuming the POLL* vaules ?

At the very end the opaque object might help in many cases while it won't hurt other cases.

Quoted 6 lines
Quoted 3 lines

You might find your machine a little bit frozen :)
Either 1) I remove the read lock from poll() or 2) I check the condition at insetion time to avoid it. I very much prefer 2)

Hehe, sure. But could become tricky: someone may build a circular chain of epoll-fd-sets.

It'll be possible to add epfd1 inside epfd2, not epfd1 inside epfd1.

Quoted 6 lines
Quoted 2 lines

I'd say yes. SCM_RIGHTS should simply do an in-kernel file* to remote task descriptor mapping.

And what happens then? Will the set refers to the fds from the sender process or of fds of the receiving process (which may not even have all those fds open)?

Uhm !? It'll refer to files opened on the other process. To handle this correctly we should prevent an epoll file to be passed with SCM_RIGHTS in net/core/scm.c. I mean, no catastrophic things should happen, only the interface won't work correctly. I don't know if the extra handling code is worth, but we should definitely put this inside epoll(2).

Quoted 2 lines

Another btw, what happens on close of an fd? Will it get removed from all epoll-fd-sets automatically?

Yes, obviously.

- Davide

#16 lore raw

I have a question about:

Quoted 6 lines
struct epoll_fd {
	int fd;
	unsigned short events;
	unsigned short revents;
	__uint64_t obj;
};

What value does the `fd' field have when a file descriptor being polled has been renumbered (by dup/close or dup2/close or fcntl(F_DUPFD)/close or passing through a unix domain socket)?

If we are honest, the `obj' field is absolutely essential as its the only value which uniquely identifies the file descriptor if you have done anything unusual with the fds.

The fd' field, on the other hand, is not guaranteed to correspond with the correct file descriptor number. So.... perhaps the structure should contain an obj' field and _no_ `fd' field?

This doesn't affect applications. Those which use obj' for something interesting (i.e. a pointer) will have the fd' value stored in the pointed-to data structure, while simple applications can just store the original fd' value in obj' in the first place.

Quoted 9 lines
Quoted 3 lines

And what happens then? Will the set refers to the fds from the sender process or of fds of the receiving process (which may not even have all those fds open)?

Uhm !? It'll refer to files opened on the other process. To handle this correctly we should prevent an epoll file to be passed with SCM_RIGHTS in net/core/scm.c. I mean, no catastrophic things should happen, only the interface won't work correctly. I don't know if the extra handling code is worth, but we should definitely put this inside epoll(2).

See above - same problem occurs with dup() and variants. The problem is that epoll is really reporting events on a file*, and the mapping back to fd is not always meaningful.

SCM_RIGHTS is just one of the ways to renumber fds, and disallowing it won't fix the exact same problem caused by dup(). Thus SCM_RIGHTS should be allowed because there is no reason to disallow it - and it is actually useful for some kinds of server implementation.

Quoted 4 lines
Quoted 2 lines

Hehe, sure. But could become tricky: someone may build a circular chain of epoll-fd-sets.

It'll be possible to add epfd1 inside epfd2, not epfd1 inside epfd1.

Beware of overflowing the kernel stack. If epfd4 becomes readable, and wakes up epfd3, which wakes up epfd2, which wakes up epfd1... If that is implemented recursively than I can write malicious code which will crash the kernel. Note that this isn't a cycle. It's possible to code the wakeups so this cannot happen but still have the expected behaviour.

A circular arrangement should be fine, if silly. The semantics are quite logical and don't require special cases: epfd2 becoming readable will trigger epfd1 to become readable if it isn't already. If you make a cycle, that's silly but still behaves as you'd expect. If epfd1 becomes readable, it wakes up epfd1... which is already readable so nothing further happens. Similarly with larger cycles. Assuming you've avoided stack overflow for acyclic graphs, there won't be any problem with cyclic ones.

-- Jamie

#17 lore raw
Quoted 17 lines · Jamie Lokier

I have a question about:

Quoted 6 lines
struct epoll_fd {
	int fd;
	unsigned short events;
	unsigned short revents;
	__uint64_t obj;
};

What value does the `fd' field have when a file descriptor being polled has been renumbered (by dup/close or dup2/close or fcntl(F_DUPFD)/close or passing through a unix domain socket)?

If we are honest, the `obj' field is absolutely essential as its the only value which uniquely identifies the file descriptor if you have done anything unusual with the fds.

Quoted 8 lines

The fd' field, on the other hand, is not guaranteed to correspond with the correct file descriptor number. So.... perhaps the structure should contain an obj' field and _no_ `fd' field?

This doesn't affect applications. Those which use obj' for something interesting (i.e. a pointer) will have the fd' value stored in the pointed-to data structure, while simple applications can just store the original fd' value in obj' in the first place.

Even if I agree with you here, this will make the API asymmetrical. We will have :

struct epoll_fd {
	unsigned short events;
	unsigned short revents;
	__uint64_t obj;
};

int epoll_ctl(int epfd, int op, int fd, struct epoll_fd *pfd);

Where the "fd" is used only for EPOLL_CTL_ADD, and "obj" for EPOLL_CTL_DEL and EPOLL_CTL_MOD.

Quoted 9 lines
Quoted 1 line

It'll be possible to add epfd1 inside epfd2, not epfd1 inside epfd1.

Beware of overflowing the kernel stack. If epfd4 becomes readable, and wakes up epfd3, which wakes up epfd2, which wakes up epfd1... If that is implemented recursively than I can write malicious code which will crash the kernel. Note that this isn't a cycle. It's possible to code the wakeups so this cannot happen but still have the expected behaviour.

Quoted 8 lines

A circular arrangement should be fine, if silly. The semantics are quite logical and don't require special cases: epfd2 becoming readable will trigger epfd1 to become readable if it isn't already. If you make a cycle, that's silly but still behaves as you'd expect. If epfd1 becomes readable, it wakes up epfd1... which is already readable so nothing further happens. Similarly with larger cycles. Assuming you've avoided stack overflow for acyclic graphs, there won't be any problem with cyclic ones.

This is a problem with the new callback'd wake_up(). I'd be tempted to not permit epoll fd inclusion inside other epoll fds.

- Davide

#18 lore raw
In reply to Jamie Lokier
Quoted 17 lines · Jamie Lokier

I have a question about:

Quoted 6 lines
struct epoll_fd {
	int fd;
	unsigned short events;
	unsigned short revents;
	__uint64_t obj;
};

What value does the `fd' field have when a file descriptor being polled has been renumbered (by dup/close or dup2/close or fcntl(F_DUPFD)/close or passing through a unix domain socket)?

If we are honest, the `obj' field is absolutely essential as its the only value which uniquely identifies the file descriptor if you have done anything unusual with the fds.

Quoted 8 lines

The fd' field, on the other hand, is not guaranteed to correspond with the correct file descriptor number. So.... perhaps the structure should contain an obj' field and _no_ `fd' field?

This doesn't affect applications. Those which use obj' for something interesting (i.e. a pointer) will have the fd' value stored in the pointed-to data structure, while simple applications can just store the original fd' value in obj' in the first place.

It's OK. I agree. We can remove the fd from inside the structure and have :

struct epoll_event {
	unsigned short events;
	unsigned short revents;
	__uint64_t obj;
};

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents,

               int timeout);

And the lower size of the structure will help to reduce the amount of memory transfered to userspace. I just saw that adding the extra "obj" member lowered performance of about 15% with crazy tests like Ben's pipetest. This because it creates, on my machine, more than 400000 events per second, and saving memory bandwidth on such conditions is a must. With the "more human" http test performance are about the same.

- Davide

#36 lore raw
In reply to Jamie Lokier
Quoted 4 lines · Jamie Lokier

[ ... regarding 'struct epollfd' ('struct epoll_fd'?) ... ] What value does the `fd' field have when a file descriptor being polled has been renumbered (by dup/close or dup2/close or fcntl(F_DUPFD)/close or passing through a unix domain socket)?

As long as the kernel doesn't freeze up or leak memory, I believe it is the responsibility of the application code to ensure that the correct file descriptors are registered (and deregistered). If the application code does screwy things, it should expect a little bit of undefined behaviour.

Quoted 3 lines

The fd' field, on the other hand, is not guaranteed to correspond with the correct file descriptor number. So.... perhaps the structure should contain an obj' field and _no_ `fd' field?

Whether the 'fd' field is in kernel space or user space or both does not really affect the ability of the application code to be able to perform dup() operations without needing to change references to the 'fd'. For the people that do not wish to use the 'obj' field, the 'fd' field will be more natural. (Anybody who dups a file descriptor, and then tells the event loop to watch both file descriptors, is asking for trouble under any scheme...)

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#19 lore raw
In reply to Davide Libenzi
Quoted 11 lines · Davide Libenzi
Quoted 4 lines · Jamie Lokier

The fd' field, on the other hand, is not guaranteed to correspond with the correct file descriptor number. So.... perhaps the structure should contain an obj' field and _no_ `fd' field?
...

It's OK. I agree. We can remove the fd from inside the structure and have :

    struct epoll_event {
        unsigned short events;
        unsigned short revents;
        __uint64_t obj;
    };

Forget any argument I had against removing 'fd'. This sounds good.

Perhaps 'obj' should be named 'userdata'?

     struct epoll_event {
         unsigned short   events;
         unsigned short   revents;
         __uint64_t       userdata;
     };

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#32 lore raw
In reply to Davide Libenzi
Quoted 6 lines · Davide Libenzi

And the lower size of the structure will help to reduce the amount of memory transfered to userspace. I just saw that adding the extra "obj" member lowered performance of about 15% with crazy tests like Ben's pipetest. This because it creates, on my machine, more than 400000 events per second, and saving memory bandwidth on such conditions is a must. With the "more human" http test performance are about the same.

I'd be quite surprised if 400,000 word/sec of memory bandwidth can explain a 15% time difference, especially considering all the other things that are done to communicate over a pipe (wakeups etc).

Btw, I have seen variations of that magnitude in other network tests that I've done that _appear_ to be explained by small changes to the code like that, but the next day or week the variation stops correlating with the change. Page colouring affecting benchmarks again?

-- Jamie

#33 lore raw
Quoted 11 lines · Jamie Lokier
Quoted 6 lines · Davide Libenzi

And the lower size of the structure will help to reduce the amount of memory transfered to userspace. I just saw that adding the extra "obj" member lowered performance of about 15% with crazy tests like Ben's pipetest. This because it creates, on my machine, more than 400000 events per second, and saving memory bandwidth on such conditions is a must. With the "more human" http test performance are about the same.

I'd be quite surprised if 400,000 word/sec of memory bandwidth can explain a 15% time difference, especially considering all the other things that are done to communicate over a pipe (wakeups etc).

Jamie, they were 16 bytes * 400000, and the token passed through the pipe was 12 bytes.

- Davide

#20 lore raw
In reply to Mark Mielke
Quoted 15 lines · Mark Mielke
Quoted 11 lines · Davide Libenzi
Quoted 4 lines · Jamie Lokier

The fd' field, on the other hand, is not guaranteed to correspond with the correct file descriptor number. So.... perhaps the structure should contain an obj' field and _no_ `fd' field?
...

It's OK. I agree. We can remove the fd from inside the structure and have :

    struct epoll_event {
        unsigned short events;
        unsigned short revents;
        __uint64_t obj;
    };

Forget any argument I had against removing 'fd'. This sounds good.

Quoted 7 lines

Perhaps 'obj' should be named 'userdata'?

     struct epoll_event {
         unsigned short   events;
         unsigned short   revents;
         __uint64_t       userdata;
     };

Do we want to have a union instead of a direct 64bit int ?

- Davide

#34 lore raw
In reply to Davide Libenzi
Quoted 7 lines · Davide Libenzi
Quoted 7 lines
Quoted 6 lines

And the lower size of the structure will help to reduce the amount of memory transfered to userspace. I just saw that adding the extra "obj" member lowered performance of about 15% with crazy tests like Ben's pipetest. This because it creates, on my machine, more than 400000 events per second, and saving memory bandwidth on such conditions is a must. With the "more human" http test performance are about the same.

Quoted 6 lines
Quoted 3 lines

I'd be quite surprised if 400,000 word/sec of memory bandwidth can explain a 15% time difference, especially considering all the other things that are done to communicate over a pipe (wakeups etc).

Jamie, they were 16 bytes * 400000, and the token passed through the pipe was 12 bytes.

However, it's 4 bytes (1 word) * 400000 _difference_ between the two tests, yes?

-- Jamie

#35 lore raw
Quoted 12 lines · Jamie Lokier
Quoted 11 lines · Davide Libenzi
Quoted 10 lines
Quoted 6 lines

And the lower size of the structure will help to reduce the amount of memory transfered to userspace. I just saw that adding the extra "obj" member lowered performance of about 15% with crazy tests like Ben's pipetest. This because it creates, on my machine, more than 400000 events per second, and saving memory bandwidth on such conditions is a must. With the "more human" http test performance are about the same.

I'd be quite surprised if 400,000 word/sec of memory bandwidth can explain a 15% time difference, especially considering all the other things that are done to communicate over a pipe (wakeups etc).

Quoted 5 lines
Quoted 2 lines

Jamie, they were 16 bytes * 400000, and the token passed through the pipe was 12 bytes.

However, it's 4 bytes (1 word) * 400000 _difference_ between the two tests, yes?

Yep, the problem is that the "tool" used to measure ( Ben's pipetest ) on my machine has a variance of about 35% and this makes every measure prety fuzzy.

- Davide

#21 lore raw
In reply to Davide Libenzi
Quoted 6 lines · Davide Libenzi
Quoted 5 lines · Mark Mielke
Quoted 5 lines
    struct epoll_event {
        unsigned short events;
        unsigned short revents;
        __uint64_t obj;
    };
Quoted 8 lines
Quoted 7 lines

Forget any argument I had against removing 'fd'. This sounds good. Perhaps 'obj' should be named 'userdata'?

     struct epoll_event {
         unsigned short   events;
         unsigned short   revents;
         __uint64_t       userdata;
     };

Do we want to have a union instead of a direct 64bit int ?

I was going to suggest this, except I couldn't figure out what to suggest that it look like... I finally figured that the value could be cast, or wrapped in a union by userspace (although theoretically, this might mean more words than absolutely necessary to initialize on a 32-bit CPU...)

What were you thinking? 1X64 bit or 2X32 bit?

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#22 lore raw
Quoted 16 lines · Mark Mielke
Quoted 6 lines · Davide Libenzi
Quoted 5 lines · Mark Mielke
Quoted 5 lines
    struct epoll_event {
        unsigned short events;
        unsigned short revents;
        __uint64_t obj;
    };
Quoted 8 lines
Quoted 7 lines

Forget any argument I had against removing 'fd'. This sounds good. Perhaps 'obj' should be named 'userdata'?

     struct epoll_event {
         unsigned short   events;
         unsigned short   revents;
         __uint64_t       userdata;
     };

Do we want to have a union instead of a direct 64bit int ?

Quoted 7 lines

I was going to suggest this, except I couldn't figure out what to suggest that it look like... I finally figured that the value could be cast, or wrapped in a union by userspace (although theoretically, this might mean more words than absolutely necessary to initialize on a 32-bit CPU...)

What were you thinking? 1X64 bit or 2X32 bit?

Something like :

typedef union epoll_obj {
	void *ptr;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

I'm open to suggestions though. The "ptr" enable me to avoid wierd casts to avoid gcc screaming.

- Davide

#23 lore raw
Quoted 8 lines · Davide Libenzi
Quoted 1 line

What were you thinking? 1X64 bit or 2X32 bit?

typedef union epoll_obj {
	void *ptr;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

I'm open to suggestions though. The "ptr" enable me to avoid wierd casts to avoid gcc screaming.

It looks fine to me for as long as we can guarantee that sizeof(void *) will be less than or equal to sizeof(__uint64_t) (relatively safe).

I still prefer 'userdata' over 'obj', but the name of thing is not very important to me.

I'm not sure if this is wise or not, but an 'fd' member might be useful as well:

  typedef union epoll_obj {
  	void *ptr;
        int fd;
  	__uint32_t u32[2];
  	__uint64_t u64;
  } epoll_obj_t;

For applications that wish to store fd's (probably common due to poll() roots), this would help them avoid casting magic as well. Also, it allows for 64 bit file descriptors if that ever became necessary.

Since userspace applications are almost guaranteed to need to do casting or union copying, using the union sounds like a good idea.

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#29 lore raw
In reply to Davide Libenzi
Quoted 8 lines · Davide Libenzi
typedef union epoll_obj {
	void *ptr;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

I'm open to suggestions though. The "ptr" enable me to avoid wierd casts to avoid gcc screaming.

That makes more sense to me, because it will be fine to use `ptr' even on 128-bit pointer machines when they arrive, yet preserves the property that 64<->32 bit conversion functions don't need to reformat the buffer when running 32-bit applications on a 64-bit CPU... even if the 32-bit application uses the `ptr' field.

Did I just write that? :)

-- Jamie

#24 lore raw
In reply to Mark Mielke
Quoted 2 lines · Mark Mielke

It looks fine to me for as long as we can guarantee that sizeof(void *) will be less than or equal to sizeof(__uint64_t) (relatively safe).

It is fine, for epoll itself, even if sizeof(void *) > sizeof(__uint64_t).

Conversion functions for legacy 32-bit code running on a 128-bit chip will be more complex, but epoll is the _least_ of problems in that case...

Quoted 2 lines

I still prefer 'userdata' over 'obj', but the name of thing is not very important to me.

The AIO subsystem has a very similar cookie, which is simply declared as __u64 aio_data' in user-supplied requests, and returned in the responses. It's converted to a field called ki_user_data' in the kernel.

Just a few reference points... I like `user_data' myself.

Quoted 6 lines

I'm not sure if this is wise or not, but an 'fd' member might be useful as well:
[...]
For applications that wish to store fd's (probably common due to poll() roots), this would help them avoid casting magic as well. Also, it allows for 64 bit file descriptors if that ever became necessary.

That is a good idea, if we go for the union.

-- Jamie

#25 lore raw
Quoted 1 line · Jamie Lokier

That is a good idea, if we go for the union.

This is the include file I'm thinking to submit to Ulrich for glibc inclusion :

#ifndef _SYS_EPOLL_H
#define _SYS_EPOLL_H 1

#include <sys/types.h>

enum EPOLL_EVENTS {
	EPOLLIN = 0x001,

#define EPOLLIN EPOLLIN

	EPOLLPRI = 0x002,

#define EPOLLPRI EPOLLPRI

	EPOLLOUT = 0x004,

#define EPOLLOUT EPOLLOUT

#ifdef __USE_XOPEN

	EPOLLRDNORM = 0x040,

#define EPOLLRDNORM EPOLLRDNORM

	EPOLLRDBAND = 0x080,

#define EPOLLRDBAND EPOLLRDBAND

	EPOLLWRNORM = 0x100,

#define EPOLLWRNORM EPOLLWRNORM

	EPOLLWRBAND = 0x200,

#define EPOLLWRBAND EPOLLWRBAND

#endif

#ifdef __USE_GNU
	EPOLLMSG = 0x400,

#define EPOLLMSG EPOLLMSG
#endif

	EPOLLERR = 0x008,

#define EPOLLERR EPOLLERR

	EPOLLHUP = 0x010

#define EPOLLHUP EPOLLHUP

};

/* Valid opcodes to issue to epoll_ctl() */
#define EPOLL_CTL_ADD 1 /* Add a file decriptor to the interface */
#define EPOLL_CTL_DEL 2 /* Remove a file decriptor from the interface */
#define EPOLL_CTL_MOD 3 /* Change file decriptor epoll_event structure */

typedef union epoll_obj {
	void *ptr;
	int fd;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

struct epoll_event {
unsigned short events; /* Required events */
unsigned short revents; /* Returned events */
epoll_obj_t data; /* User data variable */
};

__BEGIN_DECLS

/*
* Creates an epoll interface by suggesting the requested size in terms
* of file descriptors that will be presumably stored inside the interface.
* The integer returned by epoll_create() should be closed with close(). */
extern int epoll_create(int size);

/*
* Controller function for the epoll interface. Valid values for the "op"
* parameter are the EPOLL_CTL_* constant defined above. The "fd" parameter
* is the target of the operation while the "event" parameter describe which
* events the caller is interested in and the data ( obj ) he associates
* the the file "fd".
*/
extern int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) __THROW;

/*
* Wait for events on the epoll interface. The function will return the number
* of events that will be available inside the memory area pointed by "events".
* Up to "maxevents" events will be copied. The "timeout" parameter enable the
* caller to specify a miximum wait time in milliseconds ( -1 == infinite ). */
extern int epoll_wait(int epfd, struct epoll_event *events, int maxevents,

		      int timeout) __THROW;

__END_DECLS

#endif /* #ifndef _SYS_EPOLL_H */

Comments ?

- Davide

#30 lore raw
In reply to Jamie Lokier
Quoted 8 lines · Jamie Lokier
Quoted 7 lines · Davide Libenzi
typedef union epoll_obj {
	void *ptr;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

I'm open to suggestions though. The "ptr" enable me to avoid wierd casts to avoid gcc screaming.

Quoted 6 lines

That makes more sense to me, because it will be fine to use `ptr' even on 128-bit pointer machines when they arrive, yet preserves the property that 64<->32 bit conversion functions don't need to reformat the buffer when running 32-bit applications on a 64-bit CPU... even if the 32-bit application uses the `ptr' field.
Did I just write that? :)

The problem with sizeof(void *) being >= sizeof(__uint64_t) is that the data structure is the wrong length. Binary compatibility would not be maintained.

Still... I believe that the days of 128-bit pointers are comfortably far enough away that it does not cause any concerns at all for me. (I suspect the kernel will need quite a few more changes than just this to support 128-bit poitners...)

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#31 lore raw
Quoted 16 lines · Mark Mielke
Quoted 8 lines · Jamie Lokier
Quoted 7 lines · Davide Libenzi
typedef union epoll_obj {
	void *ptr;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

I'm open to suggestions though. The "ptr" enable me to avoid wierd casts to avoid gcc screaming.

Quoted 6 lines

That makes more sense to me, because it will be fine to use `ptr' even on 128-bit pointer machines when they arrive, yet preserves the property that 64<->32 bit conversion functions don't need to reformat the buffer when running 32-bit applications on a 64-bit CPU... even if the 32-bit application uses the `ptr' field.
Did I just write that? :)

Quoted 8 lines

The problem with sizeof(void *) being >= sizeof(__uint64_t) is that the data structure is the wrong length. Binary compatibility would not be maintained.

Still... I believe that the days of 128-bit pointers are comfortably far enough away that it does not cause any concerns at all for me. (I suspect the kernel will need quite a few more changes than just this to support 128-bit poitners...)

In theory it is possible to pass epoll_create() an extra parameter that will set the size of the extra data, from 0 up to N. And the kernel will return this data "as is". It'll become nasty in user space to access data though.

- Davide

#26 lore raw
In reply to Mark Mielke
Quoted 21 lines · Mark Mielke
Quoted 9 lines · Davide Libenzi
Quoted 1 line

What were you thinking? 1X64 bit or 2X32 bit?

typedef union epoll_obj {
	void *ptr;
	__uint32_t u32[2];
	__uint64_t u64;
} epoll_obj_t;

I'm open to suggestions though. The "ptr" enable me to avoid wierd casts to avoid gcc screaming.

It looks fine to me for as long as we can guarantee that sizeof(void *) will be less than or equal to sizeof(__uint64_t) (relatively safe).

I still prefer 'userdata' over 'obj', but the name of thing is not very important to me.

I'm not sure if this is wise or not, but an 'fd' member might be useful as well:

Quoted 6 lines
  typedef union epoll_obj {
  	void *ptr;
        int fd;
  	__uint32_t u32[2];
  	__uint64_t u64;
  } epoll_obj_t;

u32 and u64 sounds more like type name. d32 / d64 ? --
vda

#27 lore raw
Quoted 7 lines · Denis Vlasenko
Quoted 6 lines
  typedef union epoll_obj {
  	void *ptr;
        int fd;
  	__uint32_t u32[2];
  	__uint64_t u64;
  } epoll_obj_t;

u32 and u64 sounds more like type name. d32 / d64 ?

d32 isn't as descriptive as u32 (unsigned). Also if every field had a 'd' in front of it, it would be 'dptr', 'dfd', ...

u32 is fine to me.

mark

Signature · 9 lines
[email protected]/[email protected]/[email protected] __________________________
.  .  _  ._  . .   .__    .  . ._. .__ .   . . .__  | Neighbourhood Coder
|\/| |_| |_| |/    |_     |\/|  |  |_  |   |/  |_   | 
|  | | | | \ | \   |__ .  |  | .|. |__ |__ | \ |__  | Ottawa, Ontario, Canada

  One ring to rule them all, one ring to find them, one ring to bring them all
                       and in the darkness bind them...

                           http://mark.mielke.cc/
#28 lore raw
Quoted 7 lines · Mark Mielke
Quoted 6 lines · Denis Vlasenko
Quoted 6 lines
  typedef union epoll_obj {
  	void *ptr;
        int fd;
  	__uint32_t u32[2];
  	__uint64_t u64;
  } epoll_obj_t;
Quoted 6 lines
Quoted 1 line

u32 and u64 sounds more like type name. d32 / d64 ?

d32 isn't as descriptive as u32 (unsigned). Also if every field had a 'd' in front of it, it would be 'dptr', 'dfd', ...

u32 is fine to me.

I renamed epoll_obj_t to epoll_data_t ...

- Davide