Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting separate time limit for interpreted languages #1247

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cms/db/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ class Dataset(Base):
Float,
CheckConstraint("time_limit > 0"),
nullable=True)
time_limit_interpreted = Column(
Float,
CheckConstraint("time_limit_interpreted > 0"),
nullable=True)
memory_limit = Column(
BigInteger,
CheckConstraint("memory_limit > 0"),
Expand Down
18 changes: 16 additions & 2 deletions cms/grading/Job.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ class EvaluationJob(Job):
submission, or of an arbitrary source (as used in cmsMake).

Input data (usually filled by ES): testcase_codename, language,
files, managers, executables, input, output, time_limit,
files, managers, executables, input, output, time_limit, time_limit_interpreted,
memory_limit. Output data (filled by the Worker): success,
outcome, text, user_output, executables, text, plus. Metadata:
only_execution, get_output.
Expand All @@ -445,7 +445,7 @@ def __init__(self, operation=None, task_type=None,
language=None, multithreaded_sandbox=False,
files=None, managers=None, executables=None,
input=None, output=None,
time_limit=None, memory_limit=None,
time_limit=None, time_limit_interpreted=None, memory_limit=None,
success=None, outcome=None, text=None,
user_output=None, plus=None,
only_execution=False, get_output=False):
Expand All @@ -456,6 +456,8 @@ def __init__(self, operation=None, task_type=None,
input (string|None): digest of the input file.
output (string|None): digest of the output file.
time_limit (float|None): user time limit in seconds.
time_limit_interpreted (float|None): user time limit in seconds
for interpreted-language solutions; if None, time_limit is used.
memory_limit (int|None): memory limit in bytes.
outcome (string|None): the outcome of the evaluation, from
which to compute the score.
Expand All @@ -478,20 +480,30 @@ def __init__(self, operation=None, task_type=None,
self.input = input
self.output = output
self.time_limit = time_limit
self.time_limit_interpreted = time_limit_interpreted
self.memory_limit = memory_limit
self.outcome = outcome
self.user_output = user_output
self.plus = plus
self.only_execution = only_execution
self.get_output = get_output

def effective_time_limit(self):
res = self.time_limit
if self.time_limit_interpreted is not None and self.language is not None:
lang = get_language(self.language)
if lang.is_interpreted:
res = self.time_limit_interpreted
return res

def export_to_dict(self):
res = Job.export_to_dict(self)
res.update({
'type': 'evaluation',
'input': self.input,
'output': self.output,
'time_limit': self.time_limit,
'time_limit_interpreted': self.time_limit_interpreted,
'memory_limit': self.memory_limit,
'outcome': self.outcome,
'user_output': self.user_output,
Expand Down Expand Up @@ -544,6 +556,7 @@ def from_submission(operation, submission, dataset):
input=testcase.input,
output=testcase.output,
time_limit=dataset.time_limit,
time_limit_interpreted=dataset.time_limit_interpreted,
memory_limit=dataset.memory_limit,
info=info
)
Expand Down Expand Up @@ -623,6 +636,7 @@ def from_user_test(operation, user_test, dataset):
executables=dict(user_test_result.executables),
input=user_test.input,
time_limit=dataset.time_limit,
time_limit_interpreted=dataset.time_limit_interpreted,
memory_limit=dataset.memory_limit,
info="evaluate user test %d" % (user_test.id),
get_output=True,
Expand Down
7 changes: 7 additions & 0 deletions cms/grading/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ def requires_multithreading(self):
# Safe default is false.
return False

@property
def is_interpreted(self):
"""Whether the language is interpreted and should be graded with the
interpreted language time limit (when such a limit is specified).
"""
return False

@property
def object_extension(self):
"""Default object extension for the language."""
Expand Down
5 changes: 5 additions & 0 deletions cms/grading/languages/php.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def executable_extension(self):
"""See Language.executable_extension."""
return ".php"

@property
def is_interpreted(self):
"""See Language.is_interpreted."""
return True

def get_compilation_commands(self,
source_filenames, executable_filename,
for_evaluation=True):
Expand Down
5 changes: 5 additions & 0 deletions cms/grading/languages/python2_cpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ def executable_extension(self):
"""See Language.executable_extension."""
return ".zip"

@property
def is_interpreted(self):
"""See Language.is_interpreted."""
return True

def get_compilation_commands(self,
source_filenames, executable_filename,
for_evaluation=True):
Expand Down
5 changes: 5 additions & 0 deletions cms/grading/languages/python3_cpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def executable_extension(self):
# Defined in PEP 441 (https://www.python.org/dev/peps/pep-0441/).
return ".pyz"

@property
def is_interpreted(self):
"""See Language.is_interpreted."""
return True

def get_compilation_commands(self,
source_filenames, executable_filename,
for_evaluation=True):
Expand Down
2 changes: 1 addition & 1 deletion cms/grading/tasktypes/Batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def evaluate(self, job, file_cacher):
box_success, evaluation_success, stats = evaluation_step(
sandbox,
commands,
job.time_limit,
job.effective_time_limit(),
job.memory_limit,
writable_files=files_allowing_write,
stdin_redirect=stdin_redirect,
Expand Down
10 changes: 6 additions & 4 deletions cms/grading/tasktypes/Communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,10 @@ def evaluate(self, job, file_cacher):
# but in practice is num_processes times that because the
# constraint on the total time can only be enforced after all user
# programs terminated.
manager_time_limit = max(self.num_processes * (job.time_limit + 1.0),
config.trusted_sandbox_max_time_s)
user_time_limit = job.effective_time_limit()
manager_time_limit = None if user_time_limit is None else \
max(self.num_processes * (user_time_limit + 1.0),
config.trusted_sandbox_max_time_s)
manager = evaluation_step_before_run(
sandbox_mgr,
manager_command,
Expand Down Expand Up @@ -353,7 +355,7 @@ def evaluate(self, job, file_cacher):
processes[i] = evaluation_step_before_run(
sandbox_user[i],
commands[-1],
job.time_limit,
user_time_limit,
job.memory_limit,
dirs_map={fifo_dir[i]: (sandbox_fifo_dir[i], "rw")},
stdin_redirect=stdin_redirect,
Expand All @@ -377,7 +379,7 @@ def evaluate(self, job, file_cacher):
# sandbox can only check its own; if the sum is greater than the time
# limit we adjust the result.
if box_success_user and evaluation_success_user and \
stats_user["execution_time"] >= job.time_limit:
stats_user["execution_time"] >= user_time_limit:
evaluation_success_user = False
stats_user['exit_status'] = Sandbox.EXIT_TIMEOUT

Expand Down
4 changes: 2 additions & 2 deletions cms/grading/tasktypes/TwoSteps.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def evaluate(self, job, file_cacher):
first = evaluation_step_before_run(
first_sandbox,
first_command,
job.time_limit,
job.effective_time_limit(),
job.memory_limit,
dirs_map={fifo_dir: ("/fifo", "rw")},
stdin_redirect=TwoSteps.INPUT_FILENAME,
Expand All @@ -270,7 +270,7 @@ def evaluate(self, job, file_cacher):
second = evaluation_step_before_run(
second_sandbox,
second_command,
job.time_limit,
job.effective_time_limit(),
job.memory_limit,
dirs_map={fifo_dir: ("/fifo", "rw")},
stdout_redirect=TwoSteps.OUTPUT_FILENAME,
Expand Down
9 changes: 5 additions & 4 deletions cms/server/admin/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,29 +390,30 @@ def get_submission_format(self, dest):
raise ValueError("Submission format not recognized.")
dest["submission_format"] = format_

def get_time_limit(self, dest, field):
def get_time_limit(self, dest, field, field_name="time_limit"):
"""Parse the time limit.

Read the argument with the given name and use its value to set
the "time_limit" item of the given dictionary.
the given item of the given dictionary.

dest (dict): a place to store the result.
field (string): the name of the argument to use.
field_name (string): name of the key to store the result at.

"""
value = self.get_argument(field, None)
if value is None:
return
if len(value) == 0:
dest["time_limit"] = None
dest[field_name] = None
else:
try:
value = float(value)
except:
raise ValueError("Can't cast %s to float." % value)
if not 0 <= value < float("+inf"):
raise ValueError("Time limit out of range.")
dest["time_limit"] = value
dest[field_name] = value

def get_memory_limit(self, dest, field):
"""Parse the memory limit.
Expand Down
1 change: 1 addition & 0 deletions cms/server/admin/handlers/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def post(self, dataset_id_to_copy):
return

self.get_time_limit(attrs, "time_limit")
self.get_time_limit(attrs, "time_limit_interpreted", "time_limit_interpreted")
self.get_memory_limit(attrs, "memory_limit")
self.get_task_type(attrs, "task_type", "TaskTypeOptions_")
self.get_score_type(attrs, "score_type", "score_type_parameters")
Expand Down
2 changes: 2 additions & 0 deletions cms/server/admin/handlers/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def post(self, task_id):
attrs = dataset.get_attrs()

self.get_time_limit(attrs, "time_limit_%d" % dataset.id)
self.get_time_limit(attrs, "time_limit_interpreted_%d" % dataset.id, "time_limit_interpreted")
self.get_memory_limit(attrs, "memory_limit_%d" % dataset.id)
self.get_task_type(attrs, "task_type_%d" % dataset.id,
"TaskTypeOptions_%d_" % dataset.id)
Expand Down Expand Up @@ -418,6 +419,7 @@ def post(self, task_id):
return

self.get_time_limit(attrs, "time_limit")
self.get_time_limit(attrs, "time_limit_interpreted", "time_limit_interpreted")
self.get_memory_limit(attrs, "memory_limit")
self.get_task_type(attrs, "task_type", "TaskTypeOptions_")
self.get_score_type(attrs, "score_type", "score_type_parameters")
Expand Down
7 changes: 7 additions & 0 deletions cms/server/admin/templates/add_dataset.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ <h1><a href="{{ url("task", task.id) }}">{{ task.title }} ({{ task.name }})</a>
</td>
<td><input type="text" name="time_limit" value="{{ 1 if original_dataset is none else original_dataset.time_limit or "" }}"/> second(s)</td>
</tr>
<tr>
<td>
<span class="info" title="Total maximum time for each evaluation when using an interpreted language, in seconds."></span>
Interpreted lang. time limit
</td>
<td><input type="text" name="time_limit_interpreted_{{ dataset.id }}" value="{{ 1 if original_dataset is none else original_dataset.time_limit_interpreted or "" }}"/> second(s)</td>
</tr>
<tr>
<td>
<span class="info" title="Total maximum memory usage for each evaluation, in MiB (2^20 bytes)."></span>
Expand Down
7 changes: 7 additions & 0 deletions cms/server/admin/templates/task.html
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ <h4 id="title_task_type_{{ dataset.id }}" class="toggling_on">Dataset configurat
</td>
<td><input type="text" name="time_limit_{{ dataset.id }}" value="{{ dataset.time_limit if dataset.time_limit is not none else "" }}"/> second(s)</td>
</tr>
<tr>
<td>
<span class="info" title="Total maximum time for each evaluation when using an interpreted language, in seconds."></span>
Interpreted lang. time limit
</td>
<td><input type="text" name="time_limit_interpreted_{{ dataset.id }}" value="{{ dataset.time_limit_interpreted if dataset.time_limit_interpreted is not none else "" }}"/> second(s)</td>
</tr>
<tr>
<td>
<span class="info" title="Total maximum memory usage for each evaluation, in MiB (2^20 bytes)."></span>
Expand Down
8 changes: 8 additions & 0 deletions cms/server/contest/templates/overview.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ <h2>{% trans %}Task overview{% endtrans %}</h2>
<th>{% trans %}Task{% endtrans %}</th>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Time limit{% endtrans %}</th>
<th>{% trans %}Interpreted language time limit{% endtrans %}</th>
<th>{% trans %}Memory limit{% endtrans %}</th>
<th>{% trans %}Type{% endtrans %}</th>
<th>{% trans %}Files{% endtrans %}</th>
Expand All @@ -218,6 +219,13 @@ <h2>{% trans %}Task overview{% endtrans %}</h2>
{% endif %}
</td>
<td>
{% if t_iter.active_dataset.time_limit_interpreted is not none %}
{{ t_iter.active_dataset.time_limit_interpreted|format_duration(length="long") }}
{% else %}
{% trans %}N/A{% endtrans %}
{% endif %}
</td>
<td>
{% if t_iter.active_dataset.memory_limit is not none %}
{{ t_iter.active_dataset.memory_limit|format_size }}
{% else %}
Expand Down
6 changes: 6 additions & 0 deletions cms/server/contest/templates/task_description.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ <h2>{% trans %}Some details{% endtrans %}</h2>
<td colspan="2">{{ task.active_dataset.time_limit|format_duration(length="long") }}</td>
</tr>
{% endif %}
{% if task.active_dataset.time_limit_interpreted is not none %}
<tr>
<th>{% trans %}Interpreted language time limit{% endtrans %}</th>
<td colspan="2">{{ task.active_dataset.time_limit_interpreted|format_duration(length="long") }}</td>
</tr>
{% endif %}
{% if task.active_dataset.memory_limit is not none %}
<tr>
<th>{% trans %}Memory limit{% endtrans %}</th>
Expand Down