Proposal: Condition timeouts

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 stopped 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

Sorry Git is giving you a hard time :frowning: I hope you can create a PR for this at some point.

A few questions:

  • IIUC, with this implementation test could be a Boolean or a Function that returns a Boolean? It look like that’s the case.
  • Shouldn’t waitingTimeouts also be assigned to an Array(8) in *new ?
  • In signal and unhang, you are reseting waitingThreads and waitingTimeouts to nil. Shouldn’t they be reset to an empty Array?

I’m sure the git problem is not too hard; I just ran out of git-fu. At worst, I’ll reclone from my fork and apply patches after that.

That part of the implementation is unchanged from the current Condition. (There’s no need to change it.)

I saw in github there was a discussion of the merits of Boolean or Function here. I tend to think a Function is more expressive. I’m writing on my phone now so I won’t try to write code, but I can send an example later, illustrating why I think so.

I don’t think it’s really necessary.

If you’re creating a Condition, eventually you will hang or wait a thread onto it – so you know that waitingThreads will not go unused. The vast majority of Condition uses in the class library don’t need a timeout, so the chance of putting something into waitingTimeouts is much lower. So: on one hand, we could initialize to nil, and if multiple timeouts are requested (I believe this will be unlikely), GC load would be higher because of array reallocation (but the typical timeout case would add only one item, so reallocation is not likely to be a problem); or OTOH we could preallocate slots for timeouts, knowing that in most cases, the slots will be unused (and in most timeout cases, there would not be multiple threads requesting timeout, so no reallocation) – but there’s a certain minimum GC load which in the large majority of cases would support no actual behavior.

I don’t think it’s necessary to treat both variables identically.

Not necessarily. Calling add on nil to create an array on demand is an idiom that appears elsewhere in the class library.

hjh

Hello, this is an interesting feature. Could you give examples of use in musical practice? I found a comment in Server:sync method asking whether a timeout would be in place there. That certainly made sense. However, it might be of interest to consider that it is possible to do sync outside a routine thread and without using Condition, just by maintaining a queue of actions to be executed in order after receiving each “synced” message from the server. This works for multiple synced actions and is useful in ensuring that buffer or synth set messages happen after a buffer or synth is instantiated on a server. It is however less useful for long action sequences which run inside a pre-scripted routine.

I’d be interested to learn about other uses of Condition possibly in relation to scheduling and coordinating actions.

Iannis

I think different cases call for different sync mechanisms: queues, Condition, Semaphore (whose purpose in SC, tbh, I’m not clear on). So you use Condition when it makes sense to use Condition, and you use a queue when it makes sense to use a queue.

Off the top of my head, I use Condition for:

Flattening a series of asynchronous responses. Suppose you want the user to choose a file from a file dialog, and then load a buffer, and after the buffer is loaded, prepare some variables based on the buffer size.

(
Dialog.openPanel({ |path|
	~buffer = Buffer.read(s, path, action: { |buf|
		~frames = buf.numFrames;
		"loaded % frames\n".postf(~frames);
	});
});
)

(
{
	var cond = Condition.new;
	var path;
	
	Dialog.openPanel { |p|
		path = p;
		cond.unhang;
	};
	cond.hang;
	
	~buffer = Buffer.read(s, path, action: { cond.unhang });
	cond.hang;
	
	~frames = ~buffer.numFrames;
	"loaded % frames\n".postf(~frames);
}.fork(AppClock);
);

Here, the Condition version requires more lines. But, if you need 8 or 10 or 20 asynchronous steps, the nesting will quickly get out of control, while the Condition way will still be a flat sequence (20 steps vs 3 steps would be equally readable).

Multiple simultaneous asynchronous requests. Suppose you’re running 5-6 unixCmd processes, and you have no idea of the order in which they will finish.

(
fork {
	var remaining = 0;
	var cond = Condition({ remaining <= 0 });
	
	5.do { |i|
		"some command, maybe sox...".unixCmd({ |exit|
			remaining = remaining - 1;
			cond.signal;
		});
		remaining = remaining + 1;
	};
	cond.wait;
	
	... now continue...
};
)

Or waiting for n synths to finish, or buffer loading (though formally, the server loads buffers one at a time in a queue).

hjh

Hi James,

For both of your examples, using a queue is a simpler way. You just add all of the functions to the queue and they get executed in succession, while waiting for each of the function in the queue to finish before proceeding to the next one. This does not require fork or a condition. Thanks for the unix cmd idea though. It is an interesting use case and I will try it out.

Best,
Iannis

Not in the case of user interaction, if different actions should be taken at different stages based on user input. Then, you don’t know in advance what to put into the queue.

I said before, “So you use Condition when it makes sense to use Condition, and you use a queue when it makes sense to use a queue” and I don’t really see any way to improve on that position :laughing:

Edit: On second thought, queues and Conditions don’t address the same problem – they shouldn’t be thought of as interchangeable. For example, I could run a queue in a routine, and use a Condition to pause the routine during a queued task.

hjh

@Iannis_Zannos I’m not sure what you are imagining (it would be really helpful if you could post the actual code which illustrates your idea!), but what you’re describing doesn’t sound like it would accomplish the same thing. Just taking the second of @jamshark70 's examples above, the external processes are executing in parallel, not sequentially.

One of the classic examples for using a condition variable is the producer-consumer problem (note, condition variables are sometimes also called monitors). While this isn’t an inherently musical problem, having a producer-consumer relationship between threads can be a useful tool for all sorts of tasks. In the world of multithreaded code, condition variables are a fairly low-level concept, but you can build powerful higher-level objects and workflows on top of them.

Hello Brian,

the queue implementation I am talking about is here:

https://github.com/iani/sc-hacks-redux/blob/master/Classes/Audio_Server_Busses/ActionQueue/Queue.sc
Examples of tests are here:

https://github.com/iani/sc-hacks-redux/tree/master/Snippets/Queue_and_Syncing_210222

In the case above, the objective is to make sure that a buffer or a synthdef has been loaded before using it.

So far I have not found any failure cases.

My implementation of Queue only waits for synced messages from the server. So it

is not capable of waiting for responses from unix commands. But it would be interesting

to add new methods for doing that. I will look at that in the close future and let you know.

As said, coding a sequence of actions in a separate thread does present advantages
if you want to make sure that all statements run in an order without having to send them
to the queue. If I understand correctly, James was referring to that by writing that the forked thread flattens
the action sequence. So my implementation is not an equivalent solution to the problem
presented by James, it is just a handy way for ensuring that busses or synthdefs are loaded,
without having to start a different thread. I’ll try to think of an audible example of two

parallel threads synchronising through conditions.

It seems to me that the timeout proposed by James is correct, but I have not

checked the logic in detail.

Cheers,
Iannis

Ah sorry for the confusion, I was asking if you could just post snippets showing how you would rewrite James’s examples above!

Very likely, it’s using Condition under the hood, then.

What I was saying yesterday: a queue does not replace Condition. A queue implementation can in fact use Condition for sync! Queues only organize data. They’re not a sync mechanism in themselves.

(
var queue = Array.new;
var routine;

~addToQueue = { |path, action|
	queue = queue.add([path, action]);
	~runQueue.();
};

~popFromQueue = {
	var out = queue.first;
	queue = queue.drop(1);
	out
};

~runQueue = {
	if(routine.isNil) {
		routine = Routine {
			// here is a Condition... *supporting* a queue!
			var cond = Condition.new;
			var request, action, buf;
			while {
				request = ~popFromQueue.();
				request.notNil
			} {
				buf = Buffer.read(s, request[0], action: { cond.unhang });
				cond.hang;
				request[1].value(buf);
			};
			routine = nil;  // this routine has expired
		}.play;
	};
	routine
};
)

p = (Platform.userAppSupportDir +/+ "sounds/breaks/old_breaks/*.wav").pathMatch;
p.size;  // 27

p.do { |path| ~addToQueue.(path, { |buf| buf.numFrames.postln }) };
97008
108175
67504
140480
125749
100787
...

hjh

Hi James,

in fact, I am not using Condition in my implementation of Queue.
Perhaps we mean something different?
My Queue class does not even use a Routine or separate Thread of any kind,
it just keeps a list of functions to execute one after the other, and it proceeds
to execute the next function whenever it receives a ‘/synced’ message. It does
use an OSCFunc to wait for that message.

I find this discussion here interesting and stimulating. Exploring these
topics through dialogue and examples is a fun and constructive exercise.
I got some idea of how to illustrate synchronization of threads waiting
for each other to reach some stage in their tasks. I’ll start by using 2
Routines, and code a way for sending each other messages to trigger
them moving forward from a “yield”. This will hopefully help illustrate
the role of condition. We’ll see if any part of this bears analogies to semaphores
as Brian suggested.

Best,
Iannis

No problem with that – in fact, I have a queued buffer-loader in my quarks, which doesn’t use Routine or Condition.

It’s worth noting, though (and I didn’t realize this at first in this thread), that a queue is first and foremost a data structure [1], whose distinguishing feature is FIFO access (first-in, first-out). (A stack is LIFO last-in, first-out.) A queue by itself implies exactly nothing about flow of control. Queues may be useful in control schemes, but they aren’t control schemes.

In fact, you said as much: “it proceeds to execute the next function whenever it receives a ‘/synced’ message. It does use an OSCFunc to wait for that message.” The queue determines what will be done next, but not when or how to activate that next action.

I think that’s part of the confusion in this thread – if a queue was proposed as an alternative to Condition, it isn’t quite correct – because the part of your buffer-loading mechanism that is an alternative to Condition is the OSCFunc’s action, not the queue per se.

hjh

[1] Queue (abstract data type) - Wikipedia

Hi James,

I think that’s part of the confusion in this thread – if a queue was proposed as an alternative to Condition, it isn’t quite correct – because the part of your buffer-loading mechanism that is an alternative to Condition is the OSCFunc’s action, not the queue per se.

Yes, I agree, the distinction exists. I was merely pointing out the existence of another solution because of the practical advantage of not having to fork a Routine to guarantee that SynthDefs and Buffers are loaded. I’ll study your Quark to see how you implement this.

As an aside, looking into the code of Condition, it seems that the wait and hang as well as the signal and unhang methods are duplicating code and could be rewritten so that wait calls hang and signal uses unhang.

Also, ConditionTimeOut could be written as a separate class that uses a Condition and calls unhang after a time-out interval. I’ll try that next and send a draft.

Best,
Iannis

There’s just been an extended comment about Condition timeouts on github:

There’s already a plan to create a replacement for Condition, with a different, more flexible interface. So I’d suggest that, at this time, it isn’t useful to create any alternate versions of the current Condition (better to wait for Condition v2)… or, if you do want to try, it would be a good idea to understand Brian’s comments thoroughly and design according to that.

hjh

Thanks. I was not aware of that.
Anyway, my ConditionTimeout is here

and the test for it is here

Our emails just crossed as I was preparing this answer. I’ll read the extended comment in Simplify UnitTest.wait - But I am unfamiliar with the test suite and it will take me time to understand…

Best,
Iannis

The comments about Condition are independent of the test suite. You don’t need to worry about that.

hjh

Hi Brian, I saw this right now and realize too late the misunderstanding. I have not written a method that waits for a unixcmd to complete. I don’t know how soon I can make one, but I will look into it. Sorry.
Iannis

Hello Brian and James,
here is my rewriting of the second example above, unixCmd. I made a class UnixCmdQueue for this.
The code for the class is in file 1, and the test (using my own unix cmds as placeholders) is in file 2 below.

Regarding the first example by James, since my current own solution
does not work inside a thread, it cannot
guarantee the serialization of multiple statements depending on the reading of a buffer.
I try to avoid this kind of dependency, and it works for most cases. However,
one can generalize the UnixCmdQueue class to serialize the loading of any number
of unix commands, synthdefs, or buffers (or even Synths and Groups if required).
and to add arrays of such objects as well as Functions to execute in order.
That could be a handy scheme for loading synthdefs and buffers before executing musical
scripts.

Best,
Iannis

Class:

Test examples:

where can I find NamedSingleton ?