Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion numba/core/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ def _uuid(self):
"""
u = self.__uuid
if u is None:
u = str(uuid.uuid1())
u = str(uuid.uuid4())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: cloudpickle uses uuid4 for tracking unique classes. If it's safe for cloudpickle, it is safe for Numba

self._set_uuid(u)
return u

Expand Down
94 changes: 94 additions & 0 deletions numba/tests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
import math
import platform
import os
import signal
import shutil
import subprocess
import sys
import tempfile
import threading
import time
import io
import ctypes
Expand Down Expand Up @@ -1007,6 +1009,98 @@ def run_in_subprocess(code, flags=None, env=None, timeout=30):
return out, err


def strace(work, syscalls, timeout=10):
""" Runs strace whilst executing the function work() in the current process,
Comment thread
sklam marked this conversation as resolved.
Resolved
captures the listed syscalls (list of strings). Takes an optional timeout in
seconds, default is 10, if this is exceeded the process will be sent a
SIGKILL. Returns a list of lines that are output by strace.
"""

# Open a tmpfile for strace to write into.
with tempfile.NamedTemporaryFile('w+t') as ntf:

parent_pid = os.getpid()
strace_binary = shutil.which('strace')
if strace_binary is None:
raise ValueError("No valid 'strace' binary could be found")
cmd = ['strace', # strace
'-q', # quietly (no attach/detach print out)
'-p', str(parent_pid), # this PID
'-e', ','.join(syscalls), # these syscalls
'-o', ntf.name] # put output into this file

# redirect stdout, stderr is handled by the `-o` flag to strace.
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE,)
strace_pid = popen.pid
thread_timeout = threading.Timer(timeout, popen.kill)
thread_timeout.start()

def check_return(problem=''):
ret = popen.returncode
if ret != 0:
msg = ("strace exited non-zero, process return code was:"
f"{ret}. {problem}")
raise RuntimeError(msg)
try:
# push the communication onto a thread so it doesn't block.
# start comms thread
thread_comms = threading.Thread(target=popen.communicate)
thread_comms.start()

# do work
work()
# Flush the output buffer file
ntf.flush()
# interrupt the strace process to stop it if it's still running
if popen.poll() is None:
os.kill(strace_pid, signal.SIGINT)
else:
# it's not running, probably an issue, raise
problem="If this is SIGKILL, increase the timeout?"
check_return(problem)
# Make sure the return code is 0, SIGINT to detach is considered
# a successful exit.
popen.wait()
check_return()
# collect the data
strace_data = ntf.readlines()
# stop the process
popen.terminate()
if popen.poll() is None:
Comment thread
sklam marked this conversation as resolved.
Resolved
# Something has gone wrong, force stop the process
popen.kill()
raise RuntimeError("strace did not terminate.")
finally:
# join communication, should be stopped now as process has
# exited
thread_comms.join()
# should be stopped already
thread_timeout.cancel()

return strace_data


def _strace_supported():
"""Checks if strace is supported and working"""
def force_clone(): # subprocess triggers a clone
subprocess.run([sys.executable, '-c', 'exit()'])

try:
syscall = 'clone'
trace = strace(force_clone, [syscall,])
Comment thread
sklam marked this conversation as resolved.
Resolved
assert syscall in ''.join(trace)
Comment thread
sklam marked this conversation as resolved.
Resolved
except Exception:
return False
else:
return True


_HAVE_STRACE = _strace_supported()


needs_strace = unittest.skipUnless(_HAVE_STRACE, "needs working strace")


class IRPreservingTestPipeline(CompilerBase):
""" Same as the standard pipeline, but preserves the func_ir into the
metadata store after legalisation, useful for testing IR changes"""
Expand Down
48 changes: 47 additions & 1 deletion numba/tests/test_withlifting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import copy
import os
import signal
import subprocess
import sys
import tempfile
import threading
import warnings
import numpy as np

Expand All @@ -13,7 +19,8 @@
from numba import njit, typeof, objmode, types
from numba.core.extending import overload
from numba.tests.support import (MemoryLeak, TestCase, captured_stdout,
skip_unless_scipy)
skip_unless_scipy, needs_strace, linux_only,
strace, needs_subprocess)
from numba.core.utils import PYVERSION
from numba.experimental import jitclass
import unittest
Expand Down Expand Up @@ -1213,5 +1220,44 @@ def foo():
self.assertIn(msg, excstr)


class TestMisc(TestCase):
# Tests for miscellaneous objmode issues. Run serially.

_numba_parallel_test_ = False

@linux_only
@needs_strace
@needs_subprocess
def test_no_fork_in_compilation_impl(self):
# Checks that there is no fork/clone/execve during compilation, see
# issue #7881. This needs running in a subprocess as the offending fork
# call that triggered #7881 occurs on the first call to uuid1 as it's
# part if the initialisation process for that function (gets hardware
# address of machine).

def force_compile():
@njit('void()') # force compilation
def f():
with numba.objmode():
pass

# capture these syscalls:
syscalls = ['fork', 'clone', 'execve']

# check that compilation does not trigger fork, clone or execve
strace_data = strace(force_compile, syscalls)
self.assertFalse(strace_data)

@linux_only
@needs_strace
def test_no_fork_in_compilation(self):
# Runs the test_no_fork_in_compilation_impl test in a subprocess
themod = f'numba.tests.test_withlifting'
testname = 'test_no_fork_in_compilation_impl'
self.subprocess_test_runner(test_module=themod,
test_class='TestMisc',
test_name=testname,)


if __name__ == '__main__':
unittest.main()