lock fails to enforce exclusive access when block contains a yield statement
Using the lock
keyword to provide exclusive access to a code block fails to enforce exclusive access in async code when a yield statement is executed within the lock's block, since the re-entrant GRecMutex is used under the hood, and all async methods are executed by the same main loop thread.
For example, the output from this:
public class Test : GLib.Object {
private int member = 0;
public async void exclusive(int id) {
lock (this.member) {
print("exclusive-%d; before\n", id);
GLib.Idle.add(exclusive.callback);
yield;
print("exclusive-%d; after\n", id);
}
}
}
public void main() {
var test = new Test();
var loop = new MainLoop();
GLib.Idle.add(() => {
test.exclusive.begin(1);
test.exclusive.begin(2);
var context = loop.get_context();
while (context.pending()) {
context.iteration(true);
}
loop.quit();
return false;
});
loop.run();
}
Is this:
exclusive-1; before
exclusive-2; before
exclusive-1; after
exclusive-2; after
While for any reasonable assumption about the semantics of lock
it should be this:
exclusive-1; before
exclusive-1; after
exclusive-2; before
exclusive-2; after
While switching to using a GMutex instead of the re-entrant version may not be possible due to the deadlock that would cause in this case, having a lock
block that doesn't actually enforce exclusive access is equally undesirable.
Unless some means to use a non-re-entrant mutex can be found, the best way forward here is probably to disallow yield
statements inside a lock
block by issuing a compiler error if one is encountered.