Repository: ray-project/ray

Issue:
Actor handles don't work when running in local mode.
```python
import ray

ray.init(driver_mode=ray.PYTHON_MODE)

@ray.remote
class Actor1:
    def method(self):
        pass

@ray.remote
class Actor2:
    def __init__(self, a):
        self.a = a
    def method(self):
        ray.get(self.a.method.remote())

a1 = Actor1.remote()
a2 = Actor2.remote(a1)
ray.get(a2.method.remote())
```

Fails with an error like the following (if you're not using the master branch you'll probably see an error like "Exception: Actor objects cannot be pickled").

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-1-8b7c665c4619> in <module>()
     16 
     17 a1 = Actor1.remote()
---> 18 a2 = Actor2.remote(a1)
     19 ray.get(a2.method.remote())

~/Workspace/ray/python/ray/actor.py in remote(self, *args, **kwargs)
    556             A handle to the newly created actor.
    557         """
--> 558         return self._submit(args=args, kwargs=kwargs)
    559 
    560     def _submit(self,

~/Workspace/ray/python/ray/actor.py in _submit(self, args, kwargs, num_cpus, num_gpus, resources)
    632         # Call __init__ as a remote function.
    633         if "__init__" in actor_handle._ray_actor_method_names:
--> 634             actor_handle.__init__.remote(*args, **kwargs)
    635         else:
    636             if len(args) != 0 or len(kwargs) != 0:

~/Workspace/ray/python/ray/actor.py in remote(self, *args, **kwargs)
    454 
    455     def remote(self, *args, **kwargs):
--> 456         return self._submit(args, kwargs)
    457 
    458     def _submit(self, args, kwargs, num_return_vals=None):

~/Workspace/ray/python/ray/actor.py in _submit(self, args, kwargs, num_return_vals)
    465             kwargs=kwargs,
    466             num_return_vals=num_return_vals,
--> 467             dependency=self._actor._ray_actor_cursor)
    468 
    469 

~/Workspace/ray/python/ray/actor.py in _actor_method_call(self, method_name, args, kwargs, num_return_vals, dependency)
    774         if worker.mode == ray.PYTHON_MODE:
    775             return getattr(worker.actors[self._ray_actor_id],
--> 776                            method_name)(*copy.deepcopy(args))
    777 
    778         # Add the execution dependency.

/anaconda3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    148     copier = _deepcopy_dispatch.get(cls)
    149     if copier:
--> 150         y = copier(x, memo)
    151     else:
    152         try:

/anaconda3/lib/python3.6/copy.py in _deepcopy_list(x, memo, deepcopy)
    213     append = y.append
    214     for a in x:
--> 215         append(deepcopy(a, memo))
    216     return y
    217 d[list] = _deepcopy_list

/anaconda3/lib/python3.6/copy.py in deepcopy(x, memo, _nil)
    167                     reductor = getattr(x, "__reduce_ex__", None)
    168                     if reductor:
--> 169                         rv = reductor(4)
    170                     else:
    171                         reductor = getattr(x, "__reduce__", None)

~/Workspace/ray/python/ray/actor.py in __getstate__(self)
    944     def __getstate__(self):
    945         """This code path is used by pickling but not by Ray forking."""
--> 946         return self._serialization_helper(False)
    947 
    948     def __setstate__(self, state):

~/Workspace/ray/python/ray/actor.py in _serialization_helper(self, ray_forking)
    885             "class_name": self._ray_class_name,
    886             "actor_forks": self._ray_actor_forks,
--> 887             "actor_cursor": self._ray_actor_cursor.id(),
    888             "actor_counter": 0,  # Reset the actor counter.
    889             "actor_method_names": self._ray_actor_method_names,

AttributeError: 'NoneType' object has no attribute 'id'
```

cc @jsuarez5341 


Discussion:

Resolving patch:
diff --git a/python/ray/actor.py b/python/ray/actor.py
--- a/python/ray/actor.py
+++ b/python/ray/actor.py
@@ -884,13 +884,15 @@ def _serialization_helper(self, ray_forking):
             "actor_id": self._ray_actor_id.id(),
             "class_name": self._ray_class_name,
             "actor_forks": self._ray_actor_forks,
-            "actor_cursor": self._ray_actor_cursor.id(),
+            "actor_cursor": self._ray_actor_cursor.id()
+            if self._ray_actor_cursor is not None else None,
             "actor_counter": 0,  # Reset the actor counter.
             "actor_method_names": self._ray_actor_method_names,
             "method_signatures": self._ray_method_signatures,
             "method_num_return_vals": self._ray_method_num_return_vals,
             "actor_creation_dummy_object_id": self.
-            _ray_actor_creation_dummy_object_id.id(),
+            _ray_actor_creation_dummy_object_id.id()
+            if self._ray_actor_creation_dummy_object_id is not None else None,
             "actor_method_cpus": self._ray_actor_method_cpus,
             "actor_driver_id": self._ray_actor_driver_id.id(),
             "previous_actor_handle_id": self._ray_actor_handle_id.id()
@@ -929,12 +931,14 @@ def _deserialization_helper(self, state, ray_forking):
         self.__init__(
             ray.ObjectID(state["actor_id"]),
             state["class_name"],
-            ray.ObjectID(state["actor_cursor"]),
+            ray.ObjectID(state["actor_cursor"])
+            if state["actor_cursor"] is not None else None,
             state["actor_counter"],
             state["actor_method_names"],
             state["method_signatures"],
             state["method_num_return_vals"],
-            ray.ObjectID(state["actor_creation_dummy_object_id"]),
+            ray.ObjectID(state["actor_creation_dummy_object_id"])
+            if state["actor_creation_dummy_object_id"] is not None else None,
             state["actor_method_cpus"],
             actor_driver_id,
             actor_handle_id=actor_handle_id,


Test patch:
diff --git a/test/runtest.py b/test/runtest.py
--- a/test/runtest.py
+++ b/test/runtest.py
@@ -1189,6 +1189,16 @@ def modify_and_set_array(self, array):
         test_array[0] = -1
         assert_equal(test_array, test_actor.get_array.remote())
 
+        # Check that actor handles work in Python mode.
+
+        @ray.remote
+        def use_actor_handle(handle):
+            array = np.ones(10)
+            handle.set_array.remote(array)
+            assert np.alltrue(array == ray.get(handle.get_array.remote()))
+
+        ray.get(use_actor_handle.remote(test_actor))
+
 
 class ResourcesTest(unittest.TestCase):
     def tearDown(self):