Why does the server freeze up when using fork{loop{x.do}}}?

I’ve been having this problem for some time, and it seemed to introduce itself in a pretty tried and tested workflow, but this might be a no-no that I’ve somehow not learned about until now.

// small example:
SynthDef(\name, {
	var sig = SinOsc.ar(\freq.kr(200));
	var env = EnvGen.ar(
		Env.perc(), 1, doneAction: 2
	);
	Out.ar(0, sig * env * 0.1);
}).add;

x = (
  freq: 220,
)

(
  fork{
	loop{
      s.bind {
        Synth(\name, [freq: x.freq]);
        wait(0.2);

      };
    }
  }
)

I’ve used similar setups to do “concurrency” in supercollider between different voices or parts playing simultaneously, i.e. nested forks.

This simple example may run only once, then the server-side freezes and will not take any more command. The post window gives this wierd output taking too long to kill server.

I’ve tried this on the IDE and in neovim-scnvim, where neovim simply crashes and throws you out to the command line, while the IDE just halts.

Could any of you shed som light on this behaviour?

I’m just armchair philosophising here, but to me it looks like you are making the interpreter loop over your s.binds without letting it come up for air. The wait inside has no effect, since it’s being bundled up with the OSC that creates a new Synth, it’s not affecting the loop. (It won’t have any effect in the server either, since it’s an instruction to the interpreter, and nothing follows it – imo! ).

http://doc.sccode.org/Classes/Server.html#-bind

You could move the wait outside the s.bind, but then why even have the s.bind? I guess if this a reduced demo of a more elaborate bundle it might make sense … but as it stands here, I don’t think it does.

Cheers,
eddi
https://alln4tural.bandcamp.com

1 Like

The solution is as @alln4tural says, that i forgot where i put the wait(); It should ofcourse be put outside of the s.bind{}.

i watched your talk at the notam meetup @nathan. I usually don’t remember to use s.bind{} and it hasn’t been a huge issue, but I tried doing something similar to what you did on your demo a while back, and that just wasn’t possible without s.bind{}.

I got curious about this one… “server freeze” might have been an infinite loop with no pause (alln4tural’s guess), but the wait is taking effect, so it isn’t that.

The way bind works is:

  • Before the user function, 1/ save the server’s current NetAddr in a variable and 2/ replace the server’s addr with a BundleNetAddr.
  • Then run the user function. Any messages sent to the server will be collected in the BundleNetAddr.
  • After the user function, 1/ reset the server’s addr to the original address; 2/ get the bundle out of the BundleNetAddr, and send it with a timestamp.

This assumes that the user function will finish immediately, so that the server can be restored to its original condition right away.

wait inside bind breaks that assumption – it will be collecting messages for the next 0.2 beats – the BundleNetAddr remains in place during that time.

loop has an infinite number of repeats. So, the only way to stop this routine is from outside (cmd-. or .stop). This can happen only when the routine is paused – and the only time the routine pauses is in the middle of bind, when s.addr is the BundleNetAddr.

So, after stopping the routine, the server object is effectively corrupted. Running a modified version of the routine for the first time, “before” is OK but “after” is not:

(
t = Task {
	s.addr.debug("before");
	loop {
		s.bind {
			Synth(\name, [freq: x.freq].debug("args"));
			wait(0.2);
		};
	};
}.play;

w = SimpleController(t)
.put(\stopped, {
	w.remove;
	s.addr.debug("after");
});
)

-> a SimpleController
// this is correct
before: a NetAddr(127.0.0.1, 57110)
args: [ freq, 220 ]  // note, these are 0.2 sec apart
args: [ freq, 220 ]  // ... no hang
args: [ freq, 220 ]
args: [ freq, 220 ]
args: [ freq, 220 ]

// this is not OK
after: a BundleNetAddr(127.0.0.1, 57110)

And if you run the fork block again, you’ll get “before: a BundleNetAddr(127.0.0.1, 57110)” – meaning that the server’s natural state now is to collect messages and not send them. (This will include /status messages, with the result that editor environments that depend on sclang to poll the server’s status might think the server stopped or froze.)

I can’t think of a good way for the language to prevent this. (wait == yield, and yield is used for all types of routines, not only scheduling, so it wouldn’t work to check servers’ addr objects upon yield – not to mention that this would add overhead in a potentially high-traffic location.) AFAICS it’s just necessary to take care where to write .wait.

hjh

1 Like

Ah, does a similar thing occur when using s.bind{ ... } with .onFree? I answered my own question without knowing the exact reason:

I don’t think that’s the same case. onFree doesn’t block the thread like wait does.

hjh

1 Like