Deep dive into threading, blocking, sync/async, scheduler internals (split from scztt quarks thread)

PauseStream can pause all kinds of streams, not just Routines. See one of my examples above with wrapping a FuncStream, which is not a Routine. So, if you want to push your argument to its full extent, all Streams should have been PauseStreams.

Yeah, the threadPlayer business only really applies (in the sense that writing the slot is not pointless) when the thing wrapped is a Routine. So, indeed if Routines were subclasses of PauseStream instead of Stream, and they had a pause capability of their own, then the threadPlayer trick would not be needed. And the Task class would also be unnecessary.

The other slightly non-obvious thing is that arbitrary objects can be scheduled in SC. If they are not Routines, their awake runs on the main Thread. Technically speaking even the awake of Routines runs on the main Thread. It’s just that it calls next on the Routine which does the context switching with _RoutineResume.

Also, if you want to catalogue issue with Routines/Threads in SC, they also don’t have a join, which exists in many other thread APIs, although as I covered in my earlier posts, you could implement one fairly easily using the terminalValue slot, which is only set by alwaysYield.

Me understant, but I can’t either agree with this (there is no need to agree). Task is a PauseStream which is a Stream, that wraps a Routine which is a Stream, kind of diamond composition here. I really think is redundant, moreover it has some issues described in Task-Description, and those are mainly because the wrapping part, because Routine adds coroutines state to streams but PauseStream needs to implement it in a different way by wrapping a class which should be an ancestor. Streams can be “paused” at any time, they don’t need PauseStream, just avoid calling next, the problem is whether they interact with clocks or not and that’s what Routine is for.

Apologies if I sound negative, I love you all anyway.

Well, there’s no SelectStream class in SC (yet), but if one were to implement it, it’s easier to do with the machinery provided by an externally accessible bit like threadPlayer. If the state of the pause were internal to the Routine, then a SelectStream would have to figure out which of its sub-thingies are Routines, and walk over its routines and pause just those.

By SelectStream I mean something that would take a collection of streams as sub-objects but its awake would only go to one chosen by an index. On 2nd thought, SelectStreams would be better name for something like this.

Also, one could write a StreamRedirect that does something more arbitrary than just pause the stream, by analogy with how EnvironmentRedirect works.

By the way, there’s an element of aspect-oriented programming (AOP) to both EnvironmentRedirect and PauseStream.

I agree that there is no need to agree :wink:

Well, that’s exactly the problem, isn’t it… because, once a Routine is scheduled on a clock, then you can’t avoid calling next – it will awake, and it will call next at that point.

You could also say that if there were a method to remove something from a clock’s queue, then you wouldn’t need PauseStream – you’d just pull it out of the queue, and then it wouldn’t be nexted. But currently, there is no way to de-schedule something that has been scheduled.

In short, there would be two design approaches: 1/ (not the current approach) make Routine be able to resume after being stopped, and support de-scheduling, and then you could get rid of PauseStream/Task. 2/ (current approach) Routine is low-level and doesn’t recover; no de-scheduling; use wrapper classes to handle these cases. (It could be seen as an advantage of 2/ that more of the functionality is exposed in the class library, rather than being hidden in more complex C++ primitives.)

But, under 1/, you would still need EventStreamPlayer.

hjh

There is:

(
var arrayOfRoutines = Array.fill(5, { |i|
	Routine {
		inf.do { |j| [i, j].yield }
	}
});

~selectStream = Pswitch1(arrayOfRoutines, Pwhite(0, 4, inf)).asStream;
)

~selectStream.next;  // try many times

hjh

I think Lucas’ point is that merely for avoiding that problem, a single bit test in Routine.awake would have been sufficient. The state bit would have to be saved in the Thread context, of course… which is what threadPlayer ended up adding anyway.

But the point he’s missing is the extra flexibility that a pointer to an external wrapper adds, although this wasn’t exactly exploited yet, as I noted above. A StreamRedirect or StreamSelector class has yet to be written.

You may have a point there, but there’s no clock involved in your example. Merely picking the output of a stream (or not) by calling it (or not) is what PauseStream used to do before 2012. The complicated issue is if your Routines also try to hang on a Condition or use a Semaphore, which doesn’t happen in your example. threadPlayer was added for the latter.

(
var fva = FlowVar.new ! 5;

var ra = fva.collect { |r, i| Routine {
	var v;
	"Routine % starts".format(i).postln;
	v = fva[i].value;
	"Routine % got its value % to work with".format(i, v).postln;
	v.yield;
}};
~ra = ra;
~selectStream = Pswitch1(ra, Pwhite(0, 4, inf)).asStream;
~fva = fva;
)

~selectStream.next
// Routine 1 starts (or some other of them)
// -> hang

So now Pswitch1 needs to understand \hangs, i.e. know something about how synchronization primitive in SC cross-talk with routine value-return method (yield).

~fva.do { |fv, i| ~fva[i].value = i }

// this does work past that, once the real values are returned
~selectStream.nextN(5)

// prints something like
// Routine 0 starts
// Routine 2 starts
// Routine 2 got its value 2 to work with
// -> [ hang, hang, 2, nil, nil ]

Using synchronization “primitives” inside Routines that go into streams is not at all transparent at the moment, in terms of values returned, i.e. they are a super-leaky abstraction as currently implemented.

Let’s try to put Pswitch1 on a clock. Will it do the right thing, meaning eventually wake up when all the flovars are filled? Probably not.

~ra do: _.reset

AppClock.play(~selectStream)
// Routine 4 starts (e.g.)

~fva.do { |fv, i| ~fva[i].value = i }

// Nothing gets resumed 

I see that Pswitch1 turns its output into nils after it sees any any nil on its inputs. But
even if change the routines to never return any nil, it still doesn’t work as one might hope

(
~fva = FlowVar.new ! 5;
~ra = ~fva.collect { |r, i| Routine {
	var v;
	"Routine % starts".format(i).postln;
	v = ~fva[i].value + 0.1; // avoid zeroes "just in case".
	"Routine % got its value % to work with".format(i, v).postln;
	v.alwaysYield; // changed so no nils returned, "just in case..."
}};
~selectStream = Pswitch1(~ra, Pwhite(0, 4, inf)).asStream;
)

AppClock.play(~selectStream)
// Routine 0 starts

~fva.do { |fv, i| ~fva[i].value = i }
// Nothing gets resumed; Pswitch1 is emitting 4 more hangs to the clock.

4 do: { AppClock.play(~selectStream) }
// Routine 4 starts
// Routine 2 starts
// Routine 1 starts
// Routine 0 got its value 0.1 to work with
// Routine 3 starts

And that happens despite the fact that the FlowVars are all filled and so enabled all the routines to execute. Pswitch1 is basically “looking into the past” when the Routines were waiting on Conditions (embedded in the FlowVars) even after the routines are not doing that anymore, because Pswitch1 doesn’t understand anything about synchronization “primitives” (or rather protocols) in SC.

And it makes zero difference if you do

~selectStream.play 

instead, because that’s also from Stream, not the smarty version that something like PauseStream overrides, and which a true synchronization-aware StreamSwitcher would also have to do.

I’m sorry that I misunderstood your use case – the first post about a SelectStream was less than clear.

May I ask, though, what is the purpose?

What I mean is, I can take SuperCollider out right now and improvise on it (the other night, did so for over an hour, with a colleague on a drum machine), and it will handle tens of thousands of synth nodes (not all of them concurrently of course, spread out over time – the Christmas eve gig probably went over node ID 30000), I’ve got OSC going in and out, live coding statements being compiled by sclang code, etc etc. My day-to-day experience with SC is that the lack of a syncable SelectStream has not impaired my ability to get good results out of the tool, not in the slightest.

You’ve got something in mind and I’ll admit to being not quite insightful enough to see what it is.

I’ll freely admit that I’m only secondarily interested in programming. It’s a means to an end. If I can get a good result out of a mechanism that isn’t 100% perfect, I’ll get the good result and leave it. So I’m finding it a bit difficult to connect with this aspect of the discussion. Perhaps I should step back from it.

hjh

I wasn’t clear, my point was that a Stream is not a Routine (nor a Thread) in OOP. Stream defines next and reset, Routine defines awake[1], play and stop, so it could define pause and resume.

[1] Yes, awake is defined in object, for the same reason nil knows everything, but in practice only works with routines and functions.

It works with “pure” streams too, like FuncStream, although arguably that’s only a wrapper for a pair of functions. Eventually any fancy thing that executes needs a function somewhere in its guts :smiley:

By the way, I think awake should work for AbstractFunction as well, meaning the override that it gets in Function should really be in the superclass AbstractFunction. I suspect this may have been an oversight because PauseStream overrides awake with the exact same implementation that it gets from Object anyway. Since PauseStream is a sub-class of AbstractFunction, but not of Function, I suspect the re-override was put in PauseStream because someone thought that AbstractFunction overrides awake from Object, although it doesn’t actually do that.

I’ll answer James later. I found a little issue with FlowVar in the meantime. It doesn’t affect the above example, but it does affect my solution to that.

Yep, but I don’t think that is the intended use, the documentation only talk about routines and functions, but since funcstream is an abstractfunction, and, well etc., you know.

@jamshark70 PauseStream doesn’t touch the clock’s scheduling queue (AFAI can see), it adds an intricate pause state which makes next method to return nil (and calculates the delta from the pause call, something that I forgot somewhere else).

Which documentation? It wasn’t documented for function as far as I can tell.

It doesn’t touch the clocks queues but it does touch the Conditions’ queues. It does a substitution there, basically, at addition time. It’s best to look at the commit that added it to see all the interrelated bits.

There’s no need to do anything special with the clock queues because one normally adds the (PauseStream) wrapper to those. So those queues will easily get the right object, unlike the Condition queues which needed the threadPlayer hack.

Somewhere in the docs related with scheduling, I think, can’t find it now. But the thing for me is in which cases can/may be really used? Because everything is a stream and everything can be scheduled, but why? It allows nonsensical cases:

SystemClock.sched(0, { "evaluated once".postln; });  // A way of evaluating a function.
SystemClock.sched(0, { ":D".postln; 1; });  // You can't stop me.
SystemClock.sched(0, "string");  // If a string is evaluated in a clock and no one is around to hear it, does it make a sound?
SystemClock.sched(0, nil);  // Consistent.
SystemClock.sched(0, 1);  // A second running in the background.
SystemClock.sched(0, 0);  // will hang the language.

By the way, the “addition time” bit turns out to be pretty limiting, as I discovered. It’s because the wrapper must exist and have already wrapped the target routine before the latter does any Condiion.wait. But except for simple uses like Task and EventSteamPlayer which spawn their own-ed Routines in their own constructor, in my case the Routines exist before / independently. So they don’t yet have any threadPlayer wrapper set when they wait a Condition… Bassically I have to fake a sort of proxy-Routine that does nothing but is created and then gets filled with stuff that does the work, just so the ref to it can be first pasted into the sub-Routines’ threadPlayer. Like a a forward declaration of sorts.

Alternatively, it would have made more sense for threadPlayer to be a Ref to a Routine (or Stream), so that it can be changed after it’s already on the Condition queues. But that would require patching quite a few places since Refs aren’t exactly transparent in SC (you have to call .value on them).

Sorry, I can’t see a condition being used in PauseStream-pause logic, I see it sets stream to nil so if called after delta and before resume it will get out of the clock and then sets stream again and nextBeat to nil so it will not be played before last delta.

It matters if the wrapped stream uses a Condition itself. Because, simplifying the code slightly

Condition {
	signal {
		//// .....
			waitingThreads.do({ arg thread;
				thread.clock.sched(0, thread);
			});
	}
}

So it moves the waiting threads to the clock’s queue. The question is which one(s): the wrapper or the wrapped. In order to move the wrapper, what gets added to the Cond’s queue is:

	hang { arg value = \hang;
		waitingThreads = waitingThreads.add(thisThread.threadPlayer);
		value.yield;
	}

So it’s not always the calling Thread that gets added to the queue but sometimes its wrapper which is pointed to by threadPlayer. And PauseStream sets itself as the wrapper aka threadPlayer. A rather misleading name was chosen for this field, by the way. I guess it makes sense if you only consired EventStreamPlayers as the only wrappers, 'cause they have “Player” in name, but PauseStream, which is the actual class where the wrapper functionality is implemented, ain’t called a “player”. Duh. Also, one can set the threadPlayer (of a Routine) to any other Routine or anything that supports being scheduled by the clock really, i.e. something that has a working awake.

