Can't reserve an audio or control Bus

Hi, I’m trying to exclude certain bus numbers from the Bus.alloc system, but can’t get it to work. The method should be s.audioBusAllocator.reserve( 64, 1 ); (if I would want to reserve bus 64) but in SC 3.13.0 this returns an error “ERROR: Message ‘start’ not understood.”. I found an issue from 2021 on the git: (ContiguousBusAllocator reserve fails · Issue #5464 · supercollider/supercollider · GitHub), but the last solution posted in that thread still produces this error. Is there someone who has a workaround for this? Or a fix for the .reserve method? It seems related to the addrOffset not being 0 in the audioBusAllocator.

cheers & thanks,
Wouter

I think I put in a PR for this. It might have already been merged in, but it’s later than 3.13.

hjh

Ah, yes I found the fix, it is jamshark70:topic/FixBlockAllocReserve on jan 16th. I applied it to my local copy, and it solves most cases, but it doesn’t solve this one:

c = ContiguousBlockAllocator(64,0,64);

c.reserve(64,1); // -> ERROR: Message 'start' not understood

// while:

c.reserve(65,1); // no error, but this is the second bus, so I can't reserve the first

// and also:

c = ContiguousBlockAllocator(64,0,0);

c.reserve(0,1); // no problem, now I can reserve the first bus..

cheers,
Wouter

Looking further into this, I think I found a fix:

in the ContiguousBlockAllocator:reserve:

	reserve { |address, size = 1, warn = true|
		var block = array[address - addrOffset] ?? { this.prFindNext(address) }; 

It feels a bit shady, as this first check seems to be a kind of guess but I suppose this first block is always in that place, even though all the following methods can’t seem to find it. Should we make a pull request for this?

cheers & thanks,
Wouter

I can’t check right now, but that doesn’t seem right to me. (nope, I was wrong here, see below)

If the block is available to be reserved, then there are two possibilities:

  1. There’s a range of used addresses that ends at address-1. In that case, array[address] will be a ContiguousBlock where start == address and its used flag is false. Then reserve needs only to split this block into one “used” block with the desired size, and one unused block spanning the rest.
  2. Or, the address is in the middle of an unused block. Then array[address] is expected to be nil, and it’s supposed to scan backward to find the previous block and split into 2 or 3 new regions (unused up to the desired address, used, and if needed, another unused block). In this case it’s wrong to “correct” nil. … but the prFindNext call is original (I think it’s for the upper bound check).

E.g., if you have a 6-slot allocator, the initial state is:

0: block(0, 6, false)
1: nil
2: nil
3: nil
4: nil
5: nil

If you try to reserve 3, then it shouldn’t find “next unused” (though I admit here that I forget what prFindNext does). reserve should scan back and find the 0, 6 block and manipulate it into the final state:

0: block(0, 3, false)
1: nil
2: nil
3: block(3, 1, true)
4: block(4, 2, false)
5: nil

Now, if you reserve 4, it’s an optimization to be able to skip the linear search backward, but the optimization isn’t expected to be the normal case. (Hm, maybe that could be further optimized…)

But in your case, reserving at the beginning when addrOffset > 0, the block should be found :confused: so I don’t have an answer here from my phone. … because reserve does need to handle addrOffset.

I’ll have to take a look a little while later. I’m in the hospital now with a broken fibula so I don’t have control over my computer time atm.

hjh

1 Like

I did just have another look – I had forgotten that the prFindNext is in the original code (I thought you’d added it) – and you’re right that everywhere else, there’s array[address - addrOffset] and only in reserve is the addrOffset omitted.

So my last message was wrong – I think yours is the correct fix.

(The primary alloc/free interface isn’t affected because alloc goes directly to prReserve… hence we didn’t notice this problem for years because the main use pattern “just worked.”)

EDIT: Found more bugs in reserve – needs a thorough code review, not sure when I can do that.

hjh

OK, to distract myself from my current situation :laughing: I streamlined the reserve logic, making it easier to understand and debug. This should eliminate all the edge cases.

It turns out that the prFindNext call was never actually necessary, so I removed it here.

Would you mind kicking the tires a bit? See if any other issues shake out?

diff

To ContiguousBlock, add:

	nextAddress { ^start + size }
	lastAddress { ^start + size - 1 }

That’s a repeated calculation that appears several times.

Then:

	reserve { |address, size = 1, warn = true|
		var block = array[address - addrOffset];

		// block exists: if unused and big enough, reserve, else nil
		if(block.notNil) {
			if(block.used or: { block.size < size }) {
				if(warn) {
					"The block at (%, %) is already in use and cannot be reserved."
					.format(address, size).warn;
				};
				^nil
			} {
				^this.prReserve(address, size, block);
			};
		} {
			// block didn't exist:
			// search backward and validate 2 conditions
			block = this.prFindPrevious(address);
			if(block.isNil) {
				if(warn) {
					"Address % should be at least %".format(address, addrOffset).warn;
				};
				^nil
			};
			if(block.nextAddress < address) {
				Error("ContiguousBlockAllocator:reserve previous block doesn't span requested address (should never happen)").throw;
			};
			// is it used, or too small?
			if(block.used or: {
				block.nextAddress < (address + size)
			}) {
				if(warn) {
					"The block at (%, %) is already in use and cannot be reserved."
					.format(address, size).warn;
				};
				^nil
			} {
				^this.prReserve(address, size, nil, block);
			};
		};
		^nil  // shouldn't get here but just be safe
	}

Tests:

c = ContiguousBlockAllocator(64, 0, 64);
c.reserve(64, 1);  // OK, bug fixed

c.reserve(5, 1);
WARNING: Address 5 should be at least 64

c = ContiguousBlockAllocator(10);

c.reserve(0, 1);

c.reserve(5, 3);

c.reserve(3, 3);  // should collide

c.reserve(6, 1);  // should collide

c.reserve(9, 3);  // should collide -- all 3 collisions are correctly detected

// and this is the only way to get that "should never happen"
// nobody will ever do this
c = ContiguousBlockAllocator(10);

// force an inconsistent state
c.slotAt(\array)[0] = ContiguousBlock(0, 4, false);

c.reserve(6, 1);

ERROR: ContiguousBlockAllocator:reserve previous block doesn't span requested address (should never happen)

hjh