Stopping Forked Function

Hi all -

I have 5 forked functions within a routine - but calling “stop” on the routine doesn’t seem to stop things. I’m sure I’m missing something obvious to most, but maybe someone could advise me in the correct way to write this?

~r =  Routine({

	5.do{
		fork{
	inf.do{|o|
		var m;
		m = {SinOsc.ar(400.rand, 0, 0.01)}.play;
		5.rand.wait;
		m.release(5.0.rand);
	}}};
});

~r.play;
~r.stop;
1 Like

You’re creating Routines that continue running even when the parent Routine is stopped. That’s how it is.

You can’t access all child routines from the parent routine in SC. The language doesn’t do this for you; you must track the children to control them. You can do this in different ways. It can be just a List or a Dictionary.

(
f = {
    var parentRoutine, children;
    children = List[];
    parentRoutine = Routine({
        5.do { |i|
            var child = Routine({
                inf.do {
                    "child %".format(i).postln;
                    1.wait;
                }
            });
            children.add(child);
            child.play;
        };
    });
    [parentRoutine, children]
};
)


# a, b = f.();

a.play;

b.do(_.stop);

1 Like

Also, when you stop the routine(s) it doesn’t automatically release the active synths… so you probably want to keep track of them somehow and release them when you stop the routine

(
~synths = ();

~forked = 5.collect {
  fork {
    inf.do { |o|
      var m;
      m = {SinOsc.ar(400.rand, 0, 0.01)}.play;
      ~synths[m.nodeID] = m;
      s.sync;
      5.rand.wait;
      m.release(5.0.rand);
      ~synths[m.nodeID] = nil;
    }
  }
};
)

~forked.do(_.stop); ~synths.do(_.release(5.0.rand));
2 Likes

Yeah, that’s another confusion. It comes because of the operating system’s threads, which behave just like that. SC Routine is a thread, BUT is very different from an OS thread in Linux or MacOS.

I’m not sure what this has to do with OS threads…

Anyway, the actual issue is that Server resources – such as Synths – always have to be managed manually; there is no garbage collection or reference counting.

The name?

I was talking about parent and child Routines, not the server.

The synth just happened to be there, and it would also need to be tracked like the child routines.

I didn’t say anything implying that a synth is automatically released… that’s 100% on you, my boy))

1 Like

I was talking about parent and child Routines, not the server.

But the post you replied to was talking about releasing Synths…

I didn’t say anything implying that a synth is automatically released… that’s 100% on you, my boy))

I never claimed you did, I was just confused why you started talking about OS threads…

2 Likes

Routines are Threads in sclang. Using the same name for things that behave in entirely different ways can sometimes create confusion. But it’s probably some brain condition I have. :sweat_smile:

1 Like

Yes, the term “Threads” is confusing as sclang Threads/Routines are really coroutines. But note that “Thread” can have very different meanings in general (hardware thread, OS thread, green thread, etc.)

However, I don’t see the source of confusion. What is it about OS threads that users might confuse with sclang Routines?

1 Like

The users were expecting that all child routines would be killed, which is true for the other one.

It’s really not. There is no child-parent relationship between OS threads in a process. In other words: a thread does not own other threads. If you spawn thread B on thread A, B may still run after A has finished. You always need to keep track of threads and join them when they have finished. (Alternatively, you can also detach them, but that is generally discouraged.)

2 Likes

Ok, my man.

When a “parent” process dies/exits, all its threads are terminated. (I am not talking about threads that are kind of “peers” in the process)

I mean, I did not explain why I thought it could create confusion. But you keep trying to find something there. We can just talk about this (I would actually enjoy the conversation), I just don’t think that’s the right place.

1 Like

When a process exits, all its resources are freed (threads, memory, file handles, etc.) This has nothing to do with OS threads in particular. I just thought that was an odd reference and needed some clarification. Now that we have clarified it I think we can leave it that :slight_smile:


Anyway, the point I was trying to make in my first comment is this:

Although sclang is a garbage collected language, all Server resources – Synths, Groups, Buffers, Busses – have to be managed manually by the user. As we have seen, it is easy to accidentally leak Server resources. Unfortunately, there is no “defer” or “cleanup” mechanism in the language. Sclang doesn’t even have user-defined finalizers. (You can install a finalizer in C++, though.)

2 Likes

One quick question about this solution: ~forked seems to play instantly, without the need for a “play.” I could wrap ~forked in a function and play - but that changes how the rest of the code operates . Is there another step to having this as a more discretely controllable element?

Yes, it’s easy to happen. If you are not tracking things in the language, Kansas is going bye-bye,

no way back,

A way around such mistakes would be an improvement indeed,

Sure, wrap it in a function and evaluate it using .value or just .()

(
~playfunc = {

  ~synths = ();

  ~forked = 5.collect {
    fork {
      inf.do { |o|
        var m;
        m = {SinOsc.ar(400.rand, 0, 0.01)}.play;
        ~synths[m.nodeID] = m;
        s.sync;
        5.rand.wait;
        m.release(5.0.rand);
        ~synths[m.nodeID] = nil;
      }
    }
  };

};
)

~playfunc.();
~forked.do(_.stop); ~synths.do(_.release(5.0.rand));

(.value aka .() will basically evaluate the code block as though you used cmd-enter. .play is a convenience method that will wrap the function as a SynthDef and send it to the server.)

1 Like

Here is a way to track the forked routines automatically (though admittedly obscure). This also fixes the non-released nodes by scheduling their release, rather than 'wait’ing for it. (“Waiting” for release is bad because, if you stop the routine, then you’ve also stopped the release from occurring.)

(
~rPat = Pspawner({ |spawner|
	5.do{
		spawner.par(Prout {
			inf.do{ |o|
				var m, dur;
				m = { SinOsc.ar(400.rand, 0, 0.01) }.play;
				dur = rrand(1, 5);
				thisThread.clock.sched(dur, { m.release(5.0.rand) });
				(type: \rest, dur: dur).yield;
			}
		});
	};
});

~r = ~rPat.play;
)

~r.stop;

hjh

3 Likes