OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 import multiprocessing |
| 6 import os |
| 7 import Queue |
| 8 import resource |
| 9 import signal |
| 10 |
| 11 import psutil |
| 12 |
| 13 |
| 14 def _LimitMemory(memory_share): |
| 15 """Limits the memory available to this process, to avoid OOM issues. |
| 16 |
| 17 Args: |
| 18 memory_share: (float) Share coefficient of the total physical memory that |
| 19 the process can use. |
| 20 """ |
| 21 total_memory = psutil.virtual_memory().total |
| 22 memory_limit = memory_share * total_memory |
| 23 resource.setrlimit(resource.RLIMIT_AS, (memory_limit, -1L)) |
| 24 |
| 25 |
| 26 def _MultiprocessingWrapper(queue, memory_share, function, args): |
| 27 """Helper function that sets a memory limit on the current process, then |
| 28 calls |function| on |args| and writes the results to |queue|. |
| 29 |
| 30 Args: |
| 31 queue: (multiprocessing.Queue) Queue where the results of the wrapped |
| 32 function are written. |
| 33 memory_share: (float) Share coefficient of the total physical memory that |
| 34 the process can use. |
| 35 function: The wrapped function. |
| 36 args: (list) Arguments for the wrapped function. |
| 37 """ |
| 38 try: |
| 39 if memory_share: |
| 40 _LimitMemory(memory_share) |
| 41 |
| 42 queue.put(function(*args)) |
| 43 except Exception: |
| 44 queue.put(None) |
| 45 |
| 46 |
| 47 def RunInSeparateProcess(function, args, logger, timeout_seconds, |
| 48 memory_share=None): |
| 49 """Runs a function in a separate process, and kills it after the timeout is |
| 50 reached. |
| 51 |
| 52 Args: |
| 53 function: The function to run. |
| 54 args: (list) Arguments for the wrapped function. |
| 55 timeout_seconds: (float) Timeout in seconds after which the subprocess is |
| 56 terminated. |
| 57 memory_share: (float) Set this parameter to limit the memory available to |
| 58 the spawned subprocess. This is a ratio of the total system |
| 59 memory (between 0 and 1). |
| 60 Returns: |
| 61 The result of the wrapped function, or None if the call failed. |
| 62 """ |
| 63 queue = multiprocessing.Queue() |
| 64 process = multiprocessing.Process(target=_MultiprocessingWrapper, |
| 65 args=(queue, memory_share, function, args)) |
| 66 process.daemon = True |
| 67 process.start() |
| 68 |
| 69 result = None |
| 70 |
| 71 try: |
| 72 logger.info('Wait for result.') |
| 73 # Note: If the subprocess somehow crashes (e.g. Python crashing), this |
| 74 # process will wait the full timeout. Could be avoided but probably not |
| 75 # worth the extra complexity. |
| 76 result = queue.get(block=True, timeout=timeout_seconds) |
| 77 except Queue.Empty: |
| 78 logger.warning('Subprocess timeout.') |
| 79 process.terminate() |
| 80 |
| 81 logger.info('Wait for process to terminate.') |
| 82 process.join(timeout=5) |
| 83 |
| 84 if process.is_alive(): |
| 85 logger.warning('Process still alive, hard killing now.') |
| 86 os.kill(process.pid, signal.SIGKILL) |
| 87 |
| 88 return result |
OLD | NEW |