Repository: mars-project/mars

Issue:
[BUG] Redundant tasks are submitted when eager mode is on
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**
In eager execution, some empty graphs were generated and submitted to cluster which will lead to inefficiency.

**To Reproduce**

``` Python
In [5]: cluster = new_cluster(scheduler_n_process=2, worker_n_process=2, shared_memory='20M', web=True)

In [6]: import mars.tensor as mt                                                                                              

In [7]: from mars.config import options                                                                                       

In [8]: options.eager_mode = True                                                                                             

In [9]: a = mt.random.random((10,), chunk_size=3)                                                                             

In [10]: b = mt.random.rand(10, chunk_size=3)                                                                                 

In [11]: c = a + b
```
In theory, the code above should generate three graphs in this session, there are five however, the extra two graphs are empty.

![image](https://user-images.githubusercontent.com/14146443/60794183-ca724500-a19b-11e9-8edf-2887c1b13123.png)



Discussion:

Resolving patch:
diff --git a/mars/core.py b/mars/core.py
--- a/mars/core.py
+++ b/mars/core.py
@@ -23,7 +23,7 @@
 
 from .compat import six, izip, builtins, reduce
 from .utils import tokenize, AttributeDict, on_serialize_shape, \
-    on_deserialize_shape, on_serialize_nsplits, is_eager_mode
+    on_deserialize_shape, on_serialize_nsplits, is_eager_mode, kernel_mode
 from .serialize import HasKey, ValueType, ProviderType, Serializable, AttributeAsDict, \
     TupleField, ListField, DictField, KeyField, BoolField, StringField, OneOfField
 from .tiles import Tileable, handler
@@ -476,6 +476,7 @@ def tiles(self):
     def single_tiles(self):
         return handler.single_tiles(self)
 
+    @kernel_mode
     def build_graph(self, graph=None, cls=DAG, tiled=False, compose=True, executed_keys=None):
         from .utils import build_fetch
 
diff --git a/mars/web/session.py b/mars/web/session.py
--- a/mars/web/session.py
+++ b/mars/web/session.py
@@ -264,6 +264,10 @@ def count_workers(self):
         resp = self._req_session.get(self._endpoint + '/api/worker', timeout=1)
         return json.loads(resp.text)
 
+    def get_task_count(self):
+        resp = self._req_session.get(self._endpoint + '/api/session/{0}/graph'.format(self._session_id))
+        return len(json.loads(resp.text))
+
     def __enter__(self):
         return self
 


Test patch:
diff --git a/mars/deploy/local/tests/test_cluster.py b/mars/deploy/local/tests/test_cluster.py
--- a/mars/deploy/local/tests/test_cluster.py
+++ b/mars/deploy/local/tests/test_cluster.py
@@ -478,6 +478,9 @@ def testEagerMode(self, *_):
                     expected2 = expected1.dot(expected1)
                     np.testing.assert_array_almost_equal(r2, expected2)
 
+                    web_session = Session.default_or_local()._sess
+                    self.assertEqual(web_session.get_task_count(), 3)
+
                 a = mt.ones((10, 10), chunk_size=3)
                 with self.assertRaises(ValueError):
                     a.fetch()
@@ -505,6 +508,9 @@ def testEagerMode(self, *_):
                     df3 = add(df1, df2)
                     pd.testing.assert_frame_equal(df3.fetch(), data1 + data2)
 
+                web_session = Session.default_or_local()._sess
+                self.assertEqual(web_session.get_task_count(), 3)
+
     def testSparse(self, *_):
         import scipy.sparse as sps
 
diff --git a/mars/web/tests/test_api.py b/mars/web/tests/test_api.py
--- a/mars/web/tests/test_api.py
+++ b/mars/web/tests/test_api.py
@@ -13,7 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import time
 import requests
 import json
 import unittest