-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rate limiting for tasks. Don't allow a task to be run too many times within a 6 hour window, especially if it's erroring. Also minor refactor of commands.py
- Loading branch information
1 parent
da28b8b
commit c26e353
Showing
18 changed files
with
276 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/clusterfuzz/_internal/base/tasks/task_rate_limiting.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Copyright 2024 Google LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Task rate limiting.""" | ||
|
||
import datetime | ||
|
||
from clusterfuzz._internal.datastore import data_types | ||
from clusterfuzz._internal.datastore import ndb_utils | ||
from clusterfuzz._internal.metrics import logs | ||
from clusterfuzz._internal.system import environment | ||
|
||
|
||
def _get_datetime_now(): | ||
return datetime.datetime.now() | ||
|
||
|
||
# Things that are sometimes run as tasks by commands.py but are really portions | ||
# of actual tasks. | ||
_UTASK_PSEUDO_TASKS = {'uworker_main', 'postprocess', 'preprocess'} | ||
|
||
|
||
class TaskRateLimiter: | ||
"""Rate limiter for tasks. This limits tasks to 100 erroneous runs or 2000 | ||
succesful runs in 6 hours. It keeps track of task completion when record_task | ||
is called at the end of every task.""" | ||
TASK_RATE_LIMIT_WINDOW = datetime.timedelta(hours=6) | ||
TASK_RATE_LIMIT_MAX_ERRORS = 100 | ||
# TODO(metzman): Reevaluate this number, it's probably too high. | ||
TASK_RATE_LIMIT_MAX_COMPLETIONS = 2000 | ||
|
||
def __init__(self, task_name, task_argument, job_name): | ||
self.task_name = task_name | ||
self.task_argument = task_argument | ||
self.job_name = job_name | ||
|
||
def __str__(self): | ||
return ' '.join([self.task_name, self.task_argument, self.job_name]) | ||
|
||
def record_task(self, success: bool) -> None: | ||
"""Records a task and whether it completed succesfully.""" | ||
if self.task_name in _UTASK_PSEUDO_TASKS: | ||
# Don't rate limit these fake uworker tasks. | ||
return | ||
if success: | ||
status = data_types.TaskState.FINISHED | ||
else: | ||
status = data_types.TaskState.ERROR | ||
window_task = data_types.WindowRateLimitTask( | ||
task_name=self.task_name, | ||
task_argument=self.task_argument, | ||
job_name=self.job_name, | ||
status=status) | ||
window_task.put() | ||
|
||
def is_rate_limited(self) -> bool: | ||
"""Checks if the given task is rate limited.""" | ||
if self.task_name in _UTASK_PSEUDO_TASKS: | ||
# Don't rate limit these fake tasks. | ||
return False | ||
if environment.get_value('COMMAND_OVERRIDE'): | ||
# A user wants to run this task. | ||
return False | ||
window_start = _get_datetime_now() - self.TASK_RATE_LIMIT_WINDOW | ||
query = data_types.WindowRateLimitTask.query( | ||
data_types.WindowRateLimitTask.task_name == self.task_name, | ||
data_types.WindowRateLimitTask.task_argument == self.task_argument, | ||
data_types.WindowRateLimitTask.job_name == self.job_name, | ||
data_types.WindowRateLimitTask.timestamp >= window_start) | ||
tasks = ndb_utils.get_all_from_query(query) | ||
completed_count = 0 | ||
error_count = 0 | ||
for task in tasks: | ||
# Limit based on completions. | ||
completed_count += 1 | ||
if completed_count > self.TASK_RATE_LIMIT_MAX_COMPLETIONS: | ||
logs.warning( | ||
f'{str(self)} rate limited. ' | ||
f'It ran at least {self.TASK_RATE_LIMIT_MAX_COMPLETIONS} in window.' | ||
) | ||
return True | ||
|
||
# Limit based on errors. | ||
if task.status == data_types.TaskState.ERROR: | ||
error_count += 1 | ||
if error_count > self.TASK_RATE_LIMIT_MAX_ERRORS: | ||
logs.warning( | ||
f'{str(self)} rate limited. ' | ||
f'It errored at least {self.TASK_RATE_LIMIT_MAX_ERRORS} in window.') | ||
return True | ||
|
||
return False |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/clusterfuzz/_internal/tests/core/base/tasks/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Copyright 2024 Google LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. |
Oops, something went wrong.