I am a patient boy. I wait, I wait, I wait, I wait. My time is water down a drain
Everybody's moving. Everybody's moving. Everything is moving, moving, moving, moving
Please don't leave me to remain. In the waiting room
--- Ian MacKaye
How do you wait when you can't wait? No, that isn't a riddle; it's a question I recently faced while writing some Pony code for work.
Here's the problem: I'm writing a black box testing application that will verify the correctness of our system under test. My black box tester sends information into the box via UDP and gets data back via the same means and verifies that the data is valid. That system under test is currently a prototype written in Python. All things considered, it performs well. The problem is Pony performs better.
When I first fired up my tester, it quickly swamped the prototype. We haven't implemented any sort of backpressure yet so, I knew I needed to slow my application down via more "manual" means. I needed to limit the rate it was supplying data to the system under test. But how?
In a language with blocking operations, I could just call sleep and be done with it. It might not be the most elegant solution but it would work. Pony, however, isn't such a language. One of Pony's key features is there are no blocking operations. So, back to our original question: how do you wait when you can't wait?
After a bit of digging around, we came across the Timer class. A timer allows you to execute code at set intervals. I'm going to walk you through how to use a timer using a simple application that prints out a number to the console every 5 seconds until someone terminates the program:
use "time" actor Main new create(env: Env) => let timers = Timers let timer = Timer(NumberGenerator(env), 0, 5_000_000_000) timers(consume timer) class NumberGenerator is TimerNotify let _env: Env var _counter: U64 new iso create(env: Env) => _counter = 0 _env = env fun ref _next(): String => _counter = _counter + 1 _counter.string() fun ref apply(timer: Timer, count: U64): Bool => _env.out.print(_next()) true
Zooming in on the key bits, we first set up our timers, create one and add it to our set of timers:
let timers = Timers let timer = Timer(NumberGenerator(env), 0, 5_000_000_000) timers(consume timer)
The Timer constructor takes 3 arguments, the class to notify, how long until our timer expires and how often to fire. In our example code an instance of NumberGenerator will be called every 5 billion nanoseconds i.e. every 5 seconds until the program is killed.
Here's our method in NumberGenerator that gets executed:
fun ref apply(timer: Timer, count: U64): Bool => _env.out.print(_next()) true
If we were to compile and run our application, we'd end up with some output like:
It's not the most exciting output in the world but, it's a pattern that I expect many Pony users will need: 'how to wait without waiting'.
If you are interested in what is going on with the
consume in the
timers(consume timer), check out my previous post Deconstructing a Pony echo server where I cover the semantics of consume.