Repository: dask/distributed

Issue:
Undeserializable exception in Variable future
This code below throws Exception with message Undeserializable exception. But it should throw NameError instead.
Without sleep it works as expected.

distributed version: 1.20.0

```python
# Undeserializable exception

from time import sleep
from distributed import Client, Variable
from dask import delayed

client = Client()


@delayed
def f():
    fsdaf
    return 5

future = client.compute(f())
future_var = Variable()
future_var.set(future)
sleep(0.1)
future_var.get().result()
```


Discussion:

Resolving patch:
diff --git a/distributed/queues.py b/distributed/queues.py
--- a/distributed/queues.py
+++ b/distributed/queues.py
@@ -7,11 +7,6 @@
 from tornado import gen
 import tornado.queues
 
-try:
-    from cytoolz import assoc
-except ImportError:
-    from toolz import assoc
-
 from .client import Future, _get_global_client, Client
 from .utils import tokey, sync
 from .worker import get_client
@@ -86,13 +81,19 @@ def get(self, stream=None, name=None, client=None, timeout=None,
         def process(record):
             """ Add task status if known """
             if record['type'] == 'Future':
+                record = record.copy()
+                key = record['value']
                 try:
-                    state = self.scheduler.task_state[record['value']]
+                    state = self.scheduler.task_state[key]
                 except KeyError:
                     state = 'lost'
-                return assoc(record, 'state', state)
-            else:
-                return record
+
+                record['state'] = state
+                if state == 'erred':
+                    record['exception'] = self.scheduler.exceptions[self.scheduler.exceptions_blame[key]]
+                    record['traceback'] = self.scheduler.tracebacks[self.scheduler.exceptions_blame[key]]
+
+            return record
 
         if batch:
             q = self.queues[name]
@@ -211,6 +212,8 @@ def process(d):
             if d['type'] == 'Future':
                 value = Future(d['value'], self.client, inform=True,
                                state=d['state'])
+                if d['state'] == 'erred':
+                    value._state.set_error(d['exception'], d['traceback'])
                 self.client._send_to_scheduler({'op': 'queue-future-release',
                                                 'name': self.name,
                                                 'key': d['value']})
diff --git a/distributed/variable.py b/distributed/variable.py
--- a/distributed/variable.py
+++ b/distributed/variable.py
@@ -95,7 +95,11 @@ def get(self, stream=None, name=None, client=None, timeout=None):
                 state = self.scheduler.task_state[key]
             except KeyError:
                 state = 'lost'
-            record = merge(record, {'token': token, 'state': state})
+            msg = {'token': token, 'state': state}
+            if state == 'erred':
+                msg['exception'] = self.scheduler.exceptions[self.scheduler.exceptions_blame[key]]
+                msg['traceback'] = self.scheduler.tracebacks[self.scheduler.exceptions_blame[key]]
+            record = merge(record, msg)
             self.waiting[key, name].add(token)
         raise gen.Return(record)
 
@@ -175,6 +179,8 @@ def _get(self, timeout=None):
                                                      client=self.client.id)
         if d['type'] == 'Future':
             value = Future(d['value'], self.client, inform=True, state=d['state'])
+            if d['state'] == 'erred':
+                value._state.set_error(d['exception'], d['traceback'])
             self.client._send_to_scheduler({'op': 'variable-future-release',
                                             'name': self.name,
                                             'key': d['value'],


Test patch:
diff --git a/distributed/tests/test_queues.py b/distributed/tests/test_queues.py
--- a/distributed/tests/test_queues.py
+++ b/distributed/tests/test_queues.py
@@ -225,3 +225,14 @@ def test_Future_knows_status_immediately(c, s, a, b):
             yield gen.sleep(0.05)
 
     yield c2.close()
+
+
+@gen_cluster(client=True)
+def test_erred_future(c, s, a, b):
+    future = c.submit(div, 1, 0)
+    q = Queue()
+    yield q.put(future)
+    yield gen.sleep(0.1)
+    future2 = yield q.get()
+    with pytest.raises(ZeroDivisionError):
+        yield future2.result()
diff --git a/distributed/tests/test_variable.py b/distributed/tests/test_variable.py
--- a/distributed/tests/test_variable.py
+++ b/distributed/tests/test_variable.py
@@ -205,3 +205,14 @@ def test_Future_knows_status_immediately(c, s, a, b):
             yield gen.sleep(0.05)
 
     yield c2.close()
+
+
+@gen_cluster(client=True)
+def test_erred_future(c, s, a, b):
+    future = c.submit(div, 1, 0)
+    var = Variable()
+    yield var.set(future)
+    yield gen.sleep(0.1)
+    future2 = yield var.get()
+    with pytest.raises(ZeroDivisionError):
+        yield future2.result()