Repository: Parsl/parsl

Issue:
Walltime option via python_app decorator not honored
**Describe the bug**
Walltime specified via the python_app decorator as `python_app(walltime=20)` is ignored, while
walltime kwarg to the decorated function works correctly.

**To Reproduce**
Steps to reproduce the behavior, for e.g:
1. Setup Parsl 0.8.0 with Python 3.6 locally
2. Run:
```python
@parsl.python_app(walltime=1)
def my_app_2():
    import time
    time.sleep(2)
    return True

my_app_2().result()  # This should raise AppTimeout
```

**Expected behavior**
Raises AppTimeout

**Environment**
 - OS: ubuntu
 - Python version: Py 3.6
 - Parsl version: 0.9.0 and master

Reported by @ericmjonas


Discussion:
I propose we remove the option to specify walltime to the decorator. 

* We already have some special kwargs that are supported by app_functions via kwargs including `walltime`
* We have tests and users already using this functionality.
* `walltime` as kwarg allows users to specify per app walltimes in cases where the user might be using some custom method to estimate walltimes.



Resolving patch:
diff --git a/parsl/app/app.py b/parsl/app/app.py
--- a/parsl/app/app.py
+++ b/parsl/app/app.py
@@ -21,7 +21,7 @@ class AppBase(metaclass=ABCMeta):
 
     """
 
-    def __init__(self, func, data_flow_kernel=None, walltime=60, executors='all', cache=False):
+    def __init__(self, func, data_flow_kernel=None, executors='all', cache=False):
         """Construct the App object.
 
         Args:
@@ -31,7 +31,6 @@ def __init__(self, func, data_flow_kernel=None, walltime=60, executors='all', ca
              - data_flow_kernel (DataFlowKernel): The :class:`~parsl.dataflow.dflow.DataFlowKernel` responsible for
                managing this app. This can be omitted only
                after calling :meth:`parsl.dataflow.dflow.DataFlowKernelLoader.load`.
-             - walltime (int) : Walltime in seconds for the app execution.
              - executors (str|list) : Labels of the executors that this app can execute over. Default is 'all'.
              - cache (Bool) : Enable caching of this app ?
 
@@ -87,8 +86,6 @@ def App(apptype, data_flow_kernel=None, walltime=60, cache=False, executors='all
         - data_flow_kernel (DataFlowKernel): The :class:`~parsl.dataflow.dflow.DataFlowKernel` responsible for
           managing this app. This can be omitted only
           after calling :meth:`parsl.dataflow.dflow.DataFlowKernelLoader.load`.
-        - walltime (int) : Walltime for app in seconds,
-             default=60
         - executors (str|list) : Labels of the executors that this app can execute over. Default is 'all'.
         - cache (Bool) : Enable caching of the app call
              default=False
@@ -112,13 +109,12 @@ def App(apptype, data_flow_kernel=None, walltime=60, cache=False, executors='all
     def wrapper(f):
         return app_class(f,
                          data_flow_kernel=data_flow_kernel,
-                         walltime=walltime,
                          cache=cache,
                          executors=executors)
     return wrapper
 
 
-def python_app(function=None, data_flow_kernel=None, walltime=60, cache=False, executors='all'):
+def python_app(function=None, data_flow_kernel=None, cache=False, executors='all'):
     """Decorator function for making python apps.
 
     Parameters
@@ -131,8 +127,6 @@ def python_app(function=None, data_flow_kernel=None, walltime=60, cache=False, e
     data_flow_kernel : DataFlowKernel
         The :class:`~parsl.dataflow.dflow.DataFlowKernel` responsible for managing this app. This can
         be omitted only after calling :meth:`parsl.dataflow.dflow.DataFlowKernelLoader.load`. Default is None.
-    walltime : int
-        Walltime for app in seconds. Default is 60.
     executors : string or list
         Labels of the executors that this app can execute over. Default is 'all'.
     cache : bool
@@ -144,7 +138,6 @@ def decorator(func):
         def wrapper(f):
             return PythonApp(f,
                              data_flow_kernel=data_flow_kernel,
-                             walltime=walltime,
                              cache=cache,
                              executors=executors)
         return wrapper(func)
@@ -153,7 +146,7 @@ def wrapper(f):
     return decorator
 
 
-def bash_app(function=None, data_flow_kernel=None, walltime=60, cache=False, executors='all'):
+def bash_app(function=None, data_flow_kernel=None, cache=False, executors='all'):
     """Decorator function for making bash apps.
 
     Parameters
@@ -179,7 +172,6 @@ def decorator(func):
         def wrapper(f):
             return BashApp(f,
                            data_flow_kernel=data_flow_kernel,
-                           walltime=walltime,
                            cache=cache,
                            executors=executors)
         return wrapper(func)
diff --git a/parsl/app/bash.py b/parsl/app/bash.py
--- a/parsl/app/bash.py
+++ b/parsl/app/bash.py
@@ -112,8 +112,8 @@ def open_std_fd(fdname):
 
 class BashApp(AppBase):
 
-    def __init__(self, func, data_flow_kernel=None, walltime=60, cache=False, executors='all'):
-        super().__init__(func, data_flow_kernel=data_flow_kernel, walltime=60, executors=executors, cache=cache)
+    def __init__(self, func, data_flow_kernel=None, cache=False, executors='all'):
+        super().__init__(func, data_flow_kernel=data_flow_kernel, executors=executors, cache=cache)
         self.kwargs = {}
 
         # We duplicate the extraction of parameter defaults
diff --git a/parsl/app/python.py b/parsl/app/python.py
--- a/parsl/app/python.py
+++ b/parsl/app/python.py
@@ -35,11 +35,10 @@ def inject_exception(thread):
 class PythonApp(AppBase):
     """Extends AppBase to cover the Python App."""
 
-    def __init__(self, func, data_flow_kernel=None, walltime=60, cache=False, executors='all'):
+    def __init__(self, func, data_flow_kernel=None, cache=False, executors='all'):
         super().__init__(
             wrap_error(func),
             data_flow_kernel=data_flow_kernel,
-            walltime=walltime,
             executors=executors,
             cache=cache
         )


Test patch:
diff --git a/parsl/tests/test_error_handling/test_python_walltime.py b/parsl/tests/test_error_handling/test_python_walltime.py
--- a/parsl/tests/test_error_handling/test_python_walltime.py
+++ b/parsl/tests/test_error_handling/test_python_walltime.py
@@ -5,16 +5,24 @@
 
 
 @parsl.python_app
-def my_app(walltime=3):
+def my_app(walltime=1):
     import time
-    # the loop count must be substantially bigger than the walltime
-    # but not infinite - so that the test eventually terminates
-    # even if the walltime doesn't work.
-    for n in range(0, 6):
-        time.sleep(1)
+    time.sleep(1.2)
+    return True
 
 
 def test_python_walltime():
     f = my_app()
     with pytest.raises(AppTimeout):
         f.result()
+
+
+def test_python_bad_decorator_args():
+
+    with pytest.raises(TypeError):
+        @pytest.mark.local
+        @parsl.python_app(walltime=1)
+        def my_app_2():
+            import time
+            time.sleep(1.2)
+            return True