Look at this code

import vibe.d;

__gshared TaskMutex mutex;

void foo(string name) {
	synchronized(mutex) sleep(2.seconds);
	logInfo(name);
}

shared static this() {
	mutex = new TaskMutex;
	runTask({
		while(true) foo("task 1");
	});
	runTask({
		while(true) foo("task 2");
	});
	runTask({
		while(true) foo("task 3");
	});
}

In this case task 2 and task 3 never gets control. Output is

task 1
task 1
task 1
...

Workaround is calling yield() after unlocking mutex:

void foo(string name) {
	synchronized(mutex) sleep(2.seconds);
	yield();
	logInfo(name);
}

Is this a bug or a feature?