Epic. I had realized that if you schedule a number it will keep on running (completely silently) because it (a) has awake due to Object and (b) returns itself on next. Also works for a Ref by the way, which is a bit more interesting perhaps, cause you can change what it points to.

AppClock.play(x = Ref(1))
x.value = 0 // high CPU load; doesn't crash for me!
x.value = 2 // relax

Maybe the sys clock is more crash prone. Afraid to try :stuck_out_tongue: Actually I did try it. It seems that
SystemClock has a problem in that it goes to 100% CPU on x.value = 0 but it doesn’t recover when you set x.value = 2, so live-locks the interpreter.

Also a FuncStream is slightly more useful than a plain Function here because you can change the wrapped nextFunc while it runs, without even resorting to jitlib stuff.

f = FuncStream { 1 }
AppClock.play(f)
f.nextFunc = { "Hi!!!".postln; 0.2 }
f.nextFunc = { "Stop".postln }

FuncStream basically works like a FunctionRef here.

I already gave an example like this further above, but if you want it (externally) pause-able, i.e. not implement some logic yourself, and don’t want to use a Routine for some (performance?) reason, as Task would make one for you:

p = PauseStream(f = FuncStream {1}, AppClock).play
f.nextFunc = { "Hi!!!".postln; 0.2 }
p.pause // stops it
p.resume // resumes it

Probalby the quickest way to check that it’s running on the main thread is to try yield something from it.

f.nextFunc = { 5.yield } // ERROR: yield was called outside of a Routine.

I thought for a while I knew how to showcase the necessity of that threadPlayer field, but it turns out that I really couldn’t. Maybe Julian can share his private test case for that.

Actually, I got tricked by some simple threadPlayer resetting behavior in PauseStream. Here’s the hack around it, to showcase what nixing threadPlayer does.

(
var fv = FlowVar();
p = PauseStream(r = Routine { loop {
	("Got" + fv.value).postln; 1.yield }}, AppClock).play;
~fv = fv;
)

p.pause // will (normally) prevent the next line
// from immediately awaking the inner Routine

~fv.value = 12
p.resume // needed to see effect

// do CmdPeriod here

But, nixing the threadPlayer in the following routine makes pause ineffective

(
var fv = FlowVar();
p = PauseStream(r = Routine { loop {
	thisThread.threadPlayer = thisThread; // "freedom"!
	("Got" + fv.value).postln; 1.yield }}, AppClock).play;
~fv = fv;
)

p.pause // not effective now!

~fv.value = 12
// -> a FlowVar
// Got 12
// Got 12

The other obscure thing that bit me is that this in that Routine is actually Interpreter; only thisThread points to the Routine itself.

The reason you still see the r = there and why that’s not just a Task is that I tried to change the threadPlayer externally via that r, but PauseStream is good at resetting it back just in the nick of time, before calling the actual Routine. Only by making the change in the routine itself, right before the Condition inside the FlowVar gets wait-ed, was I able to get past PauseStream’s defenses.

this is the interpreter, for all code outside of class definitions. It’s always been such.

hjh

I actually tried to use just the threadPlayer bit to implement Unix-style select fd semantics over SC streams. It turned out it’s impossible to do it just with that threadPlayer trick. The reason for this impossibility is that threadPlayer lets you substitute one thread for another in the scheduler queues, but for a unix-semantics select you need to co-schedule two threads, the producer and the [select-]consumer. And that one can’t do just with the threadPlayer when there are multiple producers involved. For PauseStream, there is a single producer, namely the stream being wrapped, so the consumer (PauseStream) may decide based solely on the internal state of the consumer (paused state or not) whether to run the producer or not. But in a Unix-select style situation, there are multiple producers, and so the consumer stealing the scheduler activation for one (or even all) of them doesn’t alone help it know which of these producers should have ran so as to run it itself as PauseStream does. So, the threadPlayer bit is not terribly useful outside the PauseStream business.

@jamshark70 To answer your earlier question as what was this for, I was trying to do “reactive streams”, where they don’t merely spit out data for a score-gen, but can block waiting for “external events”–which can be just something else in the program, not necessarily truly external, like MIDI. Alas, something like that’s not easy to pull off with SC’s conception of streams. You need additional synchronization objects which ultimately make the streams pointless as you can just use arrays, queues etc. together with synchronization.