Refine multi-threading support (#3672)

* [Python] refine multi-threading support

fix

* [java] refine multithreading code

fix java

* format
This commit is contained in:
Hao Chen
2019-01-11 05:58:11 +08:00
committed by Stephanie Wang
parent 71243203a4
commit 597abb24ea
9 changed files with 394 additions and 313 deletions
@@ -71,14 +71,14 @@ public abstract class AbstractRayRuntime implements RayRuntime {
@Override
public <T> RayObject<T> put(T obj) {
UniqueId objectId = UniqueIdUtil.computePutId(
workerContext.getCurrentTask().taskId, workerContext.nextPutIndex());
workerContext.getCurrentTaskId(), workerContext.nextPutIndex());
put(objectId, obj);
return new RayObjectImpl<>(objectId);
}
public <T> void put(UniqueId objectId, T obj) {
UniqueId taskId = workerContext.getCurrentTask().taskId;
UniqueId taskId = workerContext.getCurrentTaskId();
LOGGER.debug("Putting object {}, for task {} ", objectId, taskId);
objectStoreProxy.put(objectId, obj, null);
}
@@ -92,8 +92,8 @@ public abstract class AbstractRayRuntime implements RayRuntime {
*/
public RayObject<Object> putSerialized(byte[] obj) {
UniqueId objectId = UniqueIdUtil.computePutId(
workerContext.getCurrentTask().taskId, workerContext.nextPutIndex());
UniqueId taskId = workerContext.getCurrentTask().taskId;
workerContext.getCurrentTaskId(), workerContext.nextPutIndex());
UniqueId taskId = workerContext.getCurrentTaskId();
LOGGER.debug("Putting serialized object {}, for task {} ", objectId, taskId);
objectStoreProxy.putSerialized(objectId, obj, null);
return new RayObjectImpl<>(objectId);
@@ -108,7 +108,6 @@ public abstract class AbstractRayRuntime implements RayRuntime {
@Override
public <T> List<T> get(List<UniqueId> objectIds) {
boolean wasBlocked = false;
UniqueId taskId = workerContext.getCurrentThreadTaskId();
try {
int numObjectIds = objectIds.size();
@@ -117,7 +116,7 @@ public abstract class AbstractRayRuntime implements RayRuntime {
List<List<UniqueId>> fetchBatches =
splitIntoBatches(objectIds, FETCH_BATCH_SIZE);
for (List<UniqueId> batch : fetchBatches) {
rayletClient.fetchOrReconstruct(batch, true, taskId);
rayletClient.fetchOrReconstruct(batch, true, workerContext.getCurrentTaskId());
}
// Get the objects. We initially try to get the objects immediately.
@@ -144,7 +143,7 @@ public abstract class AbstractRayRuntime implements RayRuntime {
splitIntoBatches(unreadyList, FETCH_BATCH_SIZE);
for (List<UniqueId> batch : reconstructBatches) {
rayletClient.fetchOrReconstruct(batch, false, taskId);
rayletClient.fetchOrReconstruct(batch, false, workerContext.getCurrentTaskId());
}
List<Pair<T, GetStatus>> results = objectStoreProxy
@@ -171,7 +170,8 @@ public abstract class AbstractRayRuntime implements RayRuntime {
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Got objects {} for task {}.", Arrays.toString(objectIds.toArray()), taskId);
LOGGER.debug("Got objects {} for task {}.", Arrays.toString(objectIds.toArray()),
workerContext.getCurrentTaskId());
}
List<T> finalRet = new ArrayList<>();
@@ -182,13 +182,13 @@ public abstract class AbstractRayRuntime implements RayRuntime {
return finalRet;
} catch (RayException e) {
LOGGER.error("Failed to get objects for task {}.", taskId, e);
LOGGER.error("Failed to get objects for task {}.", workerContext.getCurrentTaskId(), e);
throw e;
} finally {
// If there were objects that we weren't able to get locally, let the local
// scheduler know that we're now unblocked.
if (wasBlocked) {
rayletClient.notifyUnblocked(taskId);
rayletClient.notifyUnblocked(workerContext.getCurrentTaskId());
}
}
}
@@ -217,7 +217,7 @@ public abstract class AbstractRayRuntime implements RayRuntime {
@Override
public <T> WaitResult<T> wait(List<RayObject<T>> waitList, int numReturns, int timeoutMs) {
return rayletClient.wait(waitList, numReturns,
timeoutMs, workerContext.getCurrentThreadTaskId());
timeoutMs, workerContext.getCurrentTaskId());
}
@Override
@@ -277,9 +277,8 @@ public abstract class AbstractRayRuntime implements RayRuntime {
*/
private TaskSpec createTaskSpec(RayFunc func, RayActorImpl actor, Object[] args,
boolean isActorCreationTask, BaseTaskOptions taskOptions) {
final TaskSpec current = workerContext.getCurrentTask();
UniqueId taskId = rayletClient.generateTaskId(current.driverId,
current.taskId, workerContext.nextCallIndex());
UniqueId taskId = rayletClient.generateTaskId(workerContext.getCurrentDriverId(),
workerContext.getCurrentTaskId(), workerContext.nextTaskIndex());
int numReturns = actor.getId().isNil() ? 1 : 2;
UniqueId[] returnIds = genReturnIds(taskId, numReturns);
@@ -304,11 +303,11 @@ public abstract class AbstractRayRuntime implements RayRuntime {
if (taskOptions instanceof ActorCreationOptions) {
maxActorReconstruction = ((ActorCreationOptions) taskOptions).maxReconstructions;
}
RayFunction rayFunction = functionManager.getFunction(current.driverId, func);
RayFunction rayFunction = functionManager.getFunction(workerContext.getCurrentDriverId(), func);
return new TaskSpec(
current.driverId,
workerContext.getCurrentDriverId(),
taskId,
current.taskId,
workerContext.getCurrentTaskId(),
-1,
actorCreationId,
maxActorReconstruction,
@@ -39,7 +39,7 @@ public final class RayNativeRuntime extends AbstractRayRuntime {
path += ":";
}
path += rayConfig.libraryPath.stream().collect(Collectors.joining(":"));
path += String.join(":", rayConfig.libraryPath);
// This is a hack to reset library path at runtime,
// see https://stackoverflow.com/questions/15409223/.
@@ -80,7 +80,7 @@ public final class RayNativeRuntime extends AbstractRayRuntime {
rayConfig.rayletSocketName,
workerContext.getCurrentWorkerId(),
rayConfig.workerMode == WorkerMode.WORKER,
workerContext.getCurrentTask().taskId
workerContext.getCurrentDriverId()
);
// register
@@ -43,8 +43,7 @@ public class Worker {
RayFunction rayFunction = runtime.getFunctionManager()
.getFunction(spec.driverId, spec.functionDescriptor);
// Set context
runtime.getWorkerContext().setCurrentTask(spec);
runtime.getWorkerContext().setCurrentClassLoader(rayFunction.classLoader);
runtime.getWorkerContext().setCurrentTask(spec, rayFunction.classLoader);
Thread.currentThread().setContextClassLoader(rayFunction.classLoader);
// Get local actor object and arguments.
Object actor = spec.isActorTask() ? runtime.localActors.get(spec.actorId) : null;
@@ -67,6 +66,7 @@ public class Worker {
LOGGER.error("Error executing task " + spec, e);
runtime.put(returnId, new RayException("Error executing task " + spec, e));
} finally {
runtime.getWorkerContext().setCurrentTask(null, null);
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
@@ -1,9 +1,6 @@
package org.ray.runtime;
import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.ray.api.id.UniqueId;
import org.ray.runtime.config.WorkerMode;
import org.ray.runtime.task.TaskSpec;
@@ -11,123 +8,114 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WorkerContext {
private static final Logger LOGGER = LoggerFactory.getLogger(WorkerContext.class);
/**
* Worker id.
*/
private UniqueId workerId;
/**
* Current task.
*/
private TaskSpec currentTask;
private ThreadLocal<UniqueId> currentTaskId;
/**
* Current class loader.
* Number of objects that have been put from current task.
*/
private ThreadLocal<Integer> putIndex;
/**
* Number of tasks that have been submitted from current task.
*/
private ThreadLocal<Integer> taskIndex;
private UniqueId currentDriverId;
private ClassLoader currentClassLoader;
/**
* How many puts have been done by current task.
*/
private AtomicInteger currentTaskPutCount;
/**
* How many calls have been done by current task.
*/
private AtomicInteger currentTaskCallCount;
/**
* The ID of main thread which created the worker context.
*/
private long mainThreadId;
/**
* If the multi-threading warning message has been logged.
*/
private AtomicBoolean multiThreadingWarned;
public WorkerContext(WorkerMode workerMode, UniqueId driverId) {
workerId = workerMode == WorkerMode.DRIVER ? driverId : UniqueId.randomId();
currentTaskPutCount = new AtomicInteger(0);
currentTaskCallCount = new AtomicInteger(0);
currentClassLoader = null;
currentTask = createDummyTask(workerMode, driverId);
mainThreadId = Thread.currentThread().getId();
multiThreadingWarned = new AtomicBoolean(false);
taskIndex = ThreadLocal.withInitial(() -> 0);
putIndex = ThreadLocal.withInitial(() -> 0);
currentTaskId = ThreadLocal.withInitial(UniqueId::randomId);
if (workerMode == WorkerMode.DRIVER) {
workerId = driverId;
currentTaskId.set(UniqueId.randomId());
currentDriverId = driverId;
currentClassLoader = null;
} else {
workerId = UniqueId.randomId();
setCurrentTask(null, null);
}
}
/**
* Get the current thread's task ID.
* This returns the assigned task ID if called on the main thread, else a
* random task ID.
* @return For the main thread, this method returns the ID of this worker's current running task;
* for other threads, this method returns a random ID.
*/
public UniqueId getCurrentThreadTaskId() {
UniqueId taskId;
if (Thread.currentThread().getId() == mainThreadId) {
taskId = currentTask.taskId;
public UniqueId getCurrentTaskId() {
return currentTaskId.get();
}
/**
* Set the current task which is being executed by the current worker. Note, this method can only
* be called from the main thread.
*/
public void setCurrentTask(TaskSpec task, ClassLoader classLoader) {
Preconditions.checkState(
Thread.currentThread().getId() == mainThreadId,
"This method should only be called from the main thread."
);
if (task != null) {
currentTaskId.set(task.taskId);
currentDriverId = task.driverId;
} else {
taskId = UniqueId.randomId();
if (multiThreadingWarned.compareAndSet(false, true)) {
LOGGER.warn("Calling Ray.get or Ray.wait in a separate thread " +
"may lead to deadlock if the main thread blocks on this " +
"thread and there are not enough resources to execute " +
"more tasks");
}
currentTaskId.set(UniqueId.NIL);
currentDriverId = UniqueId.NIL;
}
Preconditions.checkState(!taskId.isNil());
return taskId;
}
public void setWorkerId(UniqueId workerId) {
this.workerId = workerId;
}
public TaskSpec getCurrentTask() {
return currentTask;
taskIndex.set(0);
putIndex.set(0);
currentClassLoader = classLoader;
}
/**
* Increment the put index and return the new value.
*/
public int nextPutIndex() {
return currentTaskPutCount.incrementAndGet();
putIndex.set(putIndex.get() + 1);
return putIndex.get();
}
public int nextCallIndex() {
return currentTaskCallCount.incrementAndGet();
/**
* Increment the task index and return the new value.
*/
public int nextTaskIndex() {
taskIndex.set(taskIndex.get() + 1);
return taskIndex.get();
}
/**
* @return The ID of the current worker.
*/
public UniqueId getCurrentWorkerId() {
return workerId;
}
/**
* @return If this worker is a driver, this method returns the driver ID; Otherwise, it returns
* the driver ID of the current running task.
*/
public UniqueId getCurrentDriverId() {
return currentDriverId;
}
/**
* @return The class loader which is associated with the current driver.
*/
public ClassLoader getCurrentClassLoader() {
return currentClassLoader;
}
public void setCurrentTask(TaskSpec currentTask) {
this.currentTask = currentTask;
currentTaskCallCount.set(0);
currentTaskPutCount.set(0);
}
public void setCurrentClassLoader(ClassLoader currentClassLoader) {
this.currentClassLoader = currentClassLoader;
}
private TaskSpec createDummyTask(WorkerMode workerMode, UniqueId driverId) {
return new TaskSpec(
driverId,
workerMode == WorkerMode.DRIVER ? UniqueId.randomId() : UniqueId.NIL,
UniqueId.NIL,
0,
UniqueId.NIL,
0,
UniqueId.NIL,
UniqueId.NIL,
0,
null,
null,
new HashMap<>(),
null);
}
}
@@ -95,7 +95,7 @@ public class MockObjectStore implements ObjectStoreLink {
}
private String logPrefix() {
return runtime.getWorkerContext().getCurrentTask().taskId + "-" + getUserTrace() + " -> ";
return runtime.getWorkerContext().getCurrentTaskId() + "-" + getUserTrace() + " -> ";
}
private String getUserTrace() {
@@ -79,6 +79,9 @@ public class RayletClientImpl implements RayletClient {
@Override
public void submitTask(TaskSpec spec) {
LOGGER.debug("Submitting task: {}", spec);
Preconditions.checkState(!spec.parentTaskId.isNil());
Preconditions.checkState(!spec.driverId.isNil());
ByteBuffer info = convertTaskSpecToFlatbuffer(spec);
byte[] cursorId = null;
if (!spec.getExecutionDependencies().isEmpty()) {
+28 -35
View File
@@ -7,6 +7,7 @@ import hashlib
import inspect
import logging
import sys
import threading
import traceback
import ray.cloudpickle as pickle
@@ -225,8 +226,7 @@ class ActorMethod(object):
self._method_name,
args=args,
kwargs=kwargs,
num_return_vals=num_return_vals,
dependency=self._actor._ray_actor_cursor)
num_return_vals=num_return_vals)
class ActorClass(object):
@@ -525,13 +525,13 @@ class ActorHandle(object):
self._ray_actor_method_cpus = actor_method_cpus
self._ray_actor_driver_id = actor_driver_id
self._ray_new_actor_handles = []
self._ray_actor_lock = threading.Lock()
def _actor_method_call(self,
method_name,
args=None,
kwargs=None,
num_return_vals=None,
dependency=None):
num_return_vals=None):
"""Method execution stub for an actor handle.
This is the function that executes when
@@ -570,41 +570,34 @@ class ActorHandle(object):
return getattr(worker.actors[self._ray_actor_id],
method_name)(*copy.deepcopy(args))
# Add the execution dependency.
if dependency is None:
execution_dependencies = []
else:
execution_dependencies = [dependency]
is_actor_checkpoint_method = (method_name == "__ray_checkpoint__")
function_descriptor = FunctionDescriptor(
self._ray_module_name, method_name, self._ray_class_name)
object_ids = worker.submit_task(
function_descriptor,
args,
actor_id=self._ray_actor_id,
actor_handle_id=self._ray_actor_handle_id,
actor_counter=self._ray_actor_counter,
is_actor_checkpoint_method=is_actor_checkpoint_method,
actor_creation_dummy_object_id=(
self._ray_actor_creation_dummy_object_id),
execution_dependencies=execution_dependencies,
new_actor_handles=self._ray_new_actor_handles,
# We add one for the dummy return ID.
num_return_vals=num_return_vals + 1,
resources={"CPU": self._ray_actor_method_cpus},
placement_resources={},
driver_id=self._ray_actor_driver_id)
# Update the actor counter and cursor to reflect the most recent
# invocation.
self._ray_actor_counter += 1
# The last object returned is the dummy object that should be
# passed in to the next actor method. Do not return it to the user.
self._ray_actor_cursor = object_ids.pop()
# We have notified the backend of the new actor handles to expect since
# the last task was submitted, so clear the list.
self._ray_new_actor_handles = []
with self._ray_actor_lock:
object_ids = worker.submit_task(
function_descriptor,
args,
actor_id=self._ray_actor_id,
actor_handle_id=self._ray_actor_handle_id,
actor_counter=self._ray_actor_counter,
is_actor_checkpoint_method=is_actor_checkpoint_method,
actor_creation_dummy_object_id=(
self._ray_actor_creation_dummy_object_id),
execution_dependencies=[self._ray_actor_cursor],
new_actor_handles=self._ray_new_actor_handles,
# We add one for the dummy return ID.
num_return_vals=num_return_vals + 1,
resources={"CPU": self._ray_actor_method_cpus},
placement_resources={},
driver_id=self._ray_actor_driver_id,
)
# Update the actor counter and cursor to reflect the most recent
# invocation.
self._ray_actor_counter += 1
# The last object returned is the dummy object that should be
# passed in to the next actor method. Do not return it to the user.
self._ray_actor_cursor = object_ids.pop()
if len(object_ids) == 1:
object_ids = object_ids[0]
+157 -139
View File
@@ -143,13 +143,6 @@ class Worker(object):
cached_functions_to_run (List): A list of functions to run on all of
the workers that should be exported as soon as connect is called.
profiler: the profiler used to aggregate profiling information.
state_lock (Lock):
Used to lock worker's non-thread-safe internal states:
1) task_index increment: make sure we generate unique task ids;
2) Object reconstruction: because the node manager will
recycle/return the worker's resources before/after reconstruction,
it's unsafe for multiple threads to call object
reconstruction simultaneously.
"""
def __init__(self):
@@ -169,42 +162,56 @@ class Worker(object):
self.original_gpu_ids = ray.utils.get_cuda_visible_devices()
self.profiler = None
self.memory_monitor = memory_monitor.MemoryMonitor()
self.state_lock = threading.Lock()
# A dictionary that maps from driver id to SerializationContext
# TODO: clean up the SerializationContext once the job finished.
self.serialization_context_map = {}
self.function_actor_manager = FunctionActorManager(self)
# Reads/writes to the following fields must be protected by
# self.state_lock.
# Identity of the driver that this worker is processing.
self.task_driver_id = ray.ObjectID(NIL_ID)
self.current_task_id = ray.ObjectID(NIL_ID)
self.task_index = 0
self.put_index = 1
self._task_context = threading.local()
def get_current_thread_task_id(self):
"""Get the current thread's task ID.
@property
def task_context(self):
"""A thread-local that contains the following attributes.
This returns the assigned task ID if called on the main thread, else a
random task ID. This method is not thread-safe and must be called with
self.state_lock acquired.
current_task_id: For the main thread, this field is the ID of this
worker's current running task; for other threads, this field is a
fake random ID.
task_index: The number of tasks that have been submitted from the
current task.
put_index: The number of objects that have been put from the current
task.
"""
current_task_id = self.current_task_id
if not ray.utils.is_main_thread():
# If this is running on a separate thread, then the mapping
# to the current task ID may not be correct. Generate a
# random task ID so that the backend can differentiate
# between different threads.
current_task_id = ray.ObjectID(random_string())
if not self.multithreading_warned:
logger.warning(
"Calling ray.get or ray.wait in a separate thread "
"may lead to deadlock if the main thread blocks on this "
"thread and there are not enough resources to execute "
"more tasks")
self.multithreading_warned = True
assert not current_task_id.is_nil()
return current_task_id
if not hasattr(self._task_context, 'initialized'):
# Initialize task_context for the current thread.
if ray.utils.is_main_thread():
# If this is running on the main thread, initialize it to
# NIL. The actual value will set when the worker receives
# a task from raylet backend.
self._task_context.current_task_id = ray.ObjectID(NIL_ID)
else:
# If this is running on a separate thread, then the mapping
# to the current task ID may not be correct. Generate a
# random task ID so that the backend can differentiate
# between different threads.
self._task_context.current_task_id = ray.ObjectID(
random_string())
if getattr(self, '_multithreading_warned', False) is not True:
logger.warning(
"Calling ray.get or ray.wait in a separate thread "
"may lead to deadlock if the main thread blocks on "
"this thread and there are not enough resources to "
"execute more tasks")
self._multithreading_warned = True
self._task_context.task_index = 0
self._task_context.put_index = 1
self._task_context.initialized = True
return self._task_context
@property
def current_task_id(self):
return self.task_context.current_task_id
def mark_actor_init_failed(self, error):
"""Called to mark this actor as failed during initialization."""
@@ -467,48 +474,45 @@ class Worker(object):
}
if len(unready_ids) > 0:
with self.state_lock:
# Get the task ID, to notify the backend which task is blocked.
current_task_id = self.get_current_thread_task_id()
# Try reconstructing any objects we haven't gotten yet. Try to
# get them until at least get_timeout_milliseconds
# milliseconds passes, then repeat.
while len(unready_ids) > 0:
object_ids_to_fetch = [
plasma.ObjectID(unready_id)
for unready_id in unready_ids.keys()
]
ray_object_ids_to_fetch = [
ray.ObjectID(unready_id)
for unready_id in unready_ids.keys()
]
fetch_request_size = ray._config.worker_fetch_request_size()
for i in range(0, len(object_ids_to_fetch),
fetch_request_size):
self.raylet_client.fetch_or_reconstruct(
ray_object_ids_to_fetch[i:(i + fetch_request_size)],
False,
self.current_task_id,
)
results = self.retrieve_and_deserialize(
object_ids_to_fetch,
max([
ray._config.get_timeout_milliseconds(),
int(0.01 * len(unready_ids)),
]),
)
# Remove any entries for objects we received during this
# iteration so we don't retrieve the same object twice.
for i, val in enumerate(results):
if val is not plasma.ObjectNotAvailable:
object_id = object_ids_to_fetch[i].binary()
index = unready_ids[object_id]
final_results[index] = val
unready_ids.pop(object_id)
# Try reconstructing any objects we haven't gotten yet. Try to
# get them until at least get_timeout_milliseconds
# milliseconds passes, then repeat.
while len(unready_ids) > 0:
object_ids_to_fetch = [
plasma.ObjectID(unready_id)
for unready_id in unready_ids.keys()
]
ray_object_ids_to_fetch = [
ray.ObjectID(unready_id)
for unready_id in unready_ids.keys()
]
fetch_request_size = (
ray._config.worker_fetch_request_size())
for i in range(0, len(object_ids_to_fetch),
fetch_request_size):
self.raylet_client.fetch_or_reconstruct(
ray_object_ids_to_fetch[i:(
i + fetch_request_size)], False,
current_task_id)
results = self.retrieve_and_deserialize(
object_ids_to_fetch,
max([
ray._config.get_timeout_milliseconds(),
int(0.01 * len(unready_ids))
]))
# Remove any entries for objects we received during this
# iteration so we don't retrieve the same object twice.
for i, val in enumerate(results):
if val is not plasma.ObjectNotAvailable:
object_id = object_ids_to_fetch[i].binary()
index = unready_ids[object_id]
final_results[index] = val
unready_ids.pop(object_id)
# If there were objects that we weren't able to get locally,
# let the local scheduler know that we're now unblocked.
self.raylet_client.notify_unblocked(current_task_id)
# If there were objects that we weren't able to get locally,
# let the local scheduler know that we're now unblocked.
self.raylet_client.notify_unblocked(self.current_task_id)
assert len(final_results) == len(object_ids)
return final_results
@@ -616,24 +620,32 @@ class Worker(object):
if placement_resources is None:
placement_resources = {}
with self.state_lock:
# Increment the worker's task index to track how many tasks
# have been submitted by the current task so far.
task_index = self.task_index
self.task_index += 1
# The parent task must be set for the submitted task.
if self.actor_id == NIL_ACTOR_ID:
assert not self.current_task_id.is_nil()
# Increment the worker's task index to track how many tasks
# have been submitted by the current task so far.
self.task_context.task_index += 1
# The parent task must be set for the submitted task.
assert not self.current_task_id.is_nil()
# Submit the task to local scheduler.
function_descriptor_list = (
function_descriptor.get_function_descriptor_list())
task = ray.raylet.Task(
driver_id, function_descriptor_list, args_for_local_scheduler,
num_return_vals, self.current_task_id, task_index,
actor_creation_id, actor_creation_dummy_object_id,
max_actor_reconstructions, actor_id, actor_handle_id,
actor_counter, new_actor_handles, execution_dependencies,
resources, placement_resources)
driver_id,
function_descriptor_list,
args_for_local_scheduler,
num_return_vals,
self.current_task_id,
self.task_context.task_index,
actor_creation_id,
actor_creation_dummy_object_id,
max_actor_reconstructions,
actor_id,
actor_handle_id,
actor_counter,
new_actor_handles,
execution_dependencies,
resources,
placement_resources,
)
self.raylet_client.submit_task(task)
return task.returns()
@@ -770,24 +782,23 @@ class Worker(object):
(these will be retrieved by calls to get or by subsequent tasks that
use the outputs of this task).
"""
with self.state_lock:
assert self.current_task_id.is_nil()
assert self.task_index == 0
assert self.put_index == 1
if task.actor_id().is_nil():
# If this worker is not an actor, check that `task_driver_id`
# was reset when the worker finished the previous task.
assert self.task_driver_id.is_nil()
# Set the driver ID of the current running task. This is
# needed so that if the task throws an exception, we propagate
# the error message to the correct driver.
self.task_driver_id = task.driver_id()
else:
# If this worker is an actor, task_driver_id wasn't reset.
# Check that current task's driver ID equals the previous one.
assert self.task_driver_id == task.driver_id()
assert self.current_task_id.is_nil()
assert self.task_context.task_index == 0
assert self.task_context.put_index == 1
if task.actor_id().is_nil():
# If this worker is not an actor, check that `task_driver_id`
# was reset when the worker finished the previous task.
assert self.task_driver_id.is_nil()
# Set the driver ID of the current running task. This is
# needed so that if the task throws an exception, we propagate
# the error message to the correct driver.
self.task_driver_id = task.driver_id()
else:
# If this worker is an actor, task_driver_id wasn't reset.
# Check that current task's driver ID equals the previous one.
assert self.task_driver_id == task.driver_id()
self.current_task_id = task.task_id()
self.task_context.current_task_id = task.task_id()
function_descriptor = FunctionDescriptor.from_bytes_list(
task.function_descriptor_list())
@@ -931,13 +942,14 @@ class Worker(object):
with _changeproctitle(title, next_title):
self._process_task(task, execution_info)
# Reset the state fields so the next task can run.
with self.state_lock:
if self.actor_id == NIL_ACTOR_ID:
# We will keep task_driver_id unchanged for actor.
self.task_driver_id = ray.ObjectID(NIL_ID)
self.current_task_id = ray.ObjectID(NIL_ID)
self.task_index = 0
self.put_index = 1
self.task_context.current_task_id = ray.ObjectID(NIL_ID)
self.task_context.task_index = 0
self.task_context.put_index = 1
if self.actor_id == NIL_ACTOR_ID:
# Don't need to reset task_driver_id if the worker is an
# actor. Because the following tasks should all have the
# same driver id.
self.task_driver_id = ray.ObjectID(NIL_ID)
# Increase the task execution counter.
self.function_actor_manager.increase_task_counter(
@@ -1925,13 +1937,8 @@ def connect(ray_params,
else:
# Try to use true randomness.
np.random.seed(None)
worker.current_task_id = ray.ObjectID(
np.random.bytes(ray_constants.ID_SIZE))
# Reset the state of the numpy random number generator.
np.random.set_state(numpy_state)
# Set other fields needed for computing task IDs.
worker.task_index = 0
worker.put_index = 1
# Create an entry for the driver task in the task table. This task is
# added immediately with status RUNNING. This allows us to push errors
@@ -1944,11 +1951,22 @@ def connect(ray_params,
function_descriptor = FunctionDescriptor.for_driver_task()
driver_task = ray.raylet.Task(
worker.task_driver_id,
function_descriptor.get_function_descriptor_list(), [], 0,
worker.current_task_id, worker.task_index,
ray.ObjectID(NIL_ACTOR_ID), ray.ObjectID(NIL_ACTOR_ID), 0,
ray.ObjectID(NIL_ACTOR_ID), ray.ObjectID(NIL_ACTOR_ID),
nil_actor_counter, [], [], {"CPU": 0}, {})
function_descriptor.get_function_descriptor_list(),
[], # arguments.
0, # num_returns.
ray.ObjectID(random_string()), # parent_task_id.
0, # parent_counter.
ray.ObjectID(NIL_ACTOR_ID), # actor_creation_id.
ray.ObjectID(NIL_ACTOR_ID), # actor_creation_dummy_object_id.
0, # max_actor_reconstructions.
ray.ObjectID(NIL_ACTOR_ID), # actor_id.
ray.ObjectID(NIL_ACTOR_ID), # actor_handle_id.
nil_actor_counter, # actor_counter.
[], # new_actor_handles.
[], # execution_dependencies.
{"CPU": 0}, # resource_map.
{}, # placement_resource_map.
)
# Add the driver task to the task table.
global_state._execute_command(driver_task.task_id(), "RAY.TABLE_ADD",
@@ -1959,16 +1977,14 @@ def connect(ray_params,
# Set the driver's current task ID to the task ID assigned to the
# driver task.
worker.current_task_id = driver_task.task_id()
else:
# A non-driver worker begins without an assigned task.
worker.current_task_id = ray.ObjectID(NIL_ID)
# A flag for making sure that we only print one warning message about
# multithreading per worker.
worker.multithreading_warned = False
worker.task_context.current_task_id = driver_task.task_id()
worker.raylet_client = ray.raylet.RayletClient(
raylet_socket, worker.worker_id, is_worker, worker.current_task_id)
raylet_socket,
worker.worker_id,
is_worker,
worker.current_task_id,
)
# Start the import thread
import_thread.ImportThread(worker, mode).start()
@@ -2254,9 +2270,11 @@ def put(value, worker=global_worker):
# In LOCAL_MODE, ray.put is the identity operation.
return value
object_id = worker.raylet_client.compute_put_id(
worker.current_task_id, worker.put_index)
worker.current_task_id,
worker.task_context.put_index,
)
worker.put_object(object_id, value)
worker.put_index += 1
worker.task_context.put_index += 1
return object_id
@@ -2342,15 +2360,15 @@ def wait(object_ids, num_returns=1, timeout=None, worker=global_worker):
raise Exception("num_returns cannot be greater than the number "
"of objects provided to ray.wait.")
# Get the task ID, to notify the backend which task is blocked.
with worker.state_lock:
current_task_id = worker.get_current_thread_task_id()
timeout = timeout if timeout is not None else 10**6
timeout_milliseconds = int(timeout * 1000)
ready_ids, remaining_ids = worker.raylet_client.wait(
object_ids, num_returns, timeout_milliseconds, False,
current_task_id)
object_ids,
num_returns,
timeout_milliseconds,
False,
worker.current_task_id,
)
return ready_ids, remaining_ids
+112 -32
View File
@@ -5,6 +5,7 @@ from __future__ import print_function
import json
import logging
import os
import random
import re
import setproctitle
import string
@@ -13,6 +14,7 @@ import sys
import threading
import time
from collections import defaultdict, namedtuple, OrderedDict
from concurrent.futures import ThreadPoolExecutor
import numpy as np
import pytest
@@ -1176,59 +1178,137 @@ def test_multithreading(shutdown_only):
# relase resources when joining the threads.
ray.init(num_cpus=2)
def run_test_in_multi_threads(test_case, num_threads=20, num_repeats=50):
"""A helper function that runs test cases in multiple threads."""
def wrapper():
for _ in range(num_repeats):
test_case()
time.sleep(random.randint(0, 10) / 1000.0)
return "ok"
executor = ThreadPoolExecutor(max_workers=num_threads)
futures = [executor.submit(wrapper) for _ in range(num_threads)]
for future in futures:
assert future.result() == "ok"
@ray.remote
def f():
pass
def echo(value, delay_ms=0):
if delay_ms > 0:
time.sleep(delay_ms / 1000.0)
return value
def g(n):
for _ in range(1000 // n):
ray.get([f.remote() for _ in range(n)])
res = [ray.put(i) for i in range(1000 // n)]
ray.wait(res, len(res))
@ray.remote
class Echo(object):
def echo(self, value):
return value
def test_multi_threading():
threads = [
threading.Thread(target=g, args=(n, ))
for n in [1, 5, 10, 100, 1000]
def test_api_in_multi_threads():
"""Test using Ray api in multiple threads."""
# Test calling remote functions in multiple threads.
def test_remote_call():
value = random.randint(0, 1000000)
result = ray.get(echo.remote(value))
assert value == result
run_test_in_multi_threads(test_remote_call)
# Test multiple threads calling one actor.
actor = Echo.remote()
def test_call_actor():
value = random.randint(0, 1000000)
result = ray.get(actor.echo.remote(value))
assert value == result
run_test_in_multi_threads(test_call_actor)
# Test put and get.
def test_put_and_get():
value = random.randint(0, 1000000)
result = ray.get(ray.put(value))
assert value == result
run_test_in_multi_threads(test_put_and_get)
# Test multiple threads waiting for objects.
num_wait_objects = 10
objects = [
echo.remote(i, delay_ms=10) for i in range(num_wait_objects)
]
[thread.start() for thread in threads]
[thread.join() for thread in threads]
def test_wait():
ready, _ = ray.wait(
objects,
num_returns=len(objects),
timeout=1000,
)
assert len(ready) == num_wait_objects
assert ray.get(ready) == list(range(num_wait_objects))
run_test_in_multi_threads(test_wait, num_repeats=1)
# Run tests in a driver.
test_api_in_multi_threads()
# Run tests in a worker.
@ray.remote
def test_multi_threading_in_worker():
test_multi_threading()
def run_tests_in_worker():
test_api_in_multi_threads()
return "ok"
def block(args, n):
ray.wait(args, num_returns=n)
ray.get(args[:n])
assert ray.get(run_tests_in_worker.remote()) == "ok"
# Test actor that runs background threads.
@ray.remote
class MultithreadedActor(object):
def __init__(self):
pass
self.lock = threading.Lock()
self.thread_results = []
def background_thread(self, wait_objects):
try:
# Test wait
ready, _ = ray.wait(
wait_objects,
num_returns=len(wait_objects),
timeout=1000,
)
assert len(ready) == len(wait_objects)
for _ in range(50):
num = 20
# Test remote call
results = [echo.remote(i) for i in range(num)]
assert ray.get(results) == list(range(num))
# Test put and get
objects = [ray.put(i) for i in range(num)]
assert ray.get(objects) == list(range(num))
time.sleep(random.randint(0, 10) / 1000.0)
except Exception as e:
with self.lock:
self.thread_results.append(e)
else:
with self.lock:
self.thread_results.append("ok")
def spawn(self):
objects = [f.remote() for _ in range(1000)]
wait_objects = [echo.remote(i, delay_ms=10) for i in range(20)]
self.threads = [
threading.Thread(target=block, args=(objects, n))
for n in [1, 5, 10, 100, 1000]
threading.Thread(
target=self.background_thread, args=(wait_objects, ))
for _ in range(20)
]
[thread.start() for thread in self.threads]
def join(self):
[thread.join() for thread in self.threads]
assert self.thread_results == ["ok"] * len(self.threads)
return "ok"
# test multi-threading in the driver
test_multi_threading()
# test multi-threading in the worker
ray.get(test_multi_threading_in_worker.remote())
# test multi-threading in the actor
a = MultithreadedActor.remote()
ray.get(a.spawn.remote())
ray.get(a.join.remote())
actor = MultithreadedActor.remote()
actor.spawn.remote()
ray.get(actor.join.remote()) == "ok"
def test_free_objects_multi_node(ray_start_cluster):