Improvements for multi-threading and document locking
While trying to do some research on #78 (closed) and reworking !139 (merged), I realized that the multi-threading we introduced when using GThreadPool
is actually quite fragile. The idea I had in my mind is that it would be possible to have multiple pages rendering at the same time. However, it seems like poppler (or maybe just the cairo backend?) is not thread-safe. In consequence, it is just simply not possible to have multiple operations running concurrently (maybe it's possible to search and render at the same time? I have not been able to find detailed information in poppler). This is warrantied in Papers through locking, where every PpsJob, as well as many other accesses through the code-base lock the document before accessing it.
When we added multi-threading we did not touch anything related to the locking. In consequence what happens is that all the threads fight over the same lock, and block synchronously. We still run the operations one by one. However, now cancelling "queued" operations does not work, since the thread already started, and very few operations check for being cancelled.
I guess there are many different ways to improve the situation. Just some thoughts on how to improve the situation:
- Set the thread pool to a maximum of 1 concurrent thread, and go back to single-threaded. This is probably still worse in find and fonts than Evince. There they use idle callbacks, so effectively by-passing the single threaded limitation in a very weird way.
- Make sure that operations can be cancelled while waiting for locks. This would make total sense in PpsJobs, but there are other things that also do locking, and those are less clear.
- Lock less, and possibly do the locking within
pps_document_*
functions, or even in the backends instead of in the consumers. No idea how much would this improve the situation, but hopefully is less error-prone than having to remember to lock on every consumer use. If done in the backends, it needs work to understand which backends a thread-safe, etc. In addition, it would still need the previous idea of being able to cancel something that's waiting for a lock. Some problem of this approach might be that operations that need few fast locks, could possibly be blocked if in between a long-running operation (like a render) happens, and blocks the second part of an otherwise fast operation.
More ideas?