Repository: ipython/ipython

Issue:
resubmitted tasks doesn't have same session name
If I understand correctly, a task gets a new msg_id when you call client.resubmit(msg_id). I noticed that the 'cloned' task seems to have a random session name. I would suggest to take over all cutomizable attributes: session, username, retries, after, ... in order to let the resubmitted task behave like the original one.

One side effect for me would be to delete the original task after the resubmitted one took it's place.



Discussion:
The new session is not random, it's that of the Hub, the submitter of the new task.

But I think you are right, it should start with the same header, and replace only the msg_id and timestamps.

Yes, that would be cool.


Resolving patch:
diff --git a/IPython/parallel/controller/dictdb.py b/IPython/parallel/controller/dictdb.py
--- a/IPython/parallel/controller/dictdb.py
+++ b/IPython/parallel/controller/dictdb.py
@@ -46,7 +46,7 @@
 #  the file COPYING, distributed as part of this software.
 #-----------------------------------------------------------------------------
 
-from copy import copy
+from copy import deepcopy as copy
 from datetime import datetime
 
 from IPython.config.configurable import LoggingConfigurable
@@ -129,7 +129,7 @@ def _extract_subdict(self, rec, keys):
         d['msg_id'] = rec['msg_id']
         for key in keys:
             d[key] = rec[key]
-        return d
+        return copy(d)
 
     def add_record(self, msg_id, rec):
         """Add a new Task Record, by msg_id."""
diff --git a/IPython/parallel/controller/hub.py b/IPython/parallel/controller/hub.py
--- a/IPython/parallel/controller/hub.py
+++ b/IPython/parallel/controller/hub.py
@@ -1166,7 +1166,11 @@ def finish(reply):
             msg = self.session.msg(header['msg_type'])
             msg_id = msg['msg_id']
             msg['content'] = rec['content']
-            header.update(msg['header'])
+            
+            # use the old header, but update msg_id and timestamp
+            fresh = msg['header']
+            header['msg_id'] = fresh['msg_id']
+            header['date'] = fresh['date']
             msg['header'] = header
 
             self.session.send(self.resubmit, msg, buffers=rec['buffers'])


Test patch:
diff --git a/IPython/parallel/tests/test_client.py b/IPython/parallel/tests/test_client.py
--- a/IPython/parallel/tests/test_client.py
+++ b/IPython/parallel/tests/test_client.py
@@ -312,6 +312,28 @@ def f():
         r2 = ahr.get(1)
         self.assertFalse(r1 == r2)
 
+    def test_resubmit_header(self):
+        """resubmit shouldn't clobber the whole header"""
+        def f():
+            import random
+            return random.random()
+        v = self.client.load_balanced_view()
+        v.retries = 1
+        ar = v.apply_async(f)
+        r1 = ar.get(1)
+        # give the Hub a chance to notice:
+        self._wait_for_idle()
+        ahr = self.client.resubmit(ar.msg_ids)
+        ahr.get(1)
+        time.sleep(0.5)
+        records = self.client.db_query({'msg_id': {'$in': ar.msg_ids + ahr.msg_ids}}, keys='header')
+        h1,h2 = [ r['header'] for r in records ]
+        for key in set(h1.keys()).union(set(h2.keys())):
+            if key in ('msg_id', 'date'):
+                self.assertNotEquals(h1[key], h2[key])
+            else:
+                self.assertEquals(h1[key], h2[key])
+
     def test_resubmit_aborted(self):
         def f():
             import random
@@ -384,4 +406,3 @@ def test_stop_spin_thread(self):
             "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time
         )
     
-
diff --git a/IPython/parallel/tests/test_db.py b/IPython/parallel/tests/test_db.py
--- a/IPython/parallel/tests/test_db.py
+++ b/IPython/parallel/tests/test_db.py
@@ -197,9 +197,11 @@ def test_pop_safe_get(self):
         rec = self.db.get_record(msg_id)
         rec.pop('buffers')
         rec['garbage'] = 'hello'
+        rec['header']['msg_id'] = 'fubar'
         rec2 = self.db.get_record(msg_id)
         self.assertTrue('buffers' in rec2)
         self.assertFalse('garbage' in rec2)
+        self.assertEquals(rec2['header']['msg_id'], msg_id)
     
     def test_pop_safe_find(self):
         """editing query results shouldn't affect record [find]"""
@@ -207,19 +209,23 @@ def test_pop_safe_find(self):
         rec = self.db.find_records({'msg_id' : msg_id})[0]
         rec.pop('buffers')
         rec['garbage'] = 'hello'
+        rec['header']['msg_id'] = 'fubar'
         rec2 = self.db.find_records({'msg_id' : msg_id})[0]
         self.assertTrue('buffers' in rec2)
         self.assertFalse('garbage' in rec2)
+        self.assertEquals(rec2['header']['msg_id'], msg_id)
 
     def test_pop_safe_find_keys(self):
         """editing query results shouldn't affect record [find+keys]"""
         msg_id = self.db.get_history()[-1]
-        rec = self.db.find_records({'msg_id' : msg_id}, keys=['buffers'])[0]
+        rec = self.db.find_records({'msg_id' : msg_id}, keys=['buffers', 'header'])[0]
         rec.pop('buffers')
         rec['garbage'] = 'hello'
+        rec['header']['msg_id'] = 'fubar'
         rec2 = self.db.find_records({'msg_id' : msg_id})[0]
         self.assertTrue('buffers' in rec2)
         self.assertFalse('garbage' in rec2)
+        self.assertEquals(rec2['header']['msg_id'], msg_id)
 
 
 class TestSQLiteBackend(TestDictBackend):