Skip to content

Commit

Permalink
Replace old pty-based unbuffered with LD_PRELOAD; #243
Browse files Browse the repository at this point in the history
  • Loading branch information
quantum5 committed Dec 3, 2017
1 parent 2a251ff commit 540eec7
Show file tree
Hide file tree
Showing 5 changed files with 22 additions and 19 deletions.
18 changes: 5 additions & 13 deletions dmoj/cptbox/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import errno
import logging
import os
import pty
import re
import select
import signal
Expand Down Expand Up @@ -140,7 +139,7 @@ class SecurePopen(six.with_metaclass(SecurePopenMeta, Process)):

def __init__(self, debugger, _, args, executable=None, security=None, time=0, memory=0, stdin=PIPE, stdout=PIPE,
stderr=None, env=None, nproc=0, address_grace=4096, personality=0, cwd='',
fds=None, unbuffered=False, wall_time=None):
fds=None, wall_time=None):
self._debugger_type = debugger
self._syscall_index = index = _SYSCALL_INDICIES[debugger]
self._executable = executable or _find_exe(args[0])
Expand All @@ -157,7 +156,7 @@ def __init__(self, debugger, _, args, executable=None, security=None, time=0, me
self._nproc = nproc
self._tle = False
self._fds = fds
self.__init_streams(stdin, stdout, stderr, unbuffered)
self.__init_streams(stdin, stdout, stderr)
self.protection_fault = None

self.debugger._syscall_index = index
Expand Down Expand Up @@ -296,14 +295,11 @@ def _shocker_thread(self):
else:
time.sleep(0.01)

def __init_streams(self, stdin, stdout, stderr, unbuffered):
def __init_streams(self, stdin, stdout, stderr):
self.stdin = self.stdout = self.stderr = None

if unbuffered:
master, slave = pty.openpty()

if stdin is PIPE:
self._child_stdin, self._stdin = (os.dup(slave), os.dup(master)) if unbuffered else os.pipe()
self._child_stdin, self._stdin = os.pipe()
self.stdin = os.fdopen(self._stdin, 'w')
elif isinstance(stdin, int):
self._child_stdin, self._stdin = stdin, -1
Expand All @@ -313,7 +309,7 @@ def __init_streams(self, stdin, stdout, stderr, unbuffered):
self._child_stdin = self._stdin = -1

if stdout is PIPE:
self._stdout, self._child_stdout = (os.dup(master), os.dup(slave)) if unbuffered else os.pipe()
self._stdout, self._child_stdout = os.pipe()
self.stdout = os.fdopen(self._stdout, 'r')
elif isinstance(stdout, int):
self._stdout, self._child_stdout = -1, stdout
Expand All @@ -332,10 +328,6 @@ def __init_streams(self, stdin, stdout, stderr, unbuffered):
else:
self._stderr = self._child_stderr = -1

if unbuffered:
os.close(master)
os.close(slave)

# All communicate stuff copied from subprocess.
def communicate(self, input=None):
# Optimization: If we are only using one pipe, or no pipe at
Expand Down
1 change: 1 addition & 0 deletions dmoj/executors/base_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def __init__(self, problem_id, source_code, **kwargs):
self.problem = problem_id
self.source = source_code
self._hints = kwargs.pop('hints', [])
self.unbuffered = kwargs.pop('unbuffered', None) or False

def cleanup(self):
if not hasattr(self, '_dir'):
Expand Down
12 changes: 9 additions & 3 deletions dmoj/executors/mixins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import os
import shutil
import sys
from shutil import copyfile

from dmoj.judgeenv import env
from dmoj.utils import nobuf_path
from dmoj.utils.unicode import utf8bytes

try:
Expand Down Expand Up @@ -103,7 +105,12 @@ def get_address_grace(self):
return self.address_grace

def get_env(self):
return {'LANG': 'C'}
env = {'LANG': 'C'}
if self.unbuffered:
unbuf = self._file('unbuf.so')
shutil.copyfile(nobuf_path, unbuf)
env['LD_PRELOAD'] = unbuf
return env

def launch(self, *args, **kwargs):
return SecurePopen([utf8bytes(a) for a in self.get_cmdline() + list(args)],
Expand All @@ -114,8 +121,7 @@ def launch(self, *args, **kwargs):
time=kwargs.get('time'), memory=kwargs.get('memory'),
wall_time=kwargs.get('wall_time'),
stderr=(PIPE if kwargs.get('pipe_stderr', False) else None),
env=self.get_env(), cwd=utf8bytes(self._dir), nproc=self.get_nproc(),
unbuffered=kwargs.get('unbuffered', False))
env=self.get_env(), cwd=utf8bytes(self._dir), nproc=self.get_nproc())
except ImportError:
pass

Expand Down
6 changes: 3 additions & 3 deletions dmoj/graders/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ def grade(self, case):
input = case.input_data() # cache generator data

self._current_proc = self.binary.launch(time=self.problem.time_limit, memory=self.problem.memory_limit,
pipe_stderr=True, unbuffered=case.config.unbuffered,
io_redirects=case.io_redirects(),
pipe_stderr=True, io_redirects=case.io_redirects(),
wall_time=case.config.wall_time_factor * self.problem.time_limit)

error = self._interact_with_process(case, result, input)
Expand Down Expand Up @@ -165,7 +164,8 @@ def _generate_binary(self):
try:
# Fetch an appropriate executor for the language
binary = executors[self.language].Executor(self.problem.id, self.source,
hints=self.problem.config.hints or [])
hints=self.problem.config.hints or [],
unbuffered=self.problem.config.unbuffered)
except CompileError as compilation_error:
error = compilation_error.args[0]
error = error.decode('mbcs') if os.name == 'nt' and isinstance(error, six.binary_type) else error
Expand Down
4 changes: 4 additions & 0 deletions dmoj/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import os

nobuf_path = os.path.join(os.path.dirname(__file__), 'nobuf.so')
del os

0 comments on commit 540eec7

Please sign in to comment.