Merge "Allow to setup timeout instead of waiting for OOM."
diff --git a/tools/historic_memory_usage.py b/tools/historic_memory_usage.py
index f23faaa..376426f 100755
--- a/tools/historic_memory_usage.py
+++ b/tools/historic_memory_usage.py
@@ -38,6 +38,10 @@
   result.add_option('--output',
                     default='build',
                     help='Directory where to output results')
+  result.add_option('--timeout',
+                    type=int,
+                    default=0,
+                    help='Set timeout instead of waiting for OOM.')
   return result.parse_args(argv)
 
 
@@ -122,7 +126,10 @@
 def run_on_app(options, commit):
   app = options.app
   compiler = options.compiler
-  cmd = ['tools/run_on_app.py', '--app', app, '--compiler', compiler,
+  cmd = ['tools/run_on_app.py',
+         '--app', app,
+         '--compiler', compiler,
+         '--timeout', str(options.timeout),
          '--no-build', '--find-min-xmx']
   stdout = subprocess.check_output(cmd)
   output_path = options.output or 'build'
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 41d4578..b404dd3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -27,6 +27,9 @@
 
 # We use this magic exit code to signal that the program OOM'ed
 OOM_EXIT_CODE = 42
+# According to Popen.returncode doc:
+# A negative value -N indicates that the child was terminated by signal N.
+TIMEOUT_KILL_CODE = -9
 
 def ParseOptions(argv):
   result = optparse.OptionParser()
@@ -58,6 +61,10 @@
                     help='Find the minimum amount of memory we can run in',
                     default=False,
                     action='store_true')
+  result.add_option('--timeout',
+                    type=int,
+                    default=0,
+                    help='Set timeout instead of waiting for OOM.')
   result.add_option('--golem',
                     help='Running on golem, do not build or download',
                     default=False,
@@ -178,7 +185,7 @@
   assert len(args) == 0
   # If we can run in 128 MB then we are good (which we can for small examples
   # or D8 on medium sized examples)
-  not_working = 128
+  not_working = 128 if options.compiler == 'd8' else 1024
   working = 1024 * 8
   exit_code = 0
   while working - not_working > 32:
@@ -191,11 +198,15 @@
     exit_code = run_with_options(options, [], extra_args)
     t1 = time.time()
     print('Running took: %s ms' % (1000.0 * (t1 - t0)))
-    if exit_code != 0 and exit_code != OOM_EXIT_CODE:
-      print('Non OOM error executing, exiting')
-      return 2
+    if exit_code != 0:
+      if exit_code not in [OOM_EXIT_CODE, TIMEOUT_KILL_CODE]:
+        print('Non OOM/Timeout error executing, exiting')
+        return 2
     if exit_code == 0:
       working = next_candidate
+    elif exit_code == TIMEOUT_KILL_CODE:
+      print('Timeout. Continue to the next candidate.')
+      not_working = next_candidate
     else:
       assert exit_code == OOM_EXIT_CODE
       not_working = next_candidate
@@ -356,7 +367,9 @@
             debug=not options.no_debug,
             profile=options.profile,
             track_memory_file=options.track_memory_to_file,
-            extra_args=extra_args, stderr=stderr)
+            extra_args=extra_args,
+            stderr=stderr,
+            timeout=options.timeout)
       if exit_code != 0:
         with open(stderr_path) as stderr:
           stderr_text = stderr.read()
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index d7cf222..9371fb9 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -6,11 +6,12 @@
 import gradle
 import os
 import subprocess
+from threading import Timer
 import utils
 
 def run(tool, args, build=None, debug=True,
         profile=False, track_memory_file=None, extra_args=None,
-        stderr=None, stdout=None, return_stdout=False):
+        stderr=None, stdout=None, return_stdout=False, timeout=0):
   if build is None:
     build, args = extract_build_from_args(args)
   if build:
@@ -36,9 +37,20 @@
     cmd.extend(["--lib", lib])
   cmd.extend(args)
   utils.PrintCmd(cmd)
-  if return_stdout:
-    return subprocess.check_output(cmd)
-  return subprocess.call(cmd, stdout=stdout, stderr=stderr)
+  if timeout > 0:
+    kill = lambda process: process.kill()
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    timer = Timer(timeout, kill, [proc])
+    try:
+      timer.start()
+      stdout, stderr = proc.communicate()
+    finally:
+      timer.cancel()
+    return stdout if return_stdout else proc.returncode
+  else:
+    if return_stdout:
+      return subprocess.check_output(cmd)
+    return subprocess.call(cmd, stdout=stdout, stderr=stderr)
 
 def run_in_tests(tool, args, build=None, debug=True, extra_args=None):
   if build is None: