Skip to content

Commit 5908201

Browse files
committed
Build: use only one setting for build time limit
Closes #12347
1 parent bb72d07 commit 5908201

File tree

9 files changed

+42
-56
lines changed

9 files changed

+42
-56
lines changed

docs/dev/settings.rst

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
Interesting settings
22
====================
33

4-
DOCKER_LIMITS
5-
-------------
4+
BUILD_MEMORY_LIMIT
5+
------------------
66

7-
A dictionary of limits to virtual machines. These limits include:
7+
The maximum memory allocated to the virtual machine.
8+
If this limit is hit, build processes will be automatically killed.
9+
Examples: '200m' for 200MB of total memory, or '2g' for 2GB of total memory.
810

9-
time
10-
An integer representing the total allowed time limit (in
11-
seconds) of build processes. This time limit affects the parent
12-
process to the virtual machine and will force a virtual machine
13-
to die if a build is still running after the allotted time
14-
expires.
11+
BUILD_TIME_LIMIT
12+
----------------
1513

16-
memory
17-
The maximum memory allocated to the virtual machine. If this
18-
limit is hit, build processes will be automatically killed.
19-
Examples: '200m' for 200MB of total memory, or '2g' for 2GB of
20-
total memory.
14+
An integer representing the total allowed time limit (in seconds) of build processes.
15+
This time limit affects the parent process to the virtual machine and will force a virtual machine
16+
to die if a build is still running after the allotted time expires.
2117

2218
PRODUCTION_DOMAIN
2319
------------------

readthedocs/api/v2/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def create_key(self, project):
1818
Build API keys are valid for 3 hours,
1919
and can be revoked at any time by hitting the /api/v2/revoke/ endpoint.
2020
"""
21-
expiry_date = timezone.now() + timedelta(hours=3)
21+
# Use the build time limit + 15% for the API token
22+
expiry_date = timezone.now() + timedelta(seconds=settings.BUILD_TIME_LIMIT * 1.25)
2223
name_max_length = self.model._meta.get_field("name").max_length
2324
return super().create_key(
2425
# Name is required, so we use the project slug for it.

readthedocs/core/utils/__init__.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,15 @@ def prepare_build(
8686
options["queue"] = project.build_queue
8787

8888
# Set per-task time limit
89-
# TODO remove the use of Docker limits or replace the logic here. This
90-
# was pulling the Docker limits that were set on each stack, but we moved
91-
# to dynamic setting of the Docker limits. This sets a failsafe higher
92-
# limit, but if no builds hit this limit, it should be safe to remove and
93-
# rely on Docker to terminate things on time.
94-
# time_limit = DOCKER_LIMITS['time']
95-
time_limit = 7200
89+
time_limit = settings.BUILD_TIME_LIMIT
9690
try:
9791
if project.container_time_limit:
9892
time_limit = int(project.container_time_limit)
9993
except ValueError:
100-
log.warning("Invalid time_limit for project.")
94+
log.warning(
95+
"Invalid time_limit for project.",
96+
time_limit=project.container_time_limit,
97+
)
10198

10299
# Add 20% overhead to task, to ensure the build can timeout and the task
103100
# will cleanly finish.

readthedocs/doc_builder/constants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
DOCKER_SOCKET = settings.DOCKER_SOCKET
1515
DOCKER_VERSION = settings.DOCKER_VERSION
1616
DOCKER_IMAGE = settings.DOCKER_IMAGE
17-
DOCKER_LIMITS = settings.DOCKER_LIMITS
1817
DOCKER_TIMEOUT_EXIT_CODE = 42
1918
DOCKER_OOM_EXIT_CODE = 137
2019

readthedocs/doc_builder/environments.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
from .constants import DOCKER_HOSTNAME_MAX_LEN
2727
from .constants import DOCKER_IMAGE
28-
from .constants import DOCKER_LIMITS
2928
from .constants import DOCKER_OOM_EXIT_CODE
3029
from .constants import DOCKER_SOCKET
3130
from .constants import DOCKER_TIMEOUT_EXIT_CODE
@@ -581,8 +580,8 @@ class DockerBuildEnvironment(BaseBuildEnvironment):
581580

582581
command_class = DockerBuildCommand
583582
container_image = DOCKER_IMAGE
584-
container_mem_limit = DOCKER_LIMITS.get("memory")
585-
container_time_limit = DOCKER_LIMITS.get("time")
583+
container_mem_limit = settings.BUILD_MEMORY_LIMIT
584+
container_time_limit = settings.BUILD_TIME_LIMIT
586585

587586
def __init__(self, *args, **kwargs):
588587
container_image = kwargs.pop("container_image", None)

readthedocs/projects/tasks/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ def finish_inactive_builds():
167167
These inactive builds will be marked as ``success`` and ``CANCELLED`` with an
168168
``error`` to be communicated to the user.
169169
"""
170+
# TODO: delete this task once we are fully migrated to ``BUILD_HEALTHCHECK``
171+
#
170172
# TODO similar to the celery task time limit, we can't infer this from
171173
# Docker settings anymore, because Docker settings are determined on the
172174
# build servers dynamically.

