diff --git a/python/ray/actor.py b/python/ray/actor.py index deae7ba4b..648ffd380 100644 --- a/python/ray/actor.py +++ b/python/ray/actor.py @@ -5,7 +5,6 @@ from __future__ import print_function import hashlib import inspect import json -import numpy as np import random import redis import traceback @@ -14,7 +13,7 @@ import ray.local_scheduler 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 index ea86cf814..e112b86e5 100644 --- a/python/ray/utils.py +++ b/python/ray/utils.py @@ -3,11 +3,37 @@ from __future__ import division 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 index 60593a290..e9f02b383 100644 --- a/python/ray/worker.py +++ b/python/ray/worker.py @@ -27,6 +27,7 @@ import ray.signature as signature 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 @@ PUT_RECONSTRUCTION_ERROR_TYPE = b"put_reconstruction" 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 diff --git a/test/actor_test.py b/test/actor_test.py index 901ff496a..6ddf37b78 100644 --- a/test/actor_test.py +++ b/test/actor_test.py @@ -3,6 +3,7 @@ from __future__ import division from __future__ import print_function import numpy as np +import random import unittest import ray @@ -209,6 +210,27 @@ class ActorAPI(unittest.TestCase): 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):