Enhancing Thread Scheduling of Klipper
Added 2025-02-12 16:33:01 +0000 UTCOne of the most frustrating issues I’ve faced over the past few years is the “mcu: timer too close” error. It occurs sporadically whenever a high enough CPU spike happend for a long enough time.
Today, I want to break down why this error happens, share what I’ve done to fix it in THEOS
🔥 Why Does This Error Occur?
The way Klipper communicates with the MCU is based on a bus system that sends and receives data every 25ms. Klipper keeps a close eye on the timing, and if the time delta between these packets becomes too short, the print stops with the "mcu: timer too close" error.
In most cases, the MCU itself isn’t the problem—it's the Klipper host that causes the bottleneck. So, I focused my time here on talking about the issues on the host side rather than the MCU.
Klipper’s Process Architecture
If you check Klipper’s processes (ps -x), you’ll see that Klipper runs as a service under a specific process ID.

Digging deeper (top -H -p <PID>), you’ll find that Klipper spawns multiple threads, but every single one is just labeled “python”—not very helpful.

This is where things get messy:
Klipper has multiple critical and non-critical threads
The serial communication threads (C++ and Python) must respond instantly to avoid the error
Less important threads (logging, frontend communication) don’t need high priority
The OS doesn’t know which threads are critical—so it treats them all the same
Without a way to prioritize which threads matter most, the system can’t prevent delays that trigger the error.
🛠 My Fix – Giving Threads Proper Prioritization
Over the last two days, I changed klipper to assigns proper names to Klipper’s threads. Now, instead of everything being a generic “python” thread, we can identify each one separately and give the important ones the priority they deserve:

🎯 How the Linux Scheduler Works
Linux processes operate in two priority zones:
Realtime (PR: -100 to -1) → Reserved for realtime & critical tasks
Normal (PR: 0 to 39) → Where Klipper lives
Each process has a niceness value (-20 to 19) that determines its priority within the normal zone. By default, Armbian starts Klipper at niceness 0 (priority 20).
I build a thread hierarchy that looks like this:
The C++ serial thread has the highest priority
The Python serial thread follows closely behind
Other Klipper threads have a standard priority
Logging/frontend communication has the lowest priority
With that in mind I implemented those hierarchy into klipper with the following result:

I didn’t push it to the extreme, because certain system processes (like garbage collection) need higher priority to prevent unexpected side effects. But Klipper’s serial communication is now more important than things like webcam streaming or background socket traffic.
🔬 Next Steps – Testing & THEOS 1.2.0
With these changes in place, I’m expecting a significant reduction (or complete elimination) of the “timer too close” error. Over the next 4–6 weeks, I’ll be stress-testing this change to see if any unintended side effects appear. If all goes well, this fix will be included in THEOS 1.2.0 as a core improvement.
This update should make THEOS the most robust klipper operating system! Also it should lower the hardware requirements.
🙌 Thank You for Your Support!
Matt
Comments
Awesome, the extent of your ability is quite impressive!
Kahl
2025-02-14 13:16:59 +0000 UTCI’ve always been someone who likes to do a bit of everything. I spent over 15 years in embedded software development, taking on all sorts of roles. Mechanics always fascinated me, but back then, software jobs were in high demand, so that’s the path I took. Over time, I moved more into project and program management, which is what I do for a living now. These days, I’m the guy who connects all the dots—software, hardware, and everything in between—helping different teams work together and making sure things actually get done.
Matt the Printing Nerd
2025-02-14 10:25:38 +0000 UTCWhat kind of engineer are you?!? I’ve seen mechanical, electrical and now software engineering, good lordy you can do it all it seems!
Kahl
2025-02-13 18:52:33 +0000 UTCI’d guess it’s because it’s an easy argument to spin around. I’ve done a lot of research on this topic, and the official answer almost always boils down to one of two things: either the SoC isn’t powerful enough, or you’ve loaded your Pi with too much bloatware. There have been a few discussions about CPU affinity and process niceness, but the general consensus was that Unix should handle thread management just fine on its own—without any manual tweaking. Yeah…
Matt the Printing Nerd
2025-02-13 10:14:26 +0000 UTCLast week I assigned klipper, moonraker and KlipperScreen different CPU affinities and was horrified when the ttc appeared yesterday. Nice work understanding and re-prioritising the threads. Thanks for the post and the insights. You may want to fork moonraker too, as it has a hard-coded klipper repo location :( That's if you want auto-updates in THEOS
Andrew Basson
2025-02-12 21:26:19 +0000 UTCWhy I hell Klipper developers have not done this before you? I can't understand.
Kai Käpölä
2025-02-12 19:08:55 +0000 UTCAhh it's a dirty hack.. I build a python wrapper for pthread_setname_np and build a detour hook for thread.start to inject my custom code.
Matt the Printing Nerd
2025-02-12 16:57:38 +0000 UTCAmazing! How did you rename the threads? New files? Or is it a parameter when scheduling them to run?
Alex B
2025-02-12 16:36:43 +0000 UTC