Skip to content

Commit f9fe75c

Browse files
committed
Merge branch 'mhaseebtariq-master'
2 parents 3801038 + 995980b commit f9fe75c

5 files changed

Lines changed: 53 additions & 14 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ test:
1414
$(PYTHON) test/test_import.py
1515
$(PYTHON) test/test_memory_usage.py
1616
$(PYTHON) test/test_precision_import.py
17+
$(PYTHON) test/test_exception.py

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,8 @@ cleanup.
447447

448448
`Benjamin Bengfort <https://github.com/bbengfort>`_ added support for tracking the usage of individual child processes and plotting them.
449449

450+
`Muhammad Haseeb Tariq <https://github.com/mhaseebtariq>`_ fixed issue #152, which made the whole interpreter hang on functions that launched an exception.
451+
450452
=========
451453
License
452454
=========

memory_profiler.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import inspect
1717
import subprocess
1818
import logging
19+
import traceback
20+
from signal import SIGKILL
1921

2022

2123
# TODO: provide alternative when multiprocessing is not available
@@ -324,21 +326,34 @@ def memory_usage(proc=-1, interval=.1, timeout=None, timestamps=False,
324326
raise ValueError
325327

326328
while True:
329+
exit_block = False
327330
child_conn, parent_conn = Pipe() # this will store MemTimer's results
328331
p = MemTimer(os.getpid(), interval, child_conn, backend,
329332
timestamps=timestamps,
330333
max_usage=max_usage,
331334
include_children=include_children)
332335
p.start()
333336
parent_conn.recv() # wait until we start getting memory
334-
returned = f(*args, **kw)
335-
parent_conn.send(0) # finish timing
336-
ret = parent_conn.recv()
337-
n_measurements = parent_conn.recv()
338-
if retval:
339-
ret = ret, returned
337+
338+
# When there is an exception in the "proc" - the (spawned) monitoring processes don't get killed.
339+
# Therefore, the whole process hangs indefinitely. Here, we are ensuring that the process gets killed!
340+
try:
341+
returned = f(*args, **kw)
342+
parent_conn.send(0) # finish timing
343+
ret = parent_conn.recv()
344+
n_measurements = parent_conn.recv()
345+
if retval:
346+
ret = ret, returned
347+
except Exception:
348+
if has_psutil:
349+
parent = psutil.Process(os.getpid())
350+
for child in parent.children(recursive=True):
351+
os.kill(child.pid, SIGKILL)
352+
p.join(0)
353+
raise
354+
340355
p.join(5 * interval)
341-
if n_measurements > 4 or interval < 1e-6:
356+
if exit_block or n_measurements > 4 or interval < 1e-6:
342357
break
343358
interval /= 10.
344359
elif isinstance(proc, subprocess.Popen):
@@ -1108,7 +1123,7 @@ def exec_with_profiler(filename, profiler, backend):
11081123
execfile(filename, ns, ns)
11091124
else:
11101125
def exec_with_profiler(filename, profiler, backend):
1111-
choose_backend(backend)
1126+
_backend = choose_backend(backend)
11121127
if _backend == 'tracemalloc' and has_tracemalloc:
11131128
tracemalloc.start()
11141129
builtins.__dict__['profile'] = profiler

mprof

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,9 @@ def read_mprofile_file(filename):
351351
def plot_file(filename, index=0, timestamps=True, children=True, options=None):
352352
try:
353353
import pylab as pl
354-
except ImportError:
354+
except ImportError as e:
355355
print("matplotlib is needed for plotting.")
356+
print(e)
356357
sys.exit(1)
357358
import numpy as np # pylab requires numpy anyway
358359
mprofile = read_mprofile_file(filename)
@@ -455,11 +456,6 @@ def plot_action():
455456
raise OptionValueError("'%s' option must contain two numbers separated with a comma" % value)
456457
setattr(parser.values, option.dest, newvalue)
457458

458-
try:
459-
import pylab as pl
460-
except ImportError:
461-
print("matplotlib is needed for plotting.")
462-
sys.exit(1)
463459
desc = """Plots using matplotlib the data file `file.dat` generated
464460
using `mprof run`. If no .dat file is given, it will take the most recent
465461
such file in the current directory."""
@@ -477,8 +473,21 @@ such file in the current directory."""
477473
type="str", action="callback",
478474
callback=get_comma_separated_args,
479475
help="Plot a time-subset of the data. E.g. to plot between 0 and 20.5 seconds: --window 0,20.5")
476+
parser.add_option("--backend",
477+
help="Specify the Matplotlib backend to use")
480478
(options, args) = parser.parse_args()
481479

480+
try:
481+
if options.backend is not None:
482+
import matplotlib
483+
matplotlib.use(options.backend)
484+
485+
import pylab as pl
486+
except ImportError as e:
487+
print("matplotlib is needed for plotting.")
488+
print(e)
489+
sys.exit(1)
490+
482491
profiles = glob.glob("mprofile_??????????????.dat")
483492
profiles.sort()
484493

test/test_exception.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
# make sure that memory profiler does not hang on exception
3+
from memory_profiler import memory_usage
4+
5+
def foo():
6+
raise NotImplementedError('Error')
7+
8+
try:
9+
out = memory_usage((foo, tuple(), {}), timeout=1)
10+
except NotImplementedError:
11+
pass
12+
print('Success')

0 commit comments

Comments
 (0)