Saturday, February 15, 2025

Asynchronous programming 11 - writing libraries

Writing the libraries in the asynchronous way is a special challenge. There is so much context attached to the futures, and implicitly inherited between them (especially through inlining) that it tends to spill over in all the wrong places. The libraries have to take explicit steps to prevent this spill-over from their callers and to their callers. And of course the libraries at the deeper levels have to do this too.

The most frequent and absolutely worst issue is with inlining. Getting rid of the whole inlining debacle by using a delayed scheduling slot solves this problem. Otherwise you have to explicitly chain every future entering your library to your own trampolined future, and also mark every future exiting your library as trampolined.

The executors represent another issue, just as bad. You don't want your code to run on the caller's executor, and don't want caller's code to run on your executor. So not only trampoline the inputs but trampoline on your executor. But this doesn't solve the exit side, not letting the user code processing your result to run on your executor. You need not only have the caller provide the result promises but also make sure that they're trampolined on a specific executor. However, like for inlining, there is an easy solution: get rid of the explicit executors in the asynchronous subsystem altogether. There is no good reason to use the serial executors, they're all pain and no gain, and all the parallel executors are effectively equivalent, so having one global executor per process is sufficient. If you want to limit the degree of multithreaing, use some form of semaphores.

Next, do the error handling and cancellations right, this usually requires some thinking through of your code. 

Another item that can make sense is priorities. There would be priority inversions as usual, but not any worse than with synchronous programming. In fact, it could be resolved with priority inheritance in a fairly straightforward way: the cancellations propagate (and stop propagation) in the same way as the priority inheritance would, so all we need to do is to stick the priority propagation on top of the pre-existing cancellation mechanism (of course if it does pre-exist).

And as the last point, try to avoid using raw pointers as arguments, this will save great many issues with the memory getting freed under them. Use the reference-counted shared pointers instead whenever possible.

No comments:

Post a Comment