class PendingCallbackQueue:
    def __init__(self):
        self.queue = SortedList(key=PendingCallback.key)


    def fire_after(self, delay: float, handler: Callable):
        fire_timestamp = time.monotonic() + delay
        pending_callback = PendingCallback(fire_timestamp, handler)
        self.queue.add(pending_callback)


    def update(self):
        current_timestamp = time.monotonic()
        num_fired_callbacks = self.queue.bisect_key_right(current_timestamp)
        fired_callbacks = self.queue[:num_fired_callbacks]
        del self.queue[:num_fired_callbacks]
        for callback in fired_callbacks:
            callback.handle()
Example #2
0
class Allocation:
    def __init__(self,
                 size: int,
                 name: str = None,
                 policy=AllocationPolicy.FIRST_FIT):
        if size < 1:
            raise ValueError('requested size is negative or zero')

        self._size = size
        self._name = name
        self._policy = policy

        self._total_free = size
        self._first_free = 0  # start addr of first (lowest addr) free block
        self._next_free = 0  # start addr of next free block to consider
        # for allocation

        block = Block(addr=0, size=size, free=True)

        self._blocks = SortedList([block], key=lambda b: b.addr)

    def _dump(self):
        """
        Dump debugging information including the block list.
        """
        print('first free:', self._first_free)
        print('next free:', self._next_free)
        for b in self._blocks:
            print(b)

    def _find_block(self,
                    addr: int,
                    exact: bool = False,
                    require_free: bool = False):
        """
        Find the block that contains a particular address, optionally
        reqiring exactly matching the start address, and optionally requiring
        the block to be free.
        """
        i = self._blocks.bisect_key_right(addr)
        b = self._blocks[i - 1]
        if exact:
            assert b.addr == addr
        if require_free:
            assert b.free
        return b

    def _split_free_block(self, addr: int, size: int):
        """
        Given the address of a free block and a size, split that block into
        two blocks, the first of which will be of the specified size.
        """
        b = self._find_block(addr, exact=True, require_free=True)
        assert size < b.size
        nb = Block(addr=addr + size,
                   size=b.size - size,
                   data=b.data,
                   free=b.free,
                   prev_free=addr,
                   next_free=b.next_free)
        b.size = size
        b.next_free = nb.addr
        self._blocks.add(nb)

    def _allocate_block(self, addr: int, data):
        """
        Given the address of a free block, allocate the block.
        """
        b = self._find_block(addr, exact=True, require_free=True)

        self._total_free -= b.size

        if b.prev_free is not None:
            pb = self._find_block(b.prev_free, exact=True)
            pb.next_free = b.next_free
        else:
            self._first_free = b.next_free

        if b.next_free is not None:
            nb = self._find_block(b.next_free, exact=True)
            nb.prev_free = b.prev_free
        else:
            nb = None

        if self._next_free == addr:
            if nb is not None:
                self._next_free = nb.addr
            else:
                self._next_free = None

        b.free = False
        b.prev_free = None
        b.next_free = None

        b.data = data

    def find_free(self, size: int, addr: int = None) -> int:
        """
        Finds free space of a requested size. If addr is not none,
        only attempts to find free space starting at that address.

        Args:
            size:   The amount of free space to find
            addr:   The address at which to find free space

        Returns:
            An int the address at which the requested amount of space was found.

        Raises:
            AllocationError

        If a call to find_free() without an address argument is
	successful, and is immediately followed by a call to
	allocate() for the same size, the allocation will occur at the
	address returned by find_free().
        """

        if size < 0:
            raise ValueError('requested size is negative')
        if size > self._size:
            raise ValueError('requested size is larger than address space')
        if addr is not None:
            if addr < 0:
                raise ValueError('requested address is negative')
            if addr + size > self._size:
                raise ValueError(
                    'requested block extends beyond address space.')

            b = self._find_block(addr)
            assert addr >= b.addr
            if b.free and addr + size <= b.addr + b.size:
                return addr
            else:
                raise AllocationError('requested address range unavailable')

        second_pass = False
        while True:
            if self._policy == AllocationPolicy.ROTATING_FIRST_FIT:
                #                i = self._blocks.bisect_key_right(self._next_free)
                #                b = self._blocks[i-1]
                b = self._find_block(self._next_free, require_free=True)
            else:
                b = self._find_block(self._first_free, require_free=True)
            if b.size >= size:
                addr = b.addr
                break
            self._next_free = b.next_free
            if self._next_free is None:
                if second_pass or self._policy == AllocationPolicy.FIRST_FIT:
                    raise AllocationError(
                        'insufficient contiguous free space available')
                self._next_free = self._first_free
                second_pass = True
                continue
        return addr

    def is_available(self, addr: int, size: int) -> bool:
        """
        Determines whether a specified address range is free.

        Args:
            addr:   The start address of the address range
            size:   The size of the address range

        Returns:
            True if the specified address range is free.
        """
        try:
            free_addr = self.find_free(addr=addr, size=size)
            return free_addr == addr
        except AllocationError:
            return False

    def allocate(self, size: int, data=None, addr: int = None) -> int:
        """
        Allocate the reqeusted amount of space, optionally at a specific
        address. Optionally associate some data with the space.
        """
        debug = False

        if debug:
            print("allocating from:", self.name, "size:", size, "addr:", addr)

        if size < 0:
            raise ValueError('requested size is negative')
        if size > self._size:
            raise ValueError('requested size is larger than address space')
        if addr is not None:
            if addr < 0:
                raise ValueError('requested address is negative')
            if addr + size > self._size:
                raise ValueError(
                    'requested block extends beyond address space.')

        if size > self._total_free:
            raise AllocationError('insufficient free space available')

        # If explicit address is not supplied, search free list for a
        # sufficiently large free block.
        if addr is None:
            addr = self.find_free(size)

        while True:
            i = self._blocks.bisect_key_right(addr)
            b = self._blocks[i - 1]
            if not b.free:
                raise AllocationError('requested address is allocated')
            if addr + size > b.addr + b.size:
                raise AllocationError(
                    'insufficient space available at requested address')

            if addr > b.addr:
                # split block at beginning
                if debug:
                    print('splitting block at beginning')
                self._split_free_block(b.addr, addr - b.addr)
                if debug:
                    self._dump()
                continue

            if size < b.size:
                # split block at end
                if debug:
                    print('splitting block at end')
                self._split_free_block(b.addr, size)
                if debug:
                    self._dump()
                continue

            # we now have exact match, change block from free to allocated
            if debug:
                print('have match, allocating')
            self._allocate_block(addr, data)
            break

        return addr

    def free_space(self, addr: int = 0, size: int = None):
        """
        Returns the amount of free space available within an address
        range, which defaults to the entire address space.
        """
        if size is None:
            size = self._size - addr
        if addr < 0:
            raise ValueError('requested address is negative')
        if addr + size > self._size:
            raise ValueError('requested range extends beyond address space.')

        #if addr == 0 and size == self._size:
        #    return self._total_free

        sa = addr
        count = 0
        b = self._find_block(sa)
        sa += b.size
        if b.free:
            count += b.size - (b.addr - addr)
        while addr + size > b.addr + b.size:
            b = self._find_block(sa)
            sa += b.size
            if b.free:
                if addr + size >= b.addr + b.size:
                    count += b.size
                else:
                    count += (addr + size) - b.addr
        return count

    def allocated_space(self, addr: int = 0, size: int = None):
        """
        Returns the amount of allocated space within an address
        range, which defaults to the entire address space.
        """
        if size is None:
            size = self._size - addr
        if addr < 0:
            raise ValueError('requested address is negative')
        if addr + size > self._size:
            raise ValueError('requested range extends beyond address space.')
        return size - self.free_space(addr, size)

    def last_free_range(self) -> int:
        """
        Return the address of the start of the last free block, which
        is one past the end of the last allocated block, if there is one.
        If there are no allocated blocks, the returned value will be zero.
        If the entire address space is allocated, the returned value will
        be the size of the address space.
        """
        b = self._blocks[-1]  # guaranteed to be at least one block, which
        # could be free if nothing is allocated
        if b.free:
            try:
                b = self._blocks[-2]
                assert not b.free  # can't have to consecutive free blocks
            except IndexError:
                return 0
        return b.addr + b.size

    def contiguous_from_zero(self):
        ff = self.find_free(1)
        lf = self.last_free_range()
        return ff == lf