Commit 5a66cdf5 authored by Stanislav Bohm's avatar Stanislav Bohm

RF: DataPtr introduced

parent ca4801fe
......@@ -12,12 +12,12 @@ size_t Data::get_length() const
return 0;
}
std::shared_ptr<Data> Data::get_at_index(size_t index) const
DataPtr Data::get_at_index(size_t index) const
{
assert(0);
}
std::shared_ptr<Data> Data::get_slice(size_t from, size_t to) const
DataPtr Data::get_slice(size_t from, size_t to) const
{
assert(0);
}
......@@ -42,7 +42,7 @@ base::Id Data::get_type_id(Worker &worker) const
return worker.get_dictionary().find_symbol(get_type_name());
}
DataBufferItem::DataBufferItem(std::shared_ptr<Data> &data, const char *mem, size_t size)
DataBufferItem::DataBufferItem(DataPtr &data, const char *mem, size_t size)
: mem(mem), size(size), data(data)
{
......
......@@ -16,6 +16,10 @@ namespace loom {
class Worker;
class Connection;
class Data;
using DataPtr = std::shared_ptr<const Data>;
using DataVector = std::vector<DataPtr>;
/** Base class for data objects */
class Data
......@@ -35,13 +39,13 @@ public:
virtual std::string get_info() const = 0;
/** Get subobject at given index (0 ... get_length()) */
virtual std::shared_ptr<Data> get_at_index(size_t index) const;
virtual DataPtr get_at_index(size_t index) const;
/** Get subobject slice at given indices (0 ... get_length()) */
virtual std::shared_ptr<Data> get_slice(size_t from, size_t to) const;
virtual DataPtr get_slice(size_t from, size_t to) const;
/** Serialize object into send buffer */
virtual size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const = 0;
virtual size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const = 0;
/** Get pointer to raw data, returns nullptr when it is not possible */
virtual const char *get_raw_data() const;
......@@ -59,17 +63,15 @@ protected:
class DataBufferItem : public loom::base::SendBufferItem {
public:
DataBufferItem(std::shared_ptr<Data> &data, const char *mem, size_t size);
DataBufferItem(DataPtr &data, const char *mem, size_t size);
uv_buf_t get_buf();
protected:
const char *mem;
size_t size;
std::shared_ptr<Data> data;
DataPtr data;
};
typedef std::vector<std::shared_ptr<Data>> DataVector;
}
#endif // LIBLOOMW_DATA_H
......@@ -7,7 +7,7 @@
using namespace loom;
using namespace loom::base;
Array::Array(size_t length, std::unique_ptr<std::shared_ptr<Data>[]> items)
Array::Array(size_t length, std::unique_ptr<DataPtr[]> items)
: length(length), items(std::move(items))
{
......@@ -36,7 +36,7 @@ std::string Array::get_info() const
return "Array";
}
std::shared_ptr<Data> Array::get_slice(size_t from, size_t to) const
DataPtr Array::get_slice(size_t from, size_t to) const
{
if (from > length) {
from = length;
......@@ -53,7 +53,7 @@ std::shared_ptr<Data> Array::get_slice(size_t from, size_t to) const
size = to - from;
}
auto items = std::make_unique<std::shared_ptr<Data>[]>(size);
auto items = std::make_unique<DataPtr[]>(size);
size_t j = 0;
for (size_t i = from; i < to; i++, j++) {
......@@ -62,7 +62,7 @@ std::shared_ptr<Data> Array::get_slice(size_t from, size_t to) const
return std::make_shared<Array>(size, std::move(items));
}
std::shared_ptr<Data> &Array::get_ref_at_index(size_t index)
DataPtr &Array::get_ref_at_index(size_t index)
{
assert(index < length);
return items[index];
......@@ -73,13 +73,13 @@ std::string Array::get_type_name() const
return "loom/array";
}
std::shared_ptr<Data> Array::get_at_index(size_t index) const
DataPtr Array::get_at_index(size_t index) const
{
assert(index < length);
return items[index];
}
size_t Array::serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const
size_t Array::serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const
{
auto types = std::make_unique<base::MemItemWithSz>(sizeof(loom::base::Id) * length);
loom::base::Id *ts = reinterpret_cast<loom::base::Id*>(types->get_ptr());
......@@ -116,7 +116,7 @@ DataUnpacker::Result ArrayUnpacker::on_message(const char *data, size_t size)
return FINISHED;
}
items = std::make_unique<std::shared_ptr<Data>[]>(sz);
items = std::make_unique<DataPtr[]>(sz);
unpacker = worker.get_unpacker(types[0]);
return unpacker->get_initial_mode();
} else {
......@@ -138,7 +138,7 @@ DataUnpacker::Result ArrayUnpacker::on_stream_data(const char *data, size_t size
return unpack_next();
}
std::shared_ptr<Data> ArrayUnpacker::finish()
DataPtr ArrayUnpacker::finish()
{
return std::make_shared<Array>(types.size(), std::move(items));
}
......
......@@ -8,24 +8,24 @@ namespace loom {
class Array : public Data {
public:
Array(size_t length, std::unique_ptr<std::shared_ptr<Data>[]> items);
Array(size_t length, std::unique_ptr<DataPtr[]> items);
Array(const DataVector &items);
~Array();
size_t get_length() const override;
size_t get_size() const override;
std::string get_info() const override;
std::shared_ptr<Data> get_at_index(size_t index) const override;
std::shared_ptr<Data> get_slice(size_t from, size_t to) const override;
DataPtr get_at_index(size_t index) const override;
DataPtr get_slice(size_t from, size_t to) const override;
std::string get_type_name() const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const override;
std::shared_ptr<Data>& get_ref_at_index(size_t index);
DataPtr& get_ref_at_index(size_t index);
protected:
size_t length;
std::unique_ptr<std::shared_ptr<Data>[]> items;
std::unique_ptr<DataPtr[]> items;
};
......@@ -37,14 +37,14 @@ public:
Result on_message(const char *data, size_t size) override;
Result on_stream_data(const char *data, size_t size, size_t remaining) override;
std::shared_ptr<Data> finish() override;
DataPtr finish() override;
Result unpack_next();
private:
std::unique_ptr<DataUnpacker> unpacker;
std::vector<loom::base::Id> types;
std::unique_ptr<std::shared_ptr<Data>[]> items;
std::unique_ptr<DataPtr[]> items;
size_t index;
Worker &worker;
};
......
......@@ -48,7 +48,7 @@ std::string loom::ExternFile::get_info() const
return "<ExternFile '" + filename + "'>";
}
size_t ExternFile::serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const
size_t ExternFile::serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const
{
assert(0); // TODO
}
......
......@@ -18,7 +18,7 @@ public:
bool has_raw_data() const override;
std::string get_info() const override;
std::string get_filename() const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const override;
protected:
......
......@@ -8,7 +8,7 @@
using namespace loom;
using namespace loom::base;
Index::Index(Worker &worker, std::shared_ptr<Data> &data, size_t length, std::unique_ptr<size_t[]> indices)
Index::Index(Worker &worker, DataPtr &data, size_t length, std::unique_ptr<size_t[]> indices)
: worker(worker), data(data), length(length), indices(std::move(indices))
{
......@@ -39,7 +39,7 @@ std::string Index::get_info() const
return "Index";
}
std::shared_ptr<Data> Index::get_at_index(size_t index) const
DataPtr Index::get_at_index(size_t index) const
{
assert (index < length);
size_t addr = indices[index];
......@@ -53,7 +53,7 @@ std::shared_ptr<Data> Index::get_at_index(size_t index) const
return data;
}
std::shared_ptr<Data> Index::get_slice(size_t from, size_t to) const
DataPtr Index::get_slice(size_t from, size_t to) const
{
if (from > length) {
from = length;
......@@ -81,7 +81,7 @@ std::shared_ptr<Data> Index::get_slice(size_t from, size_t to) const
return data;
}
size_t Index::serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const
size_t Index::serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const
{
logger->critical("Index::serialize_data");
abort();
......@@ -140,7 +140,7 @@ bool IndexUnpacker::on_data_finish(Connection &connection)
void IndexUnpacker::finish_data()
{
std::shared_ptr<Data> &inner_data = unpacker->get_data();
DataPtr &inner_data = unpacker->get_data();
data = std::make_shared<Index>(*worker, inner_data, length, std::move(indices));
}
*/
......@@ -160,7 +160,7 @@ DataUnpacker::Result IndexUnpacker::on_message(const char *data, size_t size)
assert(0);
}
std::shared_ptr<Data> IndexUnpacker::finish()
DataPtr IndexUnpacker::finish()
{
assert(0);
}
......@@ -15,7 +15,7 @@ class Worker;
class Index : public Data {
public:
Index(Worker &worker,
std::shared_ptr<Data> &data,
DataPtr &data,
size_t length,
std::unique_ptr<size_t[]> indices);
......@@ -25,15 +25,15 @@ public:
size_t get_length() const override;
size_t get_size() const override;
std::string get_info() const override;
std::shared_ptr<Data> get_at_index(size_t index) const override;
std::shared_ptr<Data> get_slice(size_t from, size_t to) const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const override;
DataPtr get_at_index(size_t index) const override;
DataPtr get_slice(size_t from, size_t to) const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const override;
protected:
Worker &worker;
std::shared_ptr<Data> data;
DataPtr data;
size_t length;
std::unique_ptr<size_t[]> indices;
};
......@@ -46,7 +46,7 @@ public:
~IndexUnpacker();
Result on_message(const char *data, size_t size) override;
std::shared_ptr<Data> finish() override;
DataPtr finish() override;
};
}
......
......@@ -170,7 +170,7 @@ void RawData::init_from_mem(const std::string &work_dir, const void *ptr, size_t
memcpy(mem, ptr, size);
}
size_t RawData::serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const
size_t RawData::serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const
{
buffer.add(std::make_unique<base::SizeBufferItem>(size));
buffer.add(std::make_unique<DataBufferItem>(data_ptr, get_raw_data(), size));
......@@ -213,7 +213,7 @@ DataUnpacker::Result RawDataUnpacker::on_stream_data(const char *data, size_t si
}
}
std::shared_ptr<Data> RawDataUnpacker::finish()
DataPtr RawDataUnpacker::finish()
{
return result;
}
......@@ -23,7 +23,7 @@ public:
bool has_raw_data() const override;
std::string get_info() const override;
std::string get_filename() const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, std::shared_ptr<Data> &data_ptr) const override;
size_t serialize(Worker &worker, loom::base::SendBuffer &buffer, DataPtr &data_ptr) const override;
char* init_empty(const std::string &work_dir, size_t size);
void init_from_string(const std::string &work_dir, const std::string &str);
......@@ -54,9 +54,9 @@ public:
Result get_initial_mode() override;
Result on_stream_data(const char *data, size_t size, size_t remaining) override;
std::shared_ptr<Data> finish() override;
DataPtr finish() override;
private:
std::shared_ptr<Data> result;
DataPtr result;
char* ptr;
const std::string& worker_dir;
};
......
......@@ -116,7 +116,7 @@ void InterConnection::on_stream_data(const char *buffer, size_t size, size_t rem
}
}
void InterConnection::send(Id id, std::shared_ptr<Data> &data)
void InterConnection::send(Id id, DataPtr &data)
{
auto buffer = std::make_unique<loom::base::SendBuffer>();
......
......@@ -22,7 +22,7 @@ public:
InterConnection(Worker &worker);
~InterConnection();
void send(base::Id id, std::shared_ptr<Data> &data);
void send(base::Id id, DataPtr &data);
std::string get_peername() {
return socket.get_peername();
......
......@@ -44,12 +44,12 @@ void TaskInstance::fail_libuv(const std::string &error_msg, int error_code)
fail(s.str());
}
void TaskInstance::finish(const std::shared_ptr<Data> &output)
void TaskInstance::finish(const DataPtr &output)
{
assert(output);
worker.publish_data(get_id(), output);
assert(output);
worker.task_finished(*this, *output);
worker.task_finished(*this, output);
}
void TaskInstance::redirect(std::unique_ptr<TaskDescription> tdesc)
......
......@@ -45,7 +45,7 @@ public:
protected:
void fail(const std::string &error_msg);
void fail_libuv(const std::string &error_msg, int error_code);
void finish(const std::shared_ptr<Data> &output);
void finish(const DataPtr &output);
void redirect(std::unique_ptr<TaskDescription> tdesc);
......
......@@ -7,10 +7,10 @@ using namespace loom;
void ArrayMakeTask::start(DataVector &inputs)
{
size_t size = inputs.size();
auto items = std::make_unique<std::shared_ptr<Data>[]>(size);
auto items = std::make_unique<DataPtr[]>(size);
for (size_t i = 0; i < size; i++) {
items[i] = inputs[i];
}
std::shared_ptr<Data> output = std::make_shared<Array>(size, std::move(items));
DataPtr output = std::make_shared<Array>(size, std::move(items));
finish(output);
}
......@@ -12,7 +12,7 @@ void GetTask::start(DataVector &inputs)
assert(inputs.size() == 1);
assert(task->get_config().size() == sizeof(size_t));
const size_t *index = reinterpret_cast<const size_t*>(task->get_config().data());
std::shared_ptr<Data> &input = inputs[0];
DataPtr &input = inputs[0];
auto result = input->get_at_index(*index);
finish(result);
}
......@@ -22,7 +22,7 @@ void SliceTask::start(DataVector &inputs)
assert(inputs.size() == 1);
assert(task->get_config().size() == sizeof(size_t) * 2);
const size_t *index = reinterpret_cast<const size_t*>(task->get_config().data());
std::shared_ptr<Data> &input = inputs[0];
DataPtr &input = inputs[0];
auto result = input->get_slice(index[0], index[1]);
finish(result);
}
......@@ -33,7 +33,7 @@ void SizeTask::start(DataVector &inputs)
for (auto &d : inputs) {
size += d->get_size();
}
std::shared_ptr<Data> output = std::make_shared<RawData>();
auto output = std::make_shared<RawData>();
RawData &data = static_cast<RawData&>(*output);
memcpy(data.init_empty(worker.get_work_dir(), sizeof(size_t)), &size, sizeof(size_t));
finish(output);
......@@ -45,7 +45,7 @@ void LengthTask::start(DataVector &inputs)
for (auto &d : inputs) {
length += d->get_length();
}
std::shared_ptr<Data> output = std::make_shared<RawData>();
auto output = std::make_shared<RawData>();
RawData &data = static_cast<RawData&>(*output);
memcpy(data.init_empty(worker.get_work_dir(), sizeof(size_t)), &length, sizeof(size_t));
finish(output);
......
......@@ -96,7 +96,7 @@ static std::string get_attr_string(PyObject *obj, const char *name)
return result;
}
std::shared_ptr<Data> PyCallJob::run()
DataPtr PyCallJob::run()
{
// Obtain GIL
PyGILState_STATE gstate;
......
......@@ -10,7 +10,7 @@ class PyCallJob : public loom::ThreadJob
public:
PyCallJob(Worker &worker, Task &task);
std::shared_ptr<loom::Data> run() override;
DataPtr run() override;
private:
void set_python_error();
};
......
......@@ -92,7 +92,7 @@ void data_wrapper_init()
assert(!PyType_Ready(&data_wrapper_type));
}
DataWrapper *data_wrapper_create(const std::shared_ptr<loom::Data> &data)
DataWrapper *data_wrapper_create(const loom::DataPtr &data)
{
DataWrapper *self = (DataWrapper*) PyObject_CallFunctionObjArgs((PyObject*) &data_wrapper_type, NULL);
assert(self);
......
......@@ -7,11 +7,11 @@
typedef struct {
PyObject_HEAD
std::shared_ptr<loom::Data> data;
loom::DataPtr data;
} DataWrapper;
void data_wrapper_init();
bool is_data_wrapper(PyObject *obj);
DataWrapper *data_wrapper_create(const std::shared_ptr<loom::Data> &data);
DataWrapper *data_wrapper_create(const loom::DataPtr &data);
#endif // LIBLOOM_TASKS_PYTHON_WRAPPER_H
......@@ -38,7 +38,7 @@ bool MergeJob::check_run_in_thread()
return false;
}
std::shared_ptr<Data> MergeJob::run() {
DataPtr MergeJob::run() {
size_t size = 0;
for (auto& data : inputs) {
size += data->get_size();
......@@ -50,9 +50,8 @@ std::shared_ptr<Data> MergeJob::run() {
size += (inputs.size() - 1) * config.size();
}
std::shared_ptr<Data> output = std::make_shared<RawData>();
RawData &data = static_cast<RawData&>(*output);
char *dst = data.init_empty(work_dir, size);
auto data = std::make_shared<RawData>();
char *dst = data->init_empty(work_dir, size);
if (config.empty()) {
for (auto& data : inputs) {
......@@ -80,12 +79,12 @@ std::shared_ptr<Data> MergeJob::run() {
dst += size;
}
}
return output;
return data;
}
void OpenTask::start(DataVector &inputs)
{
std::shared_ptr<Data> data = std::make_shared<ExternFile>(task->get_config());
DataPtr data = std::make_shared<ExternFile>(task->get_config());
finish(data);
}
......@@ -109,7 +108,7 @@ void SplitTask::start(DataVector &inputs)
auto indices_data = std::make_unique<size_t[]>(indices.size());
memcpy(&indices_data[0], &indices[0], sizeof(size_t) * indices.size());
std::shared_ptr<Data> result = std::make_shared<Index>(worker, input, indices.size() - 1, std::move(indices_data));
DataPtr result = std::make_shared<Index>(worker, input, indices.size() - 1, std::move(indices_data));
finish(result);
}
......@@ -20,7 +20,7 @@ public:
using ThreadJob::ThreadJob;
bool check_run_in_thread();
std::shared_ptr<loom::Data> run() override;
DataPtr run() override;
};
......
......@@ -211,7 +211,7 @@ void RunTask::_on_close(uv_handle_t *handle)
}
if (output_size == 1) {
std::shared_ptr<Data> data_ptr = std::make_shared<RawData>();
auto data_ptr = std::make_shared<RawData>();
RawData& data = static_cast<RawData&>(*data_ptr);
data.assign_filename(task->worker.get_work_dir());
......@@ -230,15 +230,13 @@ void RunTask::_on_close(uv_handle_t *handle)
}
auto items = std::make_unique<std::shared_ptr<Data>[]>(output_size);
auto items = std::make_unique<DataPtr[]>(output_size);
for (int i = 0; i < output_size; i++) {
items[i] = std::make_shared<RawData>();
RawData& data = static_cast<RawData&>(*items[i]);
data.assign_filename(task->worker.get_work_dir());
auto data = std::make_shared<RawData>();
data->assign_filename(task->worker.get_work_dir());
std::string path = task->get_path(msg.map_outputs(i));
std::string data_path = data.get_filename();
std::string data_path = data->get_filename();
logger->debug("Storing file '{}'' as index={}", msg.map_outputs(i), i);
//data->create(task->worker, 10);
if (unlikely(rename(path.c_str(),
......@@ -247,10 +245,11 @@ void RunTask::_on_close(uv_handle_t *handle)
path, data_path);
log_errno_abort("rename");
}
data.init_from_file(task->worker.get_work_dir());
data->init_from_file(task->worker.get_work_dir());
items[i] = std::move(data);
}
std::shared_ptr<Data> output = std::make_shared<Array>(output_size, std::move(items));
DataPtr output = std::make_shared<Array>(output_size, std::move(items));
task->finish(output);
}
......
......@@ -22,7 +22,7 @@ public:
return true;
}
virtual std::shared_ptr<Data> run() = 0;
virtual DataPtr run() = 0;
void set_inputs(DataVector &input_data) {
this->inputs = input_data;
......
......@@ -38,7 +38,7 @@ public:
protected:
uv_work_t work;
T job;
std::shared_ptr<Data> result;
DataPtr result;
static void _work_cb(uv_work_t *req)
{
......
......@@ -22,7 +22,7 @@ public:
virtual Result get_initial_mode();
virtual Result on_message(const char *data, size_t size);
virtual Result on_stream_data(const char *data, size_t size, size_t remaining);
virtual std::shared_ptr<Data> finish() = 0;
virtual DataPtr finish() = 0;
};
using UnpackFactoryFn = std::function<std::unique_ptr<DataUnpacker>()>;
......
......@@ -220,7 +220,7 @@ void Worker::start_task(std::unique_ptr<Task> task)
resource_cpus -= 1;
}
void Worker::publish_data(Id id, const std::shared_ptr<Data> &data)
void Worker:: publish_data(Id id, const DataPtr &data)
{
logger->debug("Publishing data id={} size={} info={}", id, data->get_size(), data->get_info());
public_data[id] = data;
......@@ -430,21 +430,21 @@ void Worker::task_redirect(TaskInstance &task,
t->start(new_task_desc->inputs);
}
void Worker::task_finished(TaskInstance &task, Data &data)
void Worker::task_finished(TaskInstance &task, const DataPtr &data)
{
if (server_conn.is_connected()) {
loomcomm::WorkerResponse msg;
msg.set_type(loomcomm::WorkerResponse_Type_FINISH);
msg.set_id(task.get_id());
msg.set_size(data.get_size());
msg.set_length(data.get_length());
msg.set_size(data->get_size());
msg.set_length(data->get_length());
send_message(server_conn, msg);
}
remove_task(task);
check_ready_tasks();
}
void Worker::send_data(const std::string &address, Id id, std::shared_ptr<Data> &data)
void Worker::send_data(const std::string &address, Id id, DataPtr &data)
{
auto &connection = get_connection(address);;
connection.send(id, data);
......
......@@ -36,7 +36,7 @@ public:
void register_basic_tasks();
void new_task(std::unique_ptr<Task> task);
void send_data(const std::string &address, base::Id id, std::shared_ptr<Data> &data);
void send_data(const std::string &address, base::Id id, DataPtr &data);
bool send_data(const std::string &address, base::Id id) {
auto& data = public_data[id];
if (data.get() == nullptr) {
......@@ -46,10 +46,10 @@ public:
return true;
}
void task_finished(TaskInstance &task_instance, Data &data);
void task_finished(TaskInstance &task_instance, const DataPtr &data);
void task_failed(TaskInstance &task_instance, const std::string &error_msg);
void task_redirect(TaskInstance &task, std::unique_ptr<TaskDescription> new_task_desc);
void publish_data(base::Id id, const std::shared_ptr<Data> &data);
void publish_data(base::Id id, const DataPtr &data);
void remove_data(base::Id id);
bool has_data(base::Id id) const
......@@ -57,7 +57,7 @@ public:
return public_data.find(id) != public_data.end();
}
std::shared_ptr<Data>& get_data(base::Id id)
DataPtr& get_data(base::Id id)
{
auto it = public_data.find(id);
assert(it != public_data.end());
......@@ -133,7 +133,7 @@ private:
std::vector<std::unique_ptr<Task>> waiting_tasks;
std::unordered_map<base::Id, std::unique_ptr<TaskFactory>> task_factories;
std::unordered_map<int, std::shared_ptr<Data>> public_data;
std::unordered_map<int, DataPtr> public_data;
std::string work_dir;
std::unordered_map<base::Id, UnpackFactoryFn> unpack_ffs;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment