Repository: Parsl/parsl

Issue:
Always use a fresh `ignore_for_cache`
A bug is causing the tests to fail in #1182, the error can be found [here](https://travis-ci.com/github/Parsl/parsl/jobs/323140049):
```
DEBUG    parsl.dataflow.memoization:memoization.py:159 Ignoring these kwargs for checkpointing: ['stdout']
DEBUG    parsl.dataflow.memoization:memoization.py:161 Ignoring kwarg stdout
DEBUG    parsl.dataflow.dflow:dflow.py:427 Got an exception launching task
Traceback (most recent call last):
  File "/home/travis/build/Parsl/parsl/parsl/dataflow/dflow.py", line 418, in launch_if_ready
    task_id, task_record['func'], *new_args, **kwargs)
  File "/home/travis/build/Parsl/parsl/parsl/dataflow/dflow.py", line 484, in launch_task
    memo_fu = self.memoizer.check_memo(task_id, self.tasks[task_id])
  File "/home/travis/build/Parsl/parsl/parsl/dataflow/memoization.py", line 199, in check_memo
    hashsum = self.make_hash(task)
  File "/home/travis/build/Parsl/parsl/parsl/dataflow/memoization.py", line 162, in make_hash
    del filtered_kw[k]
KeyError: 'stdout'
```

But note that the failure happens in `test_memoize.py` when the new test was added in `test_stdout.py`. I think this is due to us passing around a mutable default value `ignore_for_cache=[]` in various places; when we add `stdout` and `stderr` to `ignore_for_cache` when `stdout=parsl.AUTO_LOGNAME` is passed in the app signature, that gets propagated later unexpectedly.



Discussion:
It looks like PR #1656 doesn't change defaulting for python_apps. re-opening this as a reminder to fix.

Resolving patch:
diff --git a/parsl/dataflow/dflow.py b/parsl/dataflow/dflow.py
--- a/parsl/dataflow/dflow.py
+++ b/parsl/dataflow/dflow.py
@@ -840,6 +840,9 @@ def submit(self, func, app_args, executors='all', cache=False, ignore_for_cache=
 
         if ignore_for_cache is None:
             ignore_for_cache = []
+        else:
+            # duplicate so that it can be modified safely later
+            ignore_for_cache = list(ignore_for_cache)
 
         if self.cleanup_called:
             raise RuntimeError("Cannot submit to a DFK that has been cleaned up")


Test patch:
diff --git a/parsl/tests/test_bash_apps/test_memoize_ignore_args.py b/parsl/tests/test_bash_apps/test_memoize_ignore_args.py
--- a/parsl/tests/test_bash_apps/test_memoize_ignore_args.py
+++ b/parsl/tests/test_bash_apps/test_memoize_ignore_args.py
@@ -18,7 +18,7 @@ def test_memo_same_at_definition():
 
 
 @bash_app(cache=True, ignore_for_cache=['stdout'])
-def no_checkpoint_stdout_app(stdout=None):
+def no_checkpoint_stdout_app_ignore_args(stdout=None):
     return "echo X"
 
 
@@ -30,7 +30,7 @@ def test_memo_stdout():
     if os.path.exists(path_x):
         os.remove(path_x)
 
-    no_checkpoint_stdout_app(stdout=path_x).result()
+    no_checkpoint_stdout_app_ignore_args(stdout=path_x).result()
     assert os.path.exists(path_x)
 
     # this should be memoized, so not create benc.test.y
@@ -39,10 +39,10 @@ def test_memo_stdout():
     if os.path.exists(path_y):
         os.remove(path_y)
 
-    no_checkpoint_stdout_app(stdout=path_y).result()
+    no_checkpoint_stdout_app_ignore_args(stdout=path_y).result()
     assert not os.path.exists(path_y)
 
     # this should also be memoized, so not create an arbitrary name
-    z_fut = no_checkpoint_stdout_app(stdout=parsl.AUTO_LOGNAME)
+    z_fut = no_checkpoint_stdout_app_ignore_args(stdout=parsl.AUTO_LOGNAME)
     z_fut.result()
     assert not os.path.exists(z_fut.stdout)
diff --git a/parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py b/parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py
new file mode 100644
--- /dev/null
+++ b/parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py
@@ -0,0 +1,53 @@
+import copy
+import os
+import pytest
+
+from typing import List
+
+import parsl
+from parsl.app.app import bash_app
+
+const_list_x: List[str] = []
+const_list_x_arg: List[str] = copy.deepcopy(const_list_x)
+
+assert const_list_x == const_list_x_arg
+
+
+@bash_app(cache=True, ignore_for_cache=['x'])
+def oneshot_app(x):
+    if x != 0:
+        raise RuntimeError("App was executed when it should not have been")
+    return "true"
+
+
+def test_memo_same_at_definition():
+    oneshot_app(x=0).result()  # this should be executed
+    oneshot_app(x=1).result()  # this should be memoized, and will raise a RuntimeError if actually executed
+
+
+@bash_app(cache=True, ignore_for_cache=const_list_x_arg)
+def no_checkpoint_stdout_app(stdout=None):
+    return "echo X"
+
+
+@pytest.mark.issue363
+def test_memo_stdout():
+
+    assert const_list_x == const_list_x_arg
+
+    path_x = "test.memo.stdout.x"
+    if os.path.exists(path_x):
+        os.remove(path_x)
+
+    # this should run and create a file named after path_x
+    no_checkpoint_stdout_app(stdout=path_x).result()
+    assert os.path.exists(path_x)
+
+    os.remove(path_x)
+    no_checkpoint_stdout_app(stdout=path_x).result()
+    assert not os.path.exists(path_x)
+
+    # this should also be memoized, so not create an arbitrary name
+    z_fut = no_checkpoint_stdout_app(stdout=parsl.AUTO_LOGNAME)
+    z_fut.result()
+    assert const_list_x == const_list_x_arg