Skip to content

Commit 925f59a

Browse files
[lit] Implement ulimit builtin
This patch implements ulimit inside the lit internal shell. Implementation wise, this functions similar to umask. But instead of setting the limits within the lit test worker process, we set environment variables and add a wrapper around the command to be executed. The wrapper then sets the limits. This is because we cannot increase the limits after lowering them, so we would otherwise end up with a lit test worker stuck with a lower limit. There are several tests where the use of ulimit is essential to the semantics of the test (two in clang, ~7 in compiler-rt), so we need to implement this in order to switch on the internal shell by default without losing test coverage. Reviewers: cmtice, petrhosek, ilovepi Reviewed By: cmtice, ilovepi Pull Request: llvm/llvm-project#157958
1 parent 89f1d7d commit 925f59a

File tree

7 files changed

+98
-1
lines changed

7 files changed

+98
-1
lines changed

lit/TestRunner.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ class ShellEnvironment(object):
9292
we maintain a dir stack for pushd/popd.
9393
"""
9494

95-
def __init__(self, cwd, env, umask=-1):
95+
def __init__(self, cwd, env, umask=-1, ulimit={}):
9696
self.cwd = cwd
9797
self.env = dict(env)
9898
self.umask = umask
9999
self.dirStack = []
100+
self.ulimit = ulimit
100101

101102
def change_dir(self, newdir):
102103
if os.path.isabs(newdir):
@@ -595,6 +596,27 @@ def executeBuiltinUmask(cmd, shenv):
595596
return ShellCommandResult(cmd, "", "", 0, False)
596597

597598

599+
def executeBuiltinUlimit(cmd, shenv):
600+
"""executeBuiltinUlimit - Change the current limits."""
601+
if os.name != "posix":
602+
raise InternalShellError(cmd, "'ulimit' not supported on this system")
603+
if len(cmd.args) != 3:
604+
raise InternalShellError(cmd, "'ulimit' requires two arguments")
605+
try:
606+
new_limit = int(cmd.args[2])
607+
except ValueError as err:
608+
raise InternalShellError(cmd, "Error: 'ulimit': %s" % str(err))
609+
if cmd.args[1] == "-v":
610+
shenv.ulimit["RLIMIT_AS"] = new_limit * 1024
611+
elif cmd.args[1] == "-n":
612+
shenv.ulimit["RLIMIT_NOFILE"] = new_limit
613+
else:
614+
raise InternalShellError(
615+
cmd, "'ulimit' does not support option: %s" % cmd.args[1]
616+
)
617+
return ShellCommandResult(cmd, "", "", 0, False)
618+
619+
598620
def executeBuiltinColon(cmd, cmd_shenv):
599621
"""executeBuiltinColon - Discard arguments and exit with status 0."""
600622
return ShellCommandResult(cmd, "", "", 0, False)
@@ -749,6 +771,7 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
749771
"popd": executeBuiltinPopd,
750772
"pushd": executeBuiltinPushd,
751773
"rm": executeBuiltinRm,
774+
"ulimit": executeBuiltinUlimit,
752775
"umask": executeBuiltinUmask,
753776
":": executeBuiltinColon,
754777
}
@@ -914,6 +937,19 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
914937
if kIsWindows:
915938
args = quote_windows_command(args)
916939

940+
# Handle any resource limits. We do this by launching the command with
941+
# a wrapper that sets the necessary limits. We use a wrapper rather than
942+
# setting the limits in process as we cannot reraise the limits back to
943+
# their defaults without elevated permissions.
944+
if cmd_shenv.ulimit:
945+
executable = sys.executable
946+
args.insert(0, sys.executable)
947+
args.insert(1, os.path.join(builtin_commands_dir, "_launch_with_limit.py"))
948+
for limit in cmd_shenv.ulimit:
949+
cmd_shenv.env["LIT_INTERNAL_ULIMIT_" + limit] = str(
950+
cmd_shenv.ulimit[limit]
951+
)
952+
917953
try:
918954
# TODO(boomanaiden154): We currently wrap the subprocess.Popen with
919955
# os.umask as the umask argument in subprocess.Popen is not
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import sys
2+
import subprocess
3+
import resource
4+
import os
5+
6+
ULIMIT_ENV_VAR_PREFIX = "LIT_INTERNAL_ULIMIT_"
7+
8+
9+
def main(argv):
10+
command_args = argv[1:]
11+
for env_var in os.environ:
12+
if env_var.startswith(ULIMIT_ENV_VAR_PREFIX):
13+
limit_str = env_var[len(ULIMIT_ENV_VAR_PREFIX) :]
14+
limit_value = int(os.environ[env_var])
15+
limit = (limit_value, limit_value)
16+
if limit_str == "RLIMIT_AS":
17+
resource.setrlimit(resource.RLIMIT_AS, limit)
18+
elif limit_str == "RLIMIT_NOFILE":
19+
resource.setrlimit(resource.RLIMIT_NOFILE, limit)
20+
process_output = subprocess.run(command_args)
21+
sys.exit(process_output.returncode)
22+
23+
24+
if __name__ == "__main__":
25+
main(sys.argv)

tests/Inputs/shtest-ulimit/lit.cfg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import lit.formats
2+
3+
config.name = "shtest-ulimit"
4+
config.suffixes = [".txt"]
5+
config.test_format = lit.formats.ShTest(execute_external=False)
6+
config.test_source_root = None
7+
config.test_exec_root = None
8+
config.substitutions.append(("%{python}", '"%s"' % (sys.executable)))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import resource
2+
3+
print("RLIMIT_AS=" + str(resource.getrlimit(resource.RLIMIT_AS)[0]))
4+
print("RLIMIT_NOFILE=" + str(resource.getrlimit(resource.RLIMIT_NOFILE)[0]))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# RUN: ulimit -n
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# RUN: ulimit -v 1048576
2+
# RUN: ulimit -n 50
3+
# RUN: %{python} %S/print_limits.py
4+
# Fail the test so that we can assert on the output.
5+
# RUN: not echo return

tests/shtest-ulimit.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Check the ulimit command
2+
3+
# ulimit does not work on non-POSIX platforms.
4+
# UNSUPPORTED: system-windows
5+
6+
# RUN: not %{lit} -a -v %{inputs}/shtest-ulimit | FileCheck %s
7+
8+
# CHECK: -- Testing: 2 tests{{.*}}
9+
10+
# CHECK-LABEL: FAIL: shtest-ulimit :: ulimit-bad-arg.txt ({{[^)]*}})
11+
# CHECK: ulimit -n
12+
# CHECK: 'ulimit' requires two arguments
13+
14+
# CHECK-LABEL: FAIL: shtest-ulimit :: ulimit_okay.txt ({{[^)]*}})
15+
# CHECK: ulimit -v 1048576
16+
# CHECK: ulimit -n 50
17+
# CHECK: RLIMIT_AS=1073741824
18+
# CHECK: RLIMIT_NOFILE=50

0 commit comments

Comments
 (0)