I'm worried that my sleeping thread will drop a message, but I'm not sure what to do about it

I'm writing my first Rust program and am very uncertain I'm on the happy path vis a vis rate limiting loops inside threads.

Say I have 4 threads: my main loop, threads for keyboard input and writing output to a file, and another thread that's listening for messages over TCP, and all threads are using crossbeam channels for communication.

Currently I'm calling thread::sleep(time::Duration::from_millis(1)); in each child thread, however I'm worried that there is a chance (unlikely but real nontheless) that my keyboard thread might be sleeping when a key is pressed, and therefore that input would be lost. Because of the nature of my program, a missed keypress could cause a cascade of incorrect data.

As I mentioned this is my first Rust program, I'd rather not use async.

And the reason I'm looping in the child threads is because there's a chance messages can be sent simultaneously and so I'm calling try_iter().collect on the channel to be able to iterate over the collected messages currently in the message buffer, otherwise I would just write to file using a function instead of a function writing to file inside a loop. But that wouldn't work for my keyboard handling thread.

It seems that, ideally, what I want is for the thread to "sleep until you have something to do", which is basically what async/tokio does.

Is there another way of rate-limiting my loops so that they don't fully peg a CPU core besides sleeping? Or should I just live with thread::sleep until I feel comfortable enough to use tokio?

How are you listening for keyboard input? Generally a keyboard event gets received in some sort of app event loop so you don't have to essentially poll the keyboard state.

Generally there's no reason to call sleep() for any kind of thread control. Can you give us some more info? Maybe some demo code? Are all the threads calling sleep(), or just the keyboard-reading thread?

1 Like

Typically one does not want sleep() in a thread. Your thread will be sleeping when it stops at some input operation, read(), when data becomes ready it wakes up again to process the data.

So why do you have sleep() in your threads?

It probably won't drop the keyboard event, because if you're running on a typical desktop or mobile OS, keyboard events get buffered somewhere until your application reads them.

However, you will introduce a 1ms delay on responding to keyboard input, which is going to be noticeable. Try to use a select! tool to wait on all the events at once without sleeping, instead, if it's possible.

I'd be very impressed if you noticed a 1ms delay on keyboard input when the best possible case where you could see the update is the next vsync at an average of 8ms (at 60hz). (Realistically, most desktop compositors will add an extra frame to that, and the hardware and OS often adds a few to a few dozen ms too.)

Not a big deal, additional latency can quickly add up, but don't oversell the problem!

2 Likes

This is called polling. For example, if I have an application that handles keyboard events and also TCP requests, I can register a keyboard listener and tcp listener. Then, I can make a poll call(this is actually a family of calls, poll, epoll, select, and others, with varying small differences about target use and how deprecated they are lol). This will tell the operating system that I am waiting for input. The operating system will put the thread to sleep, and whenever it receives a new value in any of the polled places, it will wake your thread up with that value.

This interface allows you to write a pattern called an 'event loop', where you call poll in a loop. Each time through the loop, you handle one of your freshly polled events, hence the term 'event loop'.

Mio is a popular rust library for doing exactly this. Tokio actually uses mio internally, in a pattern similar to the one described above(or at least they used to, my information is over a year old).