"""
Helpers for managing garbage collection.

:ivar fileDescriptorResource: A garbage-collection-informing resource tracker
    for file descriptors.  This is used to trigger a garbage collection when
    it may be possible to reclaim a significant number of file descriptors as
    a result.  Register allocation and release of *bare* file descriptors with
    this object (file objects, socket objects, etc, have their own integration
    with the garbage collector and don't need to bother with this).

Ported to Python 3.
"""

__all__ = [
    "fileDescriptorResource",
]

import gc

import attr

@attr.s
class _ResourceTracker(object):
    """
    Keep track of some kind of resource and trigger a full garbage collection
    when allocations outnumber releases by some amount.

    :ivar int _counter: The number of allocations that have happened in excess
        of releases since the last full collection triggered by this tracker.

    :ivar int _threshold: The number of excess allocations at which point a
        full collection will be triggered.
    """
    _counter = attr.ib(default=0)
    _threshold = attr.ib(default=25)

    def allocate(self):
        """
        Register the allocation of an instance of this resource.
        """
        self._counter += 1
        if self._counter > self._threshold:
            gc.collect()
            # Garbage collection of this resource has done what it can do.  If
            # nothing was collected, it doesn't make any sense to trigger
            # another full collection the very next time the resource is
            # allocated.  Start the counter over again.  The next collection
            # happens when we again exceed the threshold.
            self._counter = 0


    def release(self):
        """
        Register the release of an instance of this resource.
        """
        if self._counter > 0:
            # If there were any excess allocations at this point, account for
            # there now being one fewer.  It is not helpful to allow the
            # counter to go below zero (as naturally would if a collection is
            # triggered and then subsequently resources are released).  In
            # that case, we would be operating as if we had set a higher
            # threshold and that is not desired.
            self._counter -= 1

fileDescriptorResource = _ResourceTracker()
