Mowedline: Three Bugs
I have been using mowedline for nearly two years now, developing it on and off for perhaps two and a half, and now in January 2013, it has been somewhat over a year since my last major round of work on it. This past year has given me a chance to observe mowedline through daily use, to see what parts of it were done right, and what parts misdesigned. As the first gui program that I have written from the ground up, I'll take these points of misdesign as learning experiences. There are three substantial ones.
Config Loading Environment
The idea is for mowedline to have an Emacs-style configuration; that is, the config file is not just some clunky list of a finite set of preferences, but an actual program; mowedline itself is programmable by its config. The user has a lot of power to customize mowedline into any imaginable behavior.
The problem is that I haven't actually found a good way to do this in Chicken Scheme. You can't just load a scheme file at runtime and expect it to work, because the compiler has optimized large portions of the language and library symbols out of the program.
With Chicken Scheme 4.7.0, I was able to use the environments egg to make an environment to load the config in that would have some of the important symbols one needs to write a mowedline config. This was not a wholly satisfying solution, but it worked while it lasted, which was until Chicken 4.7.4. Changes in Chicken core in 4.7.4 rendered the environments egg unusable until it could be updated, and, so I hear, further changes to core. The environments egg has been partially updated for this, at least in a test branch, but the specific way that I would need to use environments in mowedline is still broken.
For the moment, I am working around this problem by simply loading the config, but importing into mowedline libraries needed only by my config. This situation can't last — the config itself should be able to load those libraries. Solving the problem will take some further experimentation, and possibly refactoring the mowedline server into a module.
Event Loop Polling
Mowedline consumes a lot of CPU. And by a lot, I mean 100%. At the time I was writing the original event loop, I was happy just to get it working, and didn't look at the program's resource usage until much later, when I happened to notice in htop that it was using all of one of my CPU's time. The source of the problem was my very naive implementation of an event loop — it was my first time writing such a thing, remember. Since mowedline needs to watch events from X, dbus, and an internal queue, not knowing any better I wrote an event loop that polls these three sources constantly.
My first idea to deal with this was to insert a sleep call into the
event loop. When I did that, it basically cured the CPU usage problem,
but at the expense of UI responsiveness. I had first tried posix sleep
just to test the idea, but of course, sleep
can only sleep by an integer
number of seconds, so it was not suitable. Then I tried usleep
and
experimented with the number of microseconds of sleep, trading CPU usage
for responsiveness. Then I found out about srfi-18 thread-sleep!
, which
did much better than usleep, and is what I am using in the meanwhile
before I really attack the problem and rewrite the server code.
Despite the existence of this workaround, the problem arose, and persists to a lesser degree, because I had made the erroneous assumption that polling was the proper way to write an event loop that drew events from multiple sources. It is, emphatically, not the right way. Did I mention this was my first time writing a gui event loop? Really…
The right way, I have now learned, is to have a separate thread for each
event source, and in each thread, call thread-wait-for-i/o!
on the event
source FD, (or the equivalent for the library in question). So the
solution to this bug is at least clear.
Simultaneously Starting Multiple Servers
The last of the three major bugs arose out of trying to have the mowedline
client automatically start the mowedline server as needed. There is a
single program, mowedline
, which acts as both client and server. When
it starts, it checks dbus to see if the server is running, and if not,
forks to launch the server. It was a neat idea, and worth trying, but
this is what I had in mind when I wrote above of "misbegotten
experiments". The problem is that if you launch multiple clients in quick
succession when the server is not running, depending on the timing, they
may all detect the absence of the server, and thus all fork to launch
mowedline servers. You can end up with more than one mowedline server
running, and this leads to strange behavior indeed when you are debugging
your config and try to restart the server with mowedline quit; mowedline
— it doesn't work!
I could probably hack around this problem by introducing a lock file or somesuch, but I don't think I will. See, auto-starting the server has actually made debugging mowedline more complex than I'd like, because when there are a slew of mowedline client calls in the window manager and other software, sending updates to mowedline, any one of those calls could start the server in the background, while the hapless developer is trying to work from a clean slate for testing. Trying to use the software and debug it at the same time has shall we say, put an obstruction in the workflow.
So it is likely that mowedline will be split into two programs in the next
version, a server and a client, and it will be up to the user to start the
server in .xinitrc
or such.
Conclusion
In a way, it's satisfying to look at this program I've developed and see that there are only three large flaws in its design, and that none of them are fundamental, or insurmountable. Sure, there are others, bugs and missing features both, but these are the big three, and I have a good feeling about this program. It's doing what I set out to do. My original goals haven't been undermined by changes in the platform or my own needs, and it's also still fun to hack on!