Skip to content

Commit 9f6f2d9

Browse files
committed
Do not store a FileCacher in the Sandbox.
1 parent 727005d commit 9f6f2d9

10 files changed

Lines changed: 103 additions & 89 deletions

File tree

cms/grading/Sandbox.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class Sandbox:
198198
def __init__(
199199
self,
200200
box_index: int,
201-
file_cacher: FileCacher | None,
201+
shard: int | None,
202202
name: str | None = None,
203203
temp_dir: str | None = None,
204204
):
@@ -207,15 +207,13 @@ def __init__(
207207
box_index: index of this sandbox within the service that wants
208208
to run it. No two boxes with the same `box_index` should exist
209209
at the same time.
210-
file_cacher: an instance of the FileCacher class
211-
(to interact with FS), if the sandbox needs it.
210+
shard: the shard index of the service this sandbox runs in, if any.
212211
name: name of the sandbox, which might appear in the
213212
path and in system logs.
214213
temp_dir: temporary directory to use; if None, use the
215214
default temporary directory specified in the configuration.
216215
217216
"""
218-
self.file_cacher: FileCacher | None = file_cacher
219217
self.name: str = name if name is not None else "unnamed"
220218
self.temp_dir: str = (
221219
temp_dir if temp_dir is not None else config.global_.temp_dir
@@ -226,12 +224,11 @@ def __init__(
226224
# the range [0, 1000) for other uses (command-line scripts like cmsMake
227225
# or direct console users of isolate). Inside each range ids are
228226
# assigned sequentially, with a wrap-around.
229-
if file_cacher is None or file_cacher.service is None:
227+
if shard is None:
230228
box_id = box_index
231229
else:
232230
BOXES_PER_SHARD = 1000
233231
assert box_index < BOXES_PER_SHARD
234-
shard = file_cacher.service.shard
235232
# Note that "shard % 64" might hide misconfiguration.
236233
# However, since shard numbers are global, there is no good way
237234
# to have a number in the [0, num_workers_on_this_machine) range.
@@ -510,18 +507,18 @@ def create_file(self, path: str, executable: bool = False) -> typing.BinaryIO:
510507
return file_
511508

512509
def create_file_from_storage(
513-
self, path: str, digest: str, executable: bool = False
510+
self, path: str, digest: str, file_cacher: FileCacher, executable: bool = False
514511
):
515512
"""Write a file taken from FS in the sandbox.
516513
517514
path: relative path of the file inside the sandbox.
518515
digest: digest of the file in FS.
516+
file_cacher: a FileCacher instance.
519517
executable: to set permissions.
520518
521519
"""
522-
assert self.file_cacher is not None
523520
with self.create_file(path, executable) as dest_fobj:
524-
self.file_cacher.get_file_to_fobj(digest, dest_fobj)
521+
file_cacher.get_file_to_fobj(digest, dest_fobj)
525522

526523
def create_file_from_string(
527524
self, path: str, content: bytes, executable: bool = False
@@ -592,21 +589,25 @@ def get_file_to_string(self, path: str, maxlen: int | None = 1024) -> bytes:
592589
return file_.read(maxlen)
593590

594591
def get_file_to_storage(
595-
self, path: str, description: str = "", trunc_len: int | None = None
592+
self,
593+
path: str,
594+
file_cacher: FileCacher,
595+
description: str = "",
596+
trunc_len: int | None = None,
596597
) -> str:
597598
"""Put a sandbox file in FS and return its digest.
598599
599600
path: relative path of the file inside the sandbox.
601+
file_cacher: a FileCacher instance.
600602
description: the description for FS.
601603
trunc_len: if None, does nothing; otherwise, before
602604
returning truncate it at the specified length.
603605
604606
return: the digest of the file.
605607
606608
"""
607-
assert self.file_cacher is not None
608609
with self.get_file(path, trunc_len=trunc_len) as file_:
609-
return self.file_cacher.put_file_from_fobj(file_, description)
610+
return file_cacher.put_file_from_fobj(file_, description)
610611

611612
def stat_file(self, path: str) -> os.stat_result:
612613
"""Return the stats of a file in the sandbox.

cms/grading/steps/trusted.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"wrong".
3535
3636
"""
37+
from cms.db.filecacher import FileCacher
3738

3839
import logging
3940
import re
@@ -209,6 +210,7 @@ def trusted_step(
209210

210211
def checker_step(
211212
sandbox: Sandbox,
213+
file_cacher: FileCacher,
212214
checker_digest: str | None,
213215
input_digest: str,
214216
correct_output_digest: str,
@@ -220,6 +222,7 @@ def checker_step(
220222
sandbox: the sandbox to run the checker in; should already
221223
contain input, correct output, and user output; the checker is instead
222224
copied from the managers.
225+
file_cacher: the file cacher to use to load the checker.
223226
checker_digest: digest of the checker, will be fetched as
224227
"checker"; if None, an appropriate error for bad configuration of the
225228
task will be generated.
@@ -250,12 +253,12 @@ def checker_step(
250253
logger.error("Configuration error: missing checker in task managers.")
251254
return False, None, None, None
252255
sandbox.create_file_from_storage(CHECKER_FILENAME, checker_digest,
253-
executable=True)
256+
file_cacher, executable=True)
254257

255258
# Copy input and correct output in the sandbox.
256-
sandbox.create_file_from_storage(CHECKER_INPUT_FILENAME, input_digest)
259+
sandbox.create_file_from_storage(CHECKER_INPUT_FILENAME, input_digest, file_cacher)
257260
sandbox.create_file_from_storage(CHECKER_CORRECT_OUTPUT_FILENAME,
258-
correct_output_digest)
261+
correct_output_digest, file_cacher)
259262

260263
# Execute the checker and ensure success, or log an error.
261264
command = ["./%s" % CHECKER_FILENAME,

cms/grading/tasktypes/Batch.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def _do_compile(self, job, file_cacher):
238238

239239
# Copy required files in the sandbox (includes the grader if present).
240240
for filename, digest in filenames_and_digests_to_get.items():
241-
sandbox.create_file_from_storage(filename, digest)
241+
sandbox.create_file_from_storage(filename, digest, file_cacher)
242242

243243
# Run the compilation.
244244
box_success, compilation_success, text, stats = \
@@ -252,6 +252,7 @@ def _do_compile(self, job, file_cacher):
252252
if box_success and compilation_success:
253253
digest = sandbox.get_file_to_storage(
254254
executable_filename,
255+
file_cacher,
255256
"Executable %s for %s" % (executable_filename, job.info))
256257
job.executables[executable_filename] = \
257258
Executable(executable_filename, digest)
@@ -300,9 +301,9 @@ def _execution_step(self, job, file_cacher):
300301

301302
# Put the required files into the sandbox
302303
for filename, digest in executables_to_get.items():
303-
sandbox.create_file_from_storage(filename, digest, executable=True)
304+
sandbox.create_file_from_storage(filename, digest, file_cacher, executable=True)
304305
for filename, digest in files_to_get.items():
305-
sandbox.create_file_from_storage(filename, digest)
306+
sandbox.create_file_from_storage(filename, digest, file_cacher)
306307

307308
# Actually performs the execution
308309
box_success, evaluation_success, stats = evaluation_step(
@@ -346,6 +347,7 @@ def _execution_step(self, job, file_cacher):
346347
if job.get_output:
347348
job.user_output = sandbox.get_file_to_storage(
348349
self._actual_output,
350+
file_cacher,
349351
"Output file in job %s" % job.info,
350352
trunc_len=100 * 1024)
351353

cms/grading/tasktypes/Communication.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def compile(self, job, file_cacher):
223223

224224
# Copy all required files in the sandbox.
225225
for filename, digest in filenames_and_digests_to_get.items():
226-
sandbox.create_file_from_storage(filename, digest)
226+
sandbox.create_file_from_storage(filename, digest, file_cacher)
227227

228228
# Run the compilation.
229229
box_success, compilation_success, text, stats = \
@@ -237,6 +237,7 @@ def compile(self, job, file_cacher):
237237
if box_success and compilation_success:
238238
digest = sandbox.get_file_to_storage(
239239
executable_filename,
240+
file_cacher,
240241
"Executable %s for %s" % (executable_filename, job.info))
241242
job.executables[executable_filename] = \
242243
Executable(executable_filename, digest)
@@ -282,9 +283,9 @@ def evaluate(self, job, file_cacher):
282283
sandbox_mgr = create_sandbox(0, file_cacher, name="manager_evaluate")
283284
job.sandboxes.append(sandbox_mgr.get_root_path())
284285
sandbox_mgr.create_file_from_storage(
285-
self.MANAGER_FILENAME, manager_digest, executable=True)
286+
self.MANAGER_FILENAME, manager_digest, file_cacher, executable=True)
286287
sandbox_mgr.create_file_from_storage(
287-
self.INPUT_FILENAME, job.input)
288+
self.INPUT_FILENAME, job.input, file_cacher)
288289

289290
# Create the user sandbox(es) and copy the executable.
290291
sandbox_user = [
@@ -293,7 +294,7 @@ def evaluate(self, job, file_cacher):
293294
job.sandboxes.extend(s.get_root_path() for s in sandbox_user)
294295
for i in indices:
295296
sandbox_user[i].create_file_from_storage(
296-
executable_filename, executable_digest, executable=True)
297+
executable_filename, executable_digest, file_cacher, executable=True)
297298

298299
# Start the manager. Redirecting to stdin is unnecessary, but for
299300
# historical reasons the manager can choose to read from there
@@ -425,6 +426,7 @@ def evaluate(self, job, file_cacher):
425426
if sandbox_mgr.file_exists(self.OUTPUT_FILENAME):
426427
job.user_output = sandbox_mgr.get_file_to_storage(
427428
self.OUTPUT_FILENAME,
429+
file_cacher,
428430
"Output file in job %s" % job.info,
429431
trunc_len=100 * 1024)
430432
else:

cms/grading/tasktypes/TwoSteps.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def compile(self, job, file_cacher):
190190
job.sandboxes.append(sandbox.get_root_path())
191191

192192
for filename, digest in files_to_get.items():
193-
sandbox.create_file_from_storage(filename, digest)
193+
sandbox.create_file_from_storage(filename, digest, file_cacher)
194194

195195
# Run the compilation.
196196
box_success, compilation_success, text, stats = \
@@ -204,6 +204,7 @@ def compile(self, job, file_cacher):
204204
if box_success and compilation_success:
205205
digest = sandbox.get_file_to_storage(
206206
executable_filename,
207+
file_cacher,
207208
"Executable %s for %s" %
208209
(executable_filename, job.info))
209210
job.executables[executable_filename] = \
@@ -242,9 +243,10 @@ def evaluate(self, job, file_cacher):
242243
for filename, digest in first_executables_to_get.items():
243244
first_sandbox.create_file_from_storage(filename,
244245
digest,
246+
file_cacher,
245247
executable=True)
246248
for filename, digest in first_files_to_get.items():
247-
first_sandbox.create_file_from_storage(filename, digest)
249+
first_sandbox.create_file_from_storage(filename, digest, file_cacher)
248250

249251
first = evaluation_step_before_run(
250252
first_sandbox,
@@ -265,9 +267,10 @@ def evaluate(self, job, file_cacher):
265267
for filename, digest in second_executables_to_get.items():
266268
second_sandbox.create_file_from_storage(filename,
267269
digest,
270+
file_cacher,
268271
executable=True)
269272
for filename, digest in second_files_to_get.items():
270-
second_sandbox.create_file_from_storage(filename, digest)
273+
second_sandbox.create_file_from_storage(filename, digest, file_cacher)
271274

272275
second = evaluation_step_before_run(
273276
second_sandbox,
@@ -324,6 +327,7 @@ def evaluate(self, job, file_cacher):
324327
if job.get_output:
325328
job.user_output = second_sandbox.get_file_to_storage(
326329
TwoSteps.OUTPUT_FILENAME,
330+
file_cacher,
327331
"Output file in job %s" % job.info,
328332
trunc_len=100 * 1024)
329333

cms/grading/tasktypes/util.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def create_sandbox(box_index: int, file_cacher: FileCacher, name: str | None = N
6262
6363
"""
6464
try:
65-
sandbox = Sandbox(box_index, file_cacher, name=name)
65+
shard = file_cacher.service.shard if file_cacher.service is not None else None
66+
sandbox = Sandbox(box_index, shard, name=name)
6667
except OSError:
6768
err_msg = "Couldn't create sandbox."
6869
logger.error(err_msg, exc_info=True)
@@ -272,12 +273,13 @@ def eval_output(
272273
# function, but the type checker isn't smart enough for that
273274
assert user_output_digest is not None
274275
sandbox.create_file_from_storage(EVAL_USER_OUTPUT_FILENAME,
275-
user_output_digest)
276+
user_output_digest,
277+
file_cacher)
276278

277279
checker_digest = job.managers[checker_codename].digest \
278280
if checker_codename in job.managers else None
279281
success, outcome, text, admin_text = checker_step(
280-
sandbox, checker_digest, job.input, job.output,
282+
sandbox, file_cacher, checker_digest, job.input, job.output,
281283
EVAL_USER_OUTPUT_FILENAME, extra_args)
282284

283285
delete_sandbox(sandbox, job, success)

cmstestsuite/unit_tests/grading/steps/fakesandbox.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ class FakeSandbox(Sandbox):
3434
answer get_file or get_file_to_string.
3535
3636
"""
37-
def __init__(self, file_cacher, name=None, temp_dir=None):
38-
super().__init__(0, file_cacher, name, temp_dir)
37+
def __init__(self, shard, name=None, temp_dir=None):
38+
super().__init__(0, shard, name, temp_dir)
3939
self._fake_files = {}
4040

4141
self._fake_execute_data = deque()

0 commit comments

Comments
 (0)