Anyway… all I really wanted to do was create a PR to propose a solution for the Condition timeout problem… but git wouldn’t let me… so I have to do it here.
I had a brainstorm this morning about how to handle timeouts in Condition. Why didn’t I (or anyone) think of this before?
I don’t see a downside to it. If the condition is released, any pending timeout routines are stop
ped and they won’t fire redundant actions. If the condition is not released, timeout routines affect only their own threads (so multiple routines could attach to the same condition with independent timeouts).
Did I miss something? AFAICS this is correct.
EDIT: When you start thinking about multiple threads attaching to the same Condition with different timeout durations, the semantics get tricky. I think this implementation is the easiest to explain: the thread may resume before the timeout expires with success, or time out exactly upon the duration. You could conceive of it as: the Condition expires after a certain time, so the thread may time out earlier than its requested timeout duration – but there will always be some conflicting case (e.g. thread A requests a 1 beat timeout, then thread B requests a 0.1 beat timeout – if A’s takes precedence, then B has to wait longer than expected; if B’s takes precedence, then A expected to allow a whole beat but that would be cut short). So I think per-thread timeouts, while I can imagine arguments against them, are the least confusing way.
Condition {
var <>test, waitingThreads, waitingTimeouts;
*new { arg test=false;
^super.newCopyArgs(test, Array(8))
}
wait {
if (test.value.not, {
waitingThreads = waitingThreads.add(thisThread.threadPlayer);
\hang.yield;
});
}
hang { arg value = \hang;
// ignore the test, just wait
waitingThreads = waitingThreads.add(thisThread.threadPlayer);
value.yield;
}
setTimeout { |timeout|
var waitingThread = thisThread.threadPlayer;
var timeoutThread = Routine {
timeout.wait;
waitingThreads.remove(waitingThread);
waitingTimeouts.remove(timeoutThread);
waitingThread.clock.sched(0, waitingThread);
};
waitingTimeouts = waitingTimeouts.add(timeoutThread);
timeoutThread.play(thisThread.clock);
}
signal {
var tempWaitingThreads, time;
if (test.value, {
waitingTimeouts.do { |thread|
thread.stop;
};
waitingTimeouts = nil;
time = thisThread.seconds;
tempWaitingThreads = waitingThreads;
waitingThreads = nil;
tempWaitingThreads.do({ arg thread;
thread.clock.sched(0, thread);
});
});
}
unhang {
var tempWaitingThreads, time;
// ignore the test, just resume all waiting threads
waitingTimeouts.do { |thread|
thread.stop;
};
waitingTimeouts = nil;
time = thisThread.seconds;
tempWaitingThreads = waitingThreads;
waitingThreads = nil;
tempWaitingThreads.do({ arg thread;
thread.clock.sched(0, thread);
});
}
}
Here’s a quick test/demo case:
(
fork {
var cond = Condition.new;
var result;
thisThread.clock.sched(0.5, {
if(0.5.coin) {
result = 1;
cond.unhang;
};
});
cond.setTimeout(1).hang;
if(result.notNil) {
"Result: %\n".postf(result);
} {
"Timed out".postln;
};
};
)
hjh