Repository: mars-project/mars

Issue:
[BUG] __pre_destroy__ not called when running oscar actors
`__pre_destroy__` is not invoked when actors are destroyed.

```python
import mars.oscar as mo
        
class TestActor(mo.Actor):
    async def __pre_destroy__(self):
        print('__pre_destroy__')
        
pool = await mo.create_actor_pool('127.0.0.1', n_process=0)
await pool.start()
ref = await mo.create_actor(TestActor, address=pool.external_address)
await ref.destroy()
# nothing printed
```


Discussion:

Resolving patch:
diff --git a/mars/oscar/api.py b/mars/oscar/api.py
--- a/mars/oscar/api.py
+++ b/mars/oscar/api.py
@@ -77,6 +77,10 @@ def setup_cluster(address_to_resources: Dict[str, Dict[str, Number]]):
 
 
 class Actor(_Actor):
+    @classmethod
+    def default_uid(cls):
+        return cls.__name__
+
     def __new__(cls, *args, **kwargs):
         try:
             return _actor_implementation[cls](*args, **kwargs)
diff --git a/mars/oscar/backends/mars/pool.py b/mars/oscar/backends/mars/pool.py
--- a/mars/oscar/backends/mars/pool.py
+++ b/mars/oscar/backends/mars/pool.py
@@ -740,7 +740,7 @@ async def destroy_actor(self,
             return result
         real_actor_ref = result.result
         if real_actor_ref.address == self.external_address:
-            del self._actors[real_actor_ref.uid]
+            await super().destroy_actor(message)
             del self._allocated_actors[self.external_address][real_actor_ref]
             return ResultMessage(message.message_id, real_actor_ref.uid,
                                  protocol=message.protocol)


Test patch:
diff --git a/mars/oscar/backends/mars/tests/test_mars_actor_context.py b/mars/oscar/backends/mars/tests/test_mars_actor_context.py
--- a/mars/oscar/backends/mars/tests/test_mars_actor_context.py
+++ b/mars/oscar/backends/mars/tests/test_mars_actor_context.py
@@ -20,7 +20,7 @@
 import pytest
 
 import mars.oscar as mo
-from mars.utils import get_next_port, extensible
+from mars.utils import extensible
 
 
 class DummyActor(mo.Actor):
@@ -103,11 +103,29 @@ def get_ref(self):
         return self.ref()
 
 
-class EventActor(mo.Actor):
+class RecordActor(mo.Actor):
+    def __init__(self):
+        self._records = []
+
+    def add_record(self, rec):
+        self._records.append(rec)
+
+    def get_records(self):
+        return self._records
+
+
+class CreateDestroyActor(mo.Actor):
+    def __init__(self):
+        self._record_ref = None
+
     async def __post_create__(self):
+        self._record_ref = await mo.actor_ref(RecordActor.default_uid(),
+                                              address=self.address)
+        await self._record_ref.add_record(f'create {self.uid}')
         assert 'sth' == await self.ref().echo('sth')
 
-    async def pre_destroy(self):
+    async def __pre_destroy__(self):
+        await self._record_ref.add_record(f'destroy {self.uid}')
         assert 'sth2' == await self.ref().echo('sth2')
 
     def echo(self, message):
@@ -198,8 +216,7 @@ def get_call_log(self):
 
 @pytest.fixture
 async def actor_pool_context():
-    pool = await mo.create_actor_pool(
-        f'127.0.0.1:{get_next_port()}', n_process=2)
+    pool = await mo.create_actor_pool('127.0.0.1', n_process=2)
     await pool.start()
     yield pool
     await pool.stop()
@@ -226,9 +243,16 @@ async def test_simple_local_actor_pool(actor_pool_context):
 @pytest.mark.asyncio
 async def test_mars_post_create_pre_destroy(actor_pool_context):
     pool = actor_pool_context
-    actor_ref = await mo.create_actor(EventActor, address=pool.external_address)
+    rec_ref = await mo.create_actor(RecordActor, uid=RecordActor.default_uid(),
+                                    address=pool.external_address)
+    actor_ref = await mo.create_actor(CreateDestroyActor, address=pool.external_address)
     await actor_ref.destroy()
 
+    records = await rec_ref.get_records()
+    assert len(records) == 2
+    assert records[0].startswith('create')
+    assert records[1].startswith('destroy')
+
 
 @pytest.mark.asyncio
 async def test_mars_create_actor(actor_pool_context):
@@ -292,6 +316,8 @@ async def test_mars_tell(actor_pool_context):
     assert await ref2.get_value() == 5
     await asyncio.sleep(0.45)
     assert await ref2.get_value() == 5
+    await asyncio.sleep(0.1)
+    assert await ref2.get_value() == 9
 
     # error needed when illegal uids are passed
     with pytest.raises(ValueError):