mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-21 18:06:46 +00:00
Some gc hinting and docs
This commit is contained in:
parent
8368a72657
commit
b5659bd312
63
src/allmydata/util/gcutil.py
Normal file
63
src/allmydata/util/gcutil.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
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).
|
||||||
|
"""
|
||||||
|
|
||||||
|
__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()
|
@ -19,6 +19,10 @@ from twisted.internet.interfaces import (
|
|||||||
IStreamServerEndpoint,
|
IStreamServerEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .gcutil import (
|
||||||
|
fileDescriptorResource,
|
||||||
|
)
|
||||||
|
|
||||||
fcntl = requireModule("fcntl")
|
fcntl = requireModule("fcntl")
|
||||||
|
|
||||||
from foolscap.util import allocate_tcp_port # re-exported
|
from foolscap.util import allocate_tcp_port # re-exported
|
||||||
@ -275,6 +279,17 @@ def _foolscapEndpointForPortNumber(portnum):
|
|||||||
s.bind(('', 0))
|
s.bind(('', 0))
|
||||||
portnum = s.getsockname()[1]
|
portnum = s.getsockname()[1]
|
||||||
s.listen(1)
|
s.listen(1)
|
||||||
|
# File descriptors are a relatively scarce resource. The
|
||||||
|
# cleanup process for the file descriptor we're about to dup
|
||||||
|
# is unfortunately complicated. In particular, it involves
|
||||||
|
# the Python garbage collector. See CleanupEndpoint for
|
||||||
|
# details of that. Here, we need to make sure the garbage
|
||||||
|
# collector actually runs frequently enough to make a
|
||||||
|
# difference. Normally, the garbage collector is triggered by
|
||||||
|
# allocations. It doesn't know about *file descriptor*
|
||||||
|
# allocation though. So ... we'll "teach" it about those,
|
||||||
|
# here.
|
||||||
|
fileDescriptorResource.allocate()
|
||||||
fd = os.dup(s.fileno())
|
fd = os.dup(s.fileno())
|
||||||
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||||
flags = flags | os.O_NONBLOCK | fcntl.FD_CLOEXEC
|
flags = flags | os.O_NONBLOCK | fcntl.FD_CLOEXEC
|
||||||
@ -295,6 +310,19 @@ def _foolscapEndpointForPortNumber(portnum):
|
|||||||
@implementer(IStreamServerEndpoint)
|
@implementer(IStreamServerEndpoint)
|
||||||
@attr.s
|
@attr.s
|
||||||
class CleanupEndpoint(object):
|
class CleanupEndpoint(object):
|
||||||
|
"""
|
||||||
|
An ``IStreamServerEndpoint`` wrapper which closes a file descriptor if the
|
||||||
|
wrapped endpoint is never used.
|
||||||
|
|
||||||
|
:ivar IStreamServerEndpoint _wrapped: The wrapped endpoint. The
|
||||||
|
``listen`` implementation is delegated to this object.
|
||||||
|
|
||||||
|
:ivar int _fd: The file descriptor to close if ``listen`` is never called
|
||||||
|
by the time this object is garbage collected.
|
||||||
|
|
||||||
|
:ivar bool _listened: A flag recording whether or not ``listen`` has been
|
||||||
|
called.
|
||||||
|
"""
|
||||||
_wrapped = attr.ib()
|
_wrapped = attr.ib()
|
||||||
_fd = attr.ib()
|
_fd = attr.ib()
|
||||||
_listened = attr.ib(default=False)
|
_listened = attr.ib(default=False)
|
||||||
@ -304,8 +332,12 @@ class CleanupEndpoint(object):
|
|||||||
return self._wrapped.listen(protocolFactory)
|
return self._wrapped.listen(protocolFactory)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
"""
|
||||||
|
If ``listen`` was never called then close the file descriptor.
|
||||||
|
"""
|
||||||
if not self._listened:
|
if not self._listened:
|
||||||
os.close(self._fd)
|
os.close(self._fd)
|
||||||
|
fileDescriptorResource.release()
|
||||||
|
|
||||||
|
|
||||||
def listenOnUnused(tub, portnum=None):
|
def listenOnUnused(tub, portnum=None):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user