readthedocs/settings/base.py

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ def TEMPLATES(self):
605605
# https://github.com/readthedocs/readthedocs.org/issues/12317#issuecomment-3070950434
606606
# https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html#visibility-timeout
607607
BROKER_TRANSPORT_OPTIONS = {
608-
'visibility_timeout': 18000, # 5 hours
608+
'visibility_timeout': self.BUILD_TIME_LIMIT * 1.15, # 15% more than the build time limit
609609
}
610610

611611
CELERY_DEFAULT_QUEUE = "celery"
@@ -721,7 +721,13 @@ def TEMPLATES(self):
721721
# since we can't read their config file image choice before cloning
722722
RTD_DOCKER_CLONE_IMAGE = RTD_DOCKER_BUILD_SETTINGS["os"]["ubuntu-22.04"]
723723

724-
def _get_docker_memory_limit(self):
724+
def _get_build_memory_limit(self):
725+
"""
726+
Return the buld memory limit based on available system memory.
727+
728+
We subtract ~1000Mb for overhead of processes and base system, and set
729+
the build time as proportional to the memory limit.
730+
"""
725731
try:
726732
total_memory = int(
727733
subprocess.check_output(
@@ -735,46 +741,31 @@ def _get_docker_memory_limit(self):
735741
# int and raise a ValueError
736742
log.exception("Failed to get memory size, using defaults Docker limits.")
737743

738-
# Coefficient used to determine build time limit, as a percentage of total
739-
# memory. Historical values here were 0.225 to 0.3.
740-
DOCKER_TIME_LIMIT_COEFF = 0.25
744+
BUILD_TIME_LIMIT = 900 # seconds
741745

742746
@property
743-
def DOCKER_LIMITS(self):
747+
def BUILD_MEMORY_LIMIT(self):
744748
"""
745-
Set docker limits dynamically, if in production, based on system memory.
749+
Set build memory limit dynamically, if in production, based on system memory.
746750
747751
We do this to avoid having separate build images. This assumes 1 build
748752
process per server, which will be allowed to consume all available
749753
memory.
750-
751-
We subtract 750MiB for overhead of processes and base system, and set
752-
the build time as proportional to the memory limit.
753754
"""
754755
# Our normal default
755-
limits = {
756-
"memory": "2g",
757-
"time": 900,
758-
}
756+
default_memory_limit = "7g"
759757

760758
# Only run on our servers
761759
if self.RTD_IS_PRODUCTION:
762-
total_memory, memory_limit = self._get_docker_memory_limit()
763-
if memory_limit:
764-
limits = {
765-
"memory": f"{memory_limit}m",
766-
"time": max(
767-
limits["time"],
768-
round(total_memory * self.DOCKER_TIME_LIMIT_COEFF, -2),
769-
),
770-
}
760+
total_memory, memory_limit = self._get_build_memory_limit()
761+
762+
memory_limit = memory_limit or default_memory_limit
771763
log.info(
772-
"Using dynamic docker limits.",
764+
"Using dynamic build limits.",
773765
hostname=socket.gethostname(),
774-
memory=limits["memory"],
775-
time=limits["time"],
766+
memory=memory_limit,
776767
)
777-
return limits
768+
return memory_limit
778769

779770
# Allauth
780771
ACCOUNT_ADAPTER = "readthedocs.core.adapters.AccountAdapter"

readthedocs/settings/docker_compose.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class DockerBaseSettings(CommunityBaseSettings):
1515
RTD_DOCKER_COMPOSE_NETWORK = "community_readthedocs"
1616
RTD_DOCKER_COMPOSE_VOLUME = "community_build-user-builds"
1717
RTD_DOCKER_USER = f"{os.geteuid()}:{os.getegid()}"
18-
DOCKER_LIMITS = {"memory": "2g", "time": 900}
18+
BUILD_MEMORY_LIMIT = "2g"
1919

2020
PRODUCTION_DOMAIN = os.environ.get("RTD_PRODUCTION_DOMAIN", "devthedocs.org")
2121
PUBLIC_DOMAIN = os.environ.get("RTD_PUBLIC_DOMAIN", "devthedocs.org")

readthedocs/settings/test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class CommunityTestSettings(CommunityBaseSettings):
2626
CELERY_ALWAYS_EAGER = True
2727

2828
# Skip automatic detection of Docker limits for testing
29-
DOCKER_LIMITS = {"memory": "200m", "time": 600}
29+
BUILD_TIME_LIMIT = 600
30+
BUILD_MEMORY_LIMIT = "200m"
3031

3132
CACHES = {
3233
"default": {

0 commit comments

Comments
 (0)