Monitoring a file descriptor

The Generic event source wraps a file descriptor ("fd") and fires its callback any time there are events on it ie. becoming readable or writable, or encountering an error. It's pretty simple, but it's what every other event source is based around. And since the platforms that calloop runs on expose many different kinds of events via fds, it's usually the key to using those events in calloop.

For example on Linux, fd-based interfaces are available for GPIO, I2C, USB, UART, network interfaces, timers and many other systems. Integrating these into calloop starts with obtaining the appropriate fd, creating a Generic event source from it, and building up a more useful, abstracted event source around that. A detailed example of this is given for ZeroMQ.

You do not have to use a low-level fd either: any type that implements AsRawFd can be provided. This means that you can use a wrapper type that handles allocation and disposal itself, and implement AsRawFd on it so that Generic can manage it in the event loop.

Creating a Generic source

Creating a Generic event source requires three things:

  • The fd itself, of course, possibly in a wrapper type that implements AsRawFd
  • The "interest" you have in this descriptor — this means, whether you want to generate events when the fd becomes readable, writeable, or both
  • The "mode" of event generation - level triggered or edge triggered

The easiest constructor to use is the new() method, but if you need control over the associated error type there is also new_with_error().

Ownership and AsRawFd wrappers

It's important to remember that file descriptors by themselves have no concept of ownership attached to them in Rust — they are simply bare integers. Dropping them does not close the resource they refer to, and copying them does not carry information about how many there are.

Typically (eg. in the standard library) they would be an underlying implementation detail of another type that did encode ownership somehow. This how you can manage them in any of your own integration code - use drop handlers, reference counting (if necessary) and general RAII principles in a wrapper type, and then implement AsRawFd to allow Generic to use it. The Generic source will take ownership of it, so it will be dropped when the Generic is.

This means you need to do at least two things:

  • follow the rules of the API you obtain the fd from
  • wrap them in a type that manages ownership appropriately

For example, on Unix-like systems the UdpSocket contains a fd, and upon being dropped, libc::close(fd) is called on it. If you create a Generic<UdpSocket> then, it will be closed upon dropping it.