Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test suite case for threadPlayer? #5661

Closed
avdrd opened this issue Dec 27, 2021 · 3 comments
Closed

Test suite case for threadPlayer? #5661

avdrd opened this issue Dec 27, 2021 · 3 comments

Comments

@avdrd
Copy link
Contributor

avdrd commented Dec 27, 2021

Motivation

This is more of a question, but there was a large-ish commit in 2012 that added the rather obscure threadPlayer feature, taking a new slot in Thread.

I think I understand roughly what it is for, but I failed to write an example that would showcase its necessity by altering the threadPlayer of a PausedStream. What I tried:

(// A bit of trouble is that we get Routine args from the clock...
// unlike when you call next on a stream when you get to pass in
// something to next.
// And the Routine is also running in a different environment!
// So we need a closure capture to share data like:
var fv = Ref(FlowVar()); // FlowVar is not rebindable so re-wrap

// Not using Task so we can access the Routine ourselves
p = PauseStream(r = Routine { loop {
	("Got" + fv.value.value).postln; 1.yield }}, AppClock).play;
~fv = fv;
)

~fv.value.value = 12 // works to unpause by inner trigger
p.pause // pauses it, externally

~fv.value = FlowVar(); // reset this
// ^^ doubles as a pause on its own by the way
// due the internal logic of our Routine

// On with the 2nd test
~fv.value.value = 14 // paused PauseStream catches this
// so we need to externally resume as well, to see the effect
p.resume


// And the final test; reset one more time
p.pause // pauses it externally

// Now the "BIG HACK"; screw the threadPlayer
r.threadPlayer // -> a PauseStream
r.threadPlayer = r; // this is how un-wrapped Routines are set up
r.threadPlayer // -> a Routine (takes effect)

// ^^ we have to do that BEFORE we do any Condition waits, so
// this reset on the next line must come after the hack
~fv.value = FlowVar();
r.reset;
// We also need to externally resume and pause again so that
// the above reset acutally excutes down to the new FlowVar
p.resume

p.pause // pauses it externally
~fv.value.value = 16 // now in theory it could go
// even though it is supposed to be in externally imposed pause state.
// But the problem is that only the PauseStream is on any clock!
// So no breakage happens, even though we screwed the threadPlayer.

Clearly that doesn't actually break it because the routine is not itself being scheduled on a clock. But I'm having trouble seeing how the latter can be set up in a realistic scenario, i.e. in what setup you have two clocks. A PausedStream wrapping yet another PausedStream? I'm still not seeing why two clocks would come into play there, and without that, it seems to me there's no showcasing.

Edited to add: actually, I know one additional reason why that didn't work... PauseStream reasserted itself as the threadPlayer when I did the last p.resume. r.threadPlayer was a PauseStream again after that.
So it was smarter than my attempt to circumvent it.

Description of Proposed Feature

Given that no test suite case was added with that commit, but I'm fairly sure it fixed a real problem. Can someone at least paste an example illustrating the problem here?

Plan for Implementation

Julian?

@avdrd
Copy link
Contributor Author

avdrd commented Dec 27, 2021

Actually, it was pretty easy after I got past PauseStream's defenses. The trick was to reset threadPlayer right in the Routine before the Condition gets wait-ed (via FlowVar).

( // much more evil routine was needed
var fv = Ref(FlowVar()); // not rebindable so re-wrap
// not using Task so we can access the Routine ourselves
p = PauseStream(r = Routine { loop {
	thisThread.threadPlayer = thisThread; // "freedom"!
	("Got" + fv.value.value).postln; 5.yield }}, AppClock).play;
~fv = fv;
)

p.pause // not effective now!

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

Can be simplified a bit more, since I'm not resetting anything or need external access:

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

p.pause // not effective now!

~fv.value = 12 // triggers despite the pause

@jamshark70
Copy link
Contributor

jamshark70 commented Dec 27, 2021

t = Task {
	var cond = CondVar.new;
	b = Buffer.read(s, something....., action: {
		cond.signalOne;  // edit: retraining my brain away from Condition
	});
	cond.wait;  // point A
	
	// point B: now, at this moment, what should go back onto the clock?
}.play;

Point A: thisThread is the Task's Routine -- not the Task itself.

Point B: If the Routine goes back onto the clock, then the Task object is broken.

So it is necessary for CondVar / Condition / FlowVar etc. to be able to find thisThread's wrapper, if there is one.

I think your test case is over-complicated.

@avdrd
Copy link
Contributor Author

avdrd commented Dec 27, 2021

I'm closing this since I know what I wanted to know. If somebody feels like a testsuite case should be added, feel free to reopen.

@avdrd avdrd closed this as completed Dec 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants