Repository: ray-project/ray

Issue:
Seeding numpy interferes with random ID generation.
```python
import numpy as np
import ray

ray.init(num_workers=0)

@ray.actor
class Foo(object):
  def __init__(self):
    pass

np.random.seed(0)
a1 = Foo()
np.random.seed(0)
a2 = Foo()
print(a1)
print(a2)
```

This currently causes the local scheduler to die with the following check.

```
[FATAL] (/Users/rkn/Workspace/ray/src/local_scheduler/local_scheduler.cc:1051: errno: Operation now in progress) Check failure: state->actor_mapping.count(actor_id) == 0 

0   local_scheduler                     0x0000000102c25d07 _Z30handle_actor_creation_callback8UniqueIDS_S_Pv + 839
1   local_scheduler                     0x0000000102c37dbc _Z49redis_actor_notification_table_subscribe_callbackP17redisAsyncContextPvS1_ + 428
2   local_scheduler                     0x0000000102c4d19d redisProcessCallbacks + 861
3   local_scheduler                     0x0000000102c3af39 aeProcessEvents + 649
4   local_scheduler                     0x0000000102c3b25b aeMain + 43
5   local_scheduler                     0x0000000102c25f30 _Z12start_serverPKcS0_S0_iS0_S0_S0_bPKdS0_i + 448
6   local_scheduler                     0x0000000102c262fc main + 924
7   libdyld.dylib                       0x00007fffd54c2255 start + 1
```

And the actor ID's that are generated are the same.


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
@@ -5,7 +5,6 @@
 import hashlib
 import inspect
 import json
-import numpy as np
 import random
 import redis
 import traceback
@@ -14,7 +13,7 @@
 import ray.pickling as pickling
 import ray.signature as signature
 import ray.worker
-from ray.utils import binary_to_hex, hex_to_binary
+from ray.utils import random_string, binary_to_hex, hex_to_binary
 
 # This is a variable used by each actor to indicate the IDs of the GPUs that
 # the worker is currently allowed to use.
@@ -30,10 +29,6 @@ def get_gpu_ids():
   return gpu_ids
 
 
-def random_string():
-  return np.random.bytes(20)
-
-
 def random_actor_id():
   return ray.local_scheduler.ObjectID(random_string())
 
diff --git a/python/ray/utils.py b/python/ray/utils.py
--- a/python/ray/utils.py
+++ b/python/ray/utils.py
@@ -3,11 +3,37 @@
 from __future__ import print_function
 
 import binascii
+import numpy as np
 import sys
 
 import ray.local_scheduler
 
 
+def random_string():
+  """Generate a random string to use as an ID.
+
+  Note that users may seed numpy, which could cause this function to generate
+  duplicate IDs. Therefore, we need to seed numpy ourselves, but we can't
+  interfere with the state of the user's random number generator, so we extract
+  the state of the random number generator and reset it after we are done.
+
+  TODO(rkn): If we want to later guarantee that these are generated in a
+  deterministic manner, then we will need to make some changes here.
+
+  Returns:
+    A random byte string of length 20.
+  """
+  # Get the state of the numpy random number generator.
+  numpy_state = np.random.get_state()
+  # Try to use true randomness.
+  np.random.seed(None)
+  # Generate the random ID.
+  random_id = np.random.bytes(20)
+  # Reset the state of the numpy random number generator.
+  np.random.set_state(numpy_state)
+  return random_id
+
+
 def decode(byte_str):
   """Make this unicode in Python 3, otherwise leave it as bytes."""
   if sys.version_info >= (3, 0):
diff --git a/python/ray/worker.py b/python/ray/worker.py
--- a/python/ray/worker.py
+++ b/python/ray/worker.py
@@ -27,6 +27,7 @@
 import ray.numbuf
 import ray.local_scheduler
 import ray.plasma
+from ray.utils import random_string
 
 SCRIPT_MODE = 0
 WORKER_MODE = 1
@@ -60,14 +61,6 @@
 TASK_STATUS_RUNNING = 8
 
 
-def random_string():
-  return np.random.bytes(20)
-
-
-def random_object_id():
-  return ray.local_scheduler.ObjectID(random_string())
-
-
 class FunctionID(object):
   def __init__(self, function_id):
     self.function_id = function_id


Test patch:
diff --git a/test/actor_test.py b/test/actor_test.py
--- a/test/actor_test.py
+++ b/test/actor_test.py
@@ -3,6 +3,7 @@
 from __future__ import print_function
 
 import numpy as np
+import random
 import unittest
 
 import ray
@@ -209,6 +210,27 @@ def __init__(self):
 
     ray.worker.cleanup()
 
+  def testRandomIDGeneration(self):
+    ray.init(num_workers=0)
+
+    @ray.actor
+    class Foo(object):
+      def __init__(self):
+        pass
+
+    # Make sure that seeding numpy does not interfere with the generation of
+    # actor IDs.
+    np.random.seed(1234)
+    random.seed(1234)
+    f1 = Foo()
+    np.random.seed(1234)
+    random.seed(1234)
+    f2 = Foo()
+
+    self.assertNotEqual(f1._ray_actor_id.id(), f2._ray_actor_id.id())
+
+    ray.worker.cleanup()
+
 
 class ActorMethods(unittest.TestCase):