[Object spilling] Queue failed object creation requests until objects have been spilled (#11796)

* Queue creation requests

* Cleanup disconnected clients

* Remove unused

* todo

* FIFO order for create requests, remove warmup for IO workers

* test and lint

* disable test

* lint

* Skip on windows
This commit is contained in:
Stephanie Wang
2020-11-06 18:22:19 -05:00
committed by GitHub
parent 900a48c19c
commit 61e41257e7
15 changed files with 143 additions and 54 deletions
+5
View File
@@ -271,6 +271,8 @@ def test_spill_remote_object(ray_start_cluster_head):
ray.get(depends.remote(ref))
@pytest.mark.skipif(
platform.system() == "Windows", reason="Failing on Windows.")
def test_spill_objects_automatically(object_spilling_config, shutdown_only):
# Limit our object store to 75 MiB of memory.
ray.init(
@@ -306,6 +308,9 @@ def test_spill_objects_automatically(object_spilling_config, shutdown_only):
@pytest.mark.skipif(
platform.system() == "Windows", reason="Failing on Windows.")
@pytest.mark.skip(
"Temporarily disabled until OutOfMemory retries can be moved "
"into the plasma store")
def test_spill_during_get(object_spilling_config, shutdown_only):
ray.init(
num_cpus=4,
+1
View File
@@ -388,6 +388,7 @@ CoreWorker::CoreWorker(const CoreWorkerOptions &options, const WorkerID &worker_
options_.store_socket, local_raylet_client_, reference_counter_,
options_.check_signals,
/*evict_if_full=*/RayConfig::instance().object_pinning_enabled(),
/*warmup=*/options_.worker_type != ray::WorkerType::IO_WORKER,
/*on_store_full=*/boost::bind(&CoreWorker::TriggerGlobalGC, this),
/*get_current_call_site=*/boost::bind(&CoreWorker::CurrentCallSite, this)));
memory_store_.reset(new CoreWorkerMemoryStore(
@@ -25,7 +25,7 @@ CoreWorkerPlasmaStoreProvider::CoreWorkerPlasmaStoreProvider(
const std::string &store_socket,
const std::shared_ptr<raylet::RayletClient> raylet_client,
const std::shared_ptr<ReferenceCounter> reference_counter,
std::function<Status()> check_signals, bool evict_if_full,
std::function<Status()> check_signals, bool evict_if_full, bool warmup,
std::function<void()> on_store_full,
std::function<std::string()> get_current_call_site)
: raylet_client_(raylet_client),
@@ -40,7 +40,9 @@ CoreWorkerPlasmaStoreProvider::CoreWorkerPlasmaStoreProvider(
}
buffer_tracker_ = std::make_shared<BufferTracker>();
RAY_CHECK_OK(store_client_.Connect(store_socket));
RAY_CHECK_OK(WarmupStore());
if (warmup) {
RAY_CHECK_OK(WarmupStore());
}
}
CoreWorkerPlasmaStoreProvider::~CoreWorkerPlasmaStoreProvider() {
@@ -127,23 +129,6 @@ Status CoreWorkerPlasmaStoreProvider::Create(const std::shared_ptr<Buffer> &meta
"in the cluster."
<< "\n---\n";
}
} else if (plasma_status.IsTransientObjectStoreFull()) {
std::ostringstream message;
message << "Failed to put object " << object_id << " in object store because it "
<< "is currently full, but space is being made through object spilling. "
"Object size is "
<< data_size << " bytes.";
// The object store is full, but space is being made. Try again soon.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
delay = RayConfig::instance().object_store_full_initial_delay_ms();
// Don't count these retries towards the total count. Also, reset the
// retry count since there may be space soon so we are not out of memory
// yet.
// NOTE(swang): We do this because the plasma store cannot guarantee that
// there will be enough space for the object on a future retry when there
// are concurrent clients trying to create objects.
retries = 0;
should_retry = true;
} else if (plasma_status.IsObjectExists()) {
RAY_LOG(WARNING) << "Trying to put an object that already existed in plasma: "
<< object_id << ".";
@@ -37,7 +37,7 @@ class CoreWorkerPlasmaStoreProvider {
const std::string &store_socket,
const std::shared_ptr<raylet::RayletClient> raylet_client,
const std::shared_ptr<ReferenceCounter> reference_counter,
std::function<Status()> check_signals, bool evict_if_full,
std::function<Status()> check_signals, bool evict_if_full, bool warmup,
std::function<void()> on_store_full = nullptr,
std::function<std::string()> get_current_call_site = nullptr);
+3
View File
@@ -10,4 +10,7 @@ namespace ray {
/// complete.
using SpillObjectsCallback = std::function<int64_t(int64_t num_bytes_required)>;
/// A callback to call when space has been released.
using SpaceReleasedCallback = std::function<void()>;
} // namespace ray
@@ -128,8 +128,6 @@ Status PlasmaErrorStatus(fb::PlasmaError plasma_error) {
return Status::ObjectNotFound("object does not exist in the plasma store");
case fb::PlasmaError::OutOfMemory:
return Status::ObjectStoreFull("object does not fit in the plasma store");
case fb::PlasmaError::TransientOutOfMemory:
return Status::TransientObjectStoreFull("object does not currently fit in the plasma store, try again soon");
default:
RAY_LOG(FATAL) << "unknown plasma error code " << static_cast<int>(plasma_error);
}
+66 -25
View File
@@ -253,6 +253,45 @@ Status PlasmaStore::FreeCudaMemory(int device_num, int64_t size, uint8_t* pointe
}
#endif
Status PlasmaStore::HandleCreateObjectRequest(const std::shared_ptr<Client> &client, const std::vector<uint8_t> &message) {
uint8_t* input = (uint8_t*)message.data();
size_t input_size = message.size();
ObjectID object_id;
PlasmaObject object = {};
NodeID owner_raylet_id;
std::string owner_ip_address;
int owner_port;
WorkerID owner_worker_id;
bool evict_if_full;
int64_t data_size;
int64_t metadata_size;
int device_num;
RAY_RETURN_NOT_OK(ReadCreateRequest(
input, input_size, &object_id, &owner_raylet_id, &owner_ip_address, &owner_port,
&owner_worker_id, &evict_if_full, &data_size, &metadata_size, &device_num));
PlasmaError error_code = CreateObject(object_id, owner_raylet_id, owner_ip_address,
owner_port, owner_worker_id, evict_if_full,
data_size, metadata_size, device_num, client,
&object);
Status status;
if (error_code == PlasmaError::TransientOutOfMemory) {
RAY_LOG(DEBUG) << "Create object " << object_id << " failed, waiting for object spill";
status = Status::TransientObjectStoreFull("Object store full, queueing creation request");
} else {
int64_t mmap_size = 0;
if (error_code == PlasmaError::OK && device_num == 0) {
mmap_size = GetMmapSize(object.store_fd);
}
RAY_RETURN_NOT_OK(SendCreateReply(client, object_id, &object, error_code, mmap_size));
if (error_code == PlasmaError::OK && device_num == 0) {
RAY_RETURN_NOT_OK(client->SendFd(object.store_fd));
}
}
return status;
}
// Create a new object buffer in the hash table.
PlasmaError PlasmaStore::CreateObject(const ObjectID& object_id,
const NodeID& owner_raylet_id,
@@ -262,7 +301,7 @@ PlasmaError PlasmaStore::CreateObject(const ObjectID& object_id,
int64_t metadata_size, int device_num,
const std::shared_ptr<Client> &client,
PlasmaObject* result) {
RAY_LOG(DEBUG) << "creating object " << object_id.Hex();
RAY_LOG(DEBUG) << "creating object " << object_id.Hex() << " size " << data_size;
auto entry = GetObjectTableEntry(&store_info_, object_id);
if (entry != nullptr) {
@@ -819,6 +858,14 @@ void PlasmaStore::DisconnectClient(const std::shared_ptr<Client> &client) {
// Remove notification for this client from global map.
notification_clients_.erase(client);
}
for (auto it = create_request_queue_.begin(); it != create_request_queue_.end(); ) {
if (it->first == client) {
it = create_request_queue_.erase(it);
} else {
it++;
}
}
}
/// Send notifications about sealed objects to the subscribers. This is called
@@ -912,34 +959,12 @@ Status PlasmaStore::ProcessMessage(const std::shared_ptr<Client> &client,
uint8_t* input = (uint8_t*)message.data();
size_t input_size = message.size();
ObjectID object_id;
PlasmaObject object = {};
// Process the different types of requests.
switch (type) {
case fb::MessageType::PlasmaCreateRequest: {
NodeID owner_raylet_id;
std::string owner_ip_address;
int owner_port;
WorkerID owner_worker_id;
bool evict_if_full;
int64_t data_size;
int64_t metadata_size;
int device_num;
RAY_RETURN_NOT_OK(ReadCreateRequest(
input, input_size, &object_id, &owner_raylet_id, &owner_ip_address, &owner_port,
&owner_worker_id, &evict_if_full, &data_size, &metadata_size, &device_num));
PlasmaError error_code = CreateObject(object_id, owner_raylet_id, owner_ip_address,
owner_port, owner_worker_id, evict_if_full,
data_size, metadata_size, device_num, client,
&object);
int64_t mmap_size = 0;
if (error_code == PlasmaError::OK && device_num == 0) {
mmap_size = GetMmapSize(object.store_fd);
}
RAY_RETURN_NOT_OK(SendCreateReply(client, object_id, &object, error_code, mmap_size));
if (error_code == PlasmaError::OK && device_num == 0) {
RAY_RETURN_NOT_OK(client->SendFd(object.store_fd));
}
create_request_queue_.push_back({client, message});
ProcessCreateRequests();
} break;
case fb::MessageType::PlasmaAbortRequest: {
RAY_RETURN_NOT_OK(ReadAbortRequest(input, input_size, &object_id));
@@ -1033,4 +1058,20 @@ void PlasmaStore::DoAccept() {
boost::asio::placeholders::error));
}
void PlasmaStore::ProcessCreateRequests() {
for (auto request_it = create_request_queue_.begin();
request_it != create_request_queue_.end(); ) {
auto status = HandleCreateObjectRequest(request_it->first, request_it->second);
if (status.IsTransientObjectStoreFull()) {
// The object store is still full.
// NOTE(swang): There could be other requests behind this one that are
// actually serviceable. This may be inefficient, but eventually this
// request will get served and unblock the following requests, once
// enough objects have been spilled.
break;
}
request_it = create_request_queue_.erase(request_it);
}
}
} // namespace plasma
+24
View File
@@ -199,7 +199,18 @@ class PlasmaStore {
}
}
/// Process queued requests to create an object.
///
/// The queue is processed FIFO.
///
/// \param num_bytes_space A lower bound on the number of bytes of space that
/// have been made newly available, since the last time this method was
/// called.
void ProcessCreateRequests();
private:
Status HandleCreateObjectRequest(const std::shared_ptr<Client> &client, const std::vector<uint8_t> &message);
void PushNotification(ObjectInfoT* object_notification);
void PushNotifications(const std::vector<ObjectInfoT>& object_notifications);
@@ -273,6 +284,19 @@ class PlasmaStore {
/// complete.
ray::SpillObjectsCallback spill_objects_callback_;
/// Queue of object creation requests to respond to. Requests will be placed
/// on this queue if the object store does not have enough room at the time
/// that the client made the creation request, but space may be made through
/// object spilling. Once the raylet notifies us that objects have been
/// spilled, we will attempt to process these requests again and respond to
/// the client if successful or out of memory. If more objects must be
/// spilled, the request will be replaced at the head of the queue.
/// TODO(swang): We should also queue objects here even if there is no room
/// in the object store. Then, the client does not need to poll on an
/// OutOfMemory error and we can just respond to them once there is enough
/// space made, or after a timeout.
std::list<std::pair<const std::shared_ptr<Client>,
const std::vector<uint8_t>>> create_request_queue_;
};
} // namespace plasma
@@ -22,6 +22,14 @@ class PlasmaStoreRunner {
store_->SetNotificationListener(notification_listener);
}
ray::SpaceReleasedCallback OnSpaceReleased() {
return [this]() {
main_service_.post([this]() {
store_->ProcessCreateRequests();
});
};
}
private:
void Shutdown();
absl::Mutex store_runner_mutex_;
+6 -1
View File
@@ -194,6 +194,7 @@ void LocalObjectManager::SpillObjectsInternal(
<< status.ToString();
if (callback) {
callback(status);
on_objects_spilled_();
}
} else {
AddSpilledUrls(objects_to_spill, r, callback);
@@ -206,6 +207,7 @@ void LocalObjectManager::AddSpilledUrls(
const std::vector<ObjectID> &object_ids, const rpc::SpillObjectsReply &worker_reply,
std::function<void(const ray::Status &)> callback) {
auto num_remaining = std::make_shared<size_t>(object_ids.size());
auto num_bytes_spilled = std::make_shared<size_t>(0);
for (size_t i = 0; i < object_ids.size(); ++i) {
const ObjectID &object_id = object_ids[i];
const std::string &object_url = worker_reply.spilled_objects_url(i);
@@ -214,18 +216,21 @@ void LocalObjectManager::AddSpilledUrls(
// releasing the object to make sure that the spilled object can
// be retrieved by other raylets.
RAY_CHECK_OK(object_info_accessor_.AsyncAddSpilledUrl(
object_id, object_url, [this, object_id, callback, num_remaining](Status status) {
object_id, object_url,
[this, object_id, callback, num_remaining, num_bytes_spilled](Status status) {
RAY_CHECK_OK(status);
absl::MutexLock lock(&mutex_);
// Unpin the object.
auto it = objects_pending_spill_.find(object_id);
RAY_CHECK(it != objects_pending_spill_.end());
num_bytes_pending_spill_ -= it->second->GetSize();
*num_bytes_spilled += it->second->GetSize();
objects_pending_spill_.erase(it);
(*num_remaining)--;
if (*num_remaining == 0 && callback) {
callback(status);
on_objects_spilled_();
}
}));
}
+8 -1
View File
@@ -21,6 +21,7 @@
#include "ray/common/id.h"
#include "ray/common/ray_object.h"
#include "ray/gcs/accessor.h"
#include "ray/object_manager/common.h"
#include "ray/raylet/worker_pool.h"
#include "ray/rpc/worker/core_worker_client_pool.h"
@@ -36,13 +37,15 @@ class LocalObjectManager {
IOWorkerPoolInterface &io_worker_pool,
gcs::ObjectInfoAccessor &object_info_accessor,
rpc::CoreWorkerClientPool &owner_client_pool,
std::function<void(const std::vector<ObjectID> &)> on_objects_freed)
std::function<void(const std::vector<ObjectID> &)> on_objects_freed,
SpaceReleasedCallback on_objects_spilled)
: free_objects_period_ms_(free_objects_period_ms),
free_objects_batch_size_(free_objects_batch_size),
io_worker_pool_(io_worker_pool),
object_info_accessor_(object_info_accessor),
owner_client_pool_(owner_client_pool),
on_objects_freed_(on_objects_freed),
on_objects_spilled_(on_objects_spilled),
last_free_objects_at_ms_(current_time_ms()) {}
/// Pin objects.
@@ -138,6 +141,10 @@ class LocalObjectManager {
absl::flat_hash_map<ObjectID, std::unique_ptr<RayObject>> objects_pending_spill_
GUARDED_BY(mutex_);
/// Callback to call whenever objects have been spilled or failed to be
/// spilled.
SpaceReleasedCallback on_objects_spilled_;
/// The time that we last sent a FreeObjects request to other nodes for
/// objects that have gone out of scope in the application.
uint64_t last_free_objects_at_ms_ = 0;
+4 -2
View File
@@ -132,7 +132,8 @@ std::string WorkerOwnerString(std::shared_ptr<WorkerInterface> &worker) {
NodeManager::NodeManager(boost::asio::io_service &io_service, const NodeID &self_node_id,
const NodeManagerConfig &config, ObjectManager &object_manager,
std::shared_ptr<gcs::GcsClient> gcs_client,
std::shared_ptr<ObjectDirectoryInterface> object_directory)
std::shared_ptr<ObjectDirectoryInterface> object_directory,
SpaceReleasedCallback on_objects_spilled)
: self_node_id_(self_node_id),
io_service_(io_service),
object_manager_(object_manager),
@@ -179,7 +180,8 @@ NodeManager::NodeManager(boost::asio::io_service &io_service, const NodeID &self
[this](const std::vector<ObjectID> &object_ids) {
object_manager_.FreeObjects(object_ids,
/*local_only=*/false);
}),
},
on_objects_spilled),
new_scheduler_enabled_(RayConfig::instance().new_scheduler_enabled()),
report_worker_backlog_(RayConfig::instance().report_worker_backlog()) {
RAY_LOG(INFO) << "Initializing NodeManager with ID " << self_node_id_;
+2 -1
View File
@@ -132,7 +132,8 @@ class NodeManager : public rpc::NodeManagerServiceHandler {
NodeManager(boost::asio::io_service &io_service, const NodeID &self_node_id,
const NodeManagerConfig &config, ObjectManager &object_manager,
std::shared_ptr<gcs::GcsClient> gcs_client,
std::shared_ptr<ObjectDirectoryInterface> object_directory_);
std::shared_ptr<ObjectDirectoryInterface> object_directory_,
SpaceReleasedCallback on_objects_spilled);
/// Process a new client connection.
///
+2 -1
View File
@@ -81,7 +81,8 @@ Raylet::Raylet(boost::asio::io_service &main_service, const std::string &socket_
num_bytes_required);
}),
node_manager_(main_service, self_node_id_, node_manager_config, object_manager_,
gcs_client_, object_directory_),
gcs_client_, object_directory_,
plasma::plasma_store_runner->OnSpaceReleased()),
socket_name_(socket_name),
acceptor_(main_service, ParseUrlEndpoint(socket_name)),
socket_(main_service) {
@@ -196,7 +196,8 @@ class LocalObjectManagerTest : public ::testing::Test {
for (const auto &object_id : object_ids) {
freed.insert(object_id);
}
}),
},
[&]() { num_callbacks_fired++; }),
unpins(std::make_shared<std::unordered_map<ObjectID, int>>()) {
RayConfig::instance().initialize({{"object_spilling_config", "mock_config"}});
}
@@ -212,6 +213,7 @@ class LocalObjectManagerTest : public ::testing::Test {
// This hashmap is incremented when objects are unpinned by destroying their
// unique_ptr.
std::shared_ptr<std::unordered_map<ObjectID, int>> unpins;
size_t num_callbacks_fired = 0;
};
TEST_F(LocalObjectManagerTest, TestPin) {
@@ -240,6 +242,7 @@ TEST_F(LocalObjectManagerTest, TestPin) {
}
std::unordered_set<ObjectID> expected(object_ids.begin(), object_ids.end());
ASSERT_EQ(freed, expected);
ASSERT_EQ(num_callbacks_fired, 0);
}
TEST_F(LocalObjectManagerTest, TestRestoreSpilledObject) {
@@ -252,6 +255,7 @@ TEST_F(LocalObjectManagerTest, TestRestoreSpilledObject) {
num_times_fired++;
});
ASSERT_EQ(num_times_fired, 1);
ASSERT_EQ(num_callbacks_fired, 0);
}
TEST_F(LocalObjectManagerTest, TestExplicitSpill) {
@@ -294,6 +298,7 @@ TEST_F(LocalObjectManagerTest, TestExplicitSpill) {
for (const auto &id : object_ids) {
ASSERT_EQ((*unpins)[id], 1);
}
ASSERT_TRUE(num_callbacks_fired > 0);
}
TEST_F(LocalObjectManagerTest, TestDuplicateSpill) {
@@ -345,6 +350,7 @@ TEST_F(LocalObjectManagerTest, TestDuplicateSpill) {
for (const auto &id : object_ids) {
ASSERT_EQ((*unpins)[id], 1);
}
ASSERT_TRUE(num_callbacks_fired > 0);
}
TEST_F(LocalObjectManagerTest, TestSpillObjectsOfSize) {
@@ -400,6 +406,7 @@ TEST_F(LocalObjectManagerTest, TestSpillObjectsOfSize) {
// Check that this returns the total number of bytes currently being spilled.
num_bytes_required = manager.SpillObjectsOfSize(0);
ASSERT_EQ(num_bytes_required, 0);
ASSERT_TRUE(num_callbacks_fired > 0);
}
TEST_F(LocalObjectManagerTest, TestSpillError) {
@@ -443,6 +450,7 @@ TEST_F(LocalObjectManagerTest, TestSpillError) {
ASSERT_EQ(num_times_fired, 2);
ASSERT_EQ(object_table.object_urls[object_id], url);
ASSERT_EQ((*unpins)[object_id], 1);
ASSERT_TRUE(num_callbacks_fired > 0);
}
} // namespace raylet