Eri has a mix of a "true confession" and a "wait, really?" today.
The programming language Vala bills itself as a C# like language that compiles into something pretty close to C performance, designed specifically for writing code against Gnome and its associated libraries.
One of the C#-isms in brings in is async/await type semantics. You can yield someAsyncFunction(), which returns control to the caller, allowing it to proceed until the yielded function returns an actual value.
Because it has asynchronous functions, many library functions for handling I/O are already async. So you can make_directory_async, which yields control so you can keep executing while waiting for the filesystem to make your directory.
There are also synchronous versions of those methods. And then there's create_directory_with_parents, which will create a chain of directories for you. That's the synchronous version, and Vala's core library has decided not to provide an asynchronous version of it, which is my "wait, really?" I suspect it's really about the race conditions involved and the risks of things going wrong while doing it asynchronously; all solvable problems, but tricky ones to solve.
But it's the problem Eri had, and this is their solution:
/// Note: does not throw if target already exists
async void create_directory_with_parents_async(File file, Cancellable? cancellable = null) throws Error {
var to_create = new File[0];
var? current_target = file;
while(current_target != null) {
try {
yield current_target.make_directory_async(Priority.DEFAULT, cancellable);
} catch(IOError.NOT_FOUND e) {
to_create += current_target;
current_target = current_target.get_parent();
continue;
} catch(IOError.EXISTS e) {
break;
}
break;
}
for (int i = to_create.length - 1; i >= 0; --i) {
try {
yield to_create[i].make_directory_async(Priority.DEFAULT, cancellable);
} catch(IOError.EXISTS e) {
// Created by another process
}
}
}
If I'm reading this correctly, we start by trying to create the full path to our leaf node. If there's a not found error, we go up one level and try and create that one. We keep trying that until we either run out of parent nodes to try against, or we hit a directory that already exists, or we successfully create a directory. All along the way, we keep appending the current_target to our to_create array.
Once we've gotten that baseline, we then iterate across our to_create array, backwards, creating the shortest non-existent paths first.
This works, but it's ugly as sin. Mostly, it's ugly because we're using exceptions for flow control instead of doing things like checking for file existence, though I suppose those checks may also break our goal of doing all our I/O operations in an async context. I don't know enough about Vala to know the better way of doing this.
Eri writes:
The function works as intended, but trying to trace control flow through the first loop is not pleasant. Ironically, the C mechanism Vala wraps is slightly advanced error codes, which would be nicer to work with in this case
Eri also provides a slightly re-worked version of the main loop, that is at least a bit easier to follow, but still an ugly approach:
while(current_target != null) {
try {
yield current_target.make_directory_async(Priority.DEFAULT, cancellable);
break;
} catch(IOError.EXISTS e) {
break;
} catch(IOError.NOT_FOUND e) {
to_create += current_target;
current_target = current_target.get_parent();
}
}
Still, since this is an attempt to patch over a missing core library method and solve a tricky problem about how to handle race conditions, I think absolution is reasonable. It's ugly, it's weird, but it does the job. Go hide it in a box, and never touch its implementation again- except to make it go away.
[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.