As a more or less POSIX compatible system I would’ve expected timer_create and friends to be available on OS X, but it turns out those functions are not available (at least I couldn’t find them after hours of research).
Looking into alternatives (spoiler: there are not many I think if you want to
work from C/Rust) I settled on Kqueue.
It doesn’t have all the features that the
timer_ functions provide, but for
what I need it seems to be good enough. Also, there are a bunch of crates like
mio and nix
available that either provide abstractions or use Kqueue already so I had
something to refer to.
I didn’t find documentation or blog posts on this topic so I decided it is time to write one. This doesn’t go into all the details since, frankly, I don’t know all of them yet too. It should be enough to get you started though.
A Kqueue primer
Kqueue is a event notification system originally introduced in FreeBSD and subsequently supported in many more BSD variants as well as OS X. It can be used for similar tasks (like handling network connections) as the epoll system on Linux or the IOCP framework on Windows. You can also schedule timers with it, and this is what we are doing in this blog post.
There are two main functions you need to work with:
which translate to the following rust signatures (from
kqueue function translates into a system call and creates a new kernel
event queue and returns a descriptor. The
kevent function is used to both
KEvents as well as check if any of them are currently pending.
KEvent is a generic struct that describes the type of event to monitor
and looks like this in rust (also from
ident holds a value which is used to identify the event. Depending on the
configured filter its type and meaning can change (but is very often a file
filter is important since it determines the kernel filter to
process this event. For our timers we’ll use
there are many more available. The
flags define which actions to perform
on the given event. The
fflags allow you to configure filter-specific flags,
in our example we won’t use them. Finally
data allows to set filter-specific
udata is opaque user-defined data that is passed through the kernel
Check out the docs in nix
for all the different values on
the original documentation on kqueue
provides lots of insights into the flags and their functionality.
The last thing you need to know before diving into the actual code is the difference
eventlist: Both take a slice of
KEvents, but only
eventlist is mutable.
changelistis used to register events with kqueue.
eventlistcontains all the events which are currently active at the time of polling.
Note that the same slice underlying container (like a
Vec) can be used to
maintain both lists.
With the basics covered, let’s dive into the code.
Create a new (
--bin) project through
cargo and add
nix as a dependency:
Add this to the top of your
main.rs file so we have the imports out of the
Next, we add a helper function that encapsulates the
Here the caller passes in the event id as well as the timer in milliseconds. Since
we want to get a timer event we need to use the
EV_ADD | EV_ENABLE indicates we want to add and enable the timer at the same
time. No flag filters are needed and the
data payload for our timer event is the
time provided by the caller. We also don’t set any opaque user data here.
main function we first need to grab a
kqueue and then register
events. We make use of our
event function here and create one event that
runs each second and one that runs every 1.5 seconds:
Kqueue now knows about the events we are interested in, so it’s time to run a loop and poll until they happen:
kevent and the
changes slice is updated with the results on each
poll with the
kevent returns either an
Ok with the
number of events that are available now. Note that
Ok(0) is a special case
that nothing is available, so we move on. If we have at least one event pending
we iterate through all pending events and print their ID.
So if you run this example what you’ll see is:
$ cargo run Fresh bitflags v0.4.0 Fresh semver v0.1.20 Fresh void v1.0.2 Fresh cfg-if v0.1.0 Fresh libc v0.2.12 Fresh rustc_version v0.1.7 Fresh nix v0.6.0 Fresh kqueue-samples v0.1.0 Running `target/debug/kqueue-samples` --- Event with ID 1 triggered --- Event with ID 2 triggered --- Event with ID 1 triggered --- Event with ID 1 triggered Event with ID 2 triggered --- Event with ID 1 triggered --- Event with ID 2 triggered --- ^C
Since our timers fire at different intervals (and occasionally fire together) you can see that every time the different events are available to process by your application.
Of course this is barely scratching the surface of what you can do with
but I’d like to mention one final thing: as you can see even if you just
registered the events once they fire over and over again. If you want to fire
them just once you can use
flags: EV_ADD | EV_ENABLE | EV_ONESHOT instead.