Commit faa8d22d authored by holte@chromium.org's avatar holte@chromium.org

Remove the provisional store and just keep the staged log in the queue while uploading it.

BUG=372911

Review URL: https://codereview.chromium.org/290103005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285684 0039d316-1c4b-4281-b951-d872f2087c98
parent 6c9a96d7
...@@ -115,22 +115,6 @@ void MetricsLogManager::StoreLog(const std::string& log_data, ...@@ -115,22 +115,6 @@ void MetricsLogManager::StoreLog(const std::string& log_data,
} }
} }
void MetricsLogManager::StoreStagedLogAsUnsent(
PersistedLogs::StoreType store_type) {
DCHECK(has_staged_log());
if (initial_log_queue_.has_staged_log())
initial_log_queue_.StoreStagedLogAsUnsent(store_type);
else
ongoing_log_queue_.StoreStagedLogAsUnsent(store_type);
}
void MetricsLogManager::DiscardLastProvisionalStore() {
// We have at most one provisional store, (since at most one log is being
// uploaded at a time), so at least one of these will be a no-op.
initial_log_queue_.DiscardLastProvisionalStore();
ongoing_log_queue_.DiscardLastProvisionalStore();
}
void MetricsLogManager::PersistUnsentLogs() { void MetricsLogManager::PersistUnsentLogs() {
DCHECK(unsent_logs_loaded_); DCHECK(unsent_logs_loaded_);
if (!unsent_logs_loaded_) if (!unsent_logs_loaded_)
......
...@@ -84,20 +84,6 @@ class MetricsLogManager { ...@@ -84,20 +84,6 @@ class MetricsLogManager {
// This should only be called if there is not a current log. // This should only be called if there is not a current log.
void ResumePausedLog(); void ResumePausedLog();
// Saves the staged log, then clears staged_log().
// If |store_type| is PROVISIONAL_STORE, it can be dropped from storage with
// a later call to DiscardLastProvisionalStore (if it hasn't already been
// staged again).
// This is intended to be used when logs are being saved while an upload is in
// progress, in case the upload later succeeds.
// This can only be called if has_staged_log() is true.
void StoreStagedLogAsUnsent(PersistedLogs::StoreType store_type);
// Discards the last log stored with StoreStagedLogAsUnsent with |store_type|
// set to PROVISIONAL_STORE, as long as it hasn't already been re-staged. If
// the log is no longer present, this is a no-op.
void DiscardLastProvisionalStore();
// Saves any unsent logs to persistent storage. // Saves any unsent logs to persistent storage.
void PersistUnsentLogs(); void PersistUnsentLogs();
......
...@@ -178,7 +178,6 @@ TEST(MetricsLogManagerTest, StoreAndLoad) { ...@@ -178,7 +178,6 @@ TEST(MetricsLogManagerTest, StoreAndLoad) {
log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.StageNextLogForUpload(); log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
log_manager.FinishCurrentLog(); log_manager.FinishCurrentLog();
// Nothing should be written out until PersistUnsentLogs is called. // Nothing should be written out until PersistUnsentLogs is called.
...@@ -238,7 +237,6 @@ TEST(MetricsLogManagerTest, StoreStagedLogTypes) { ...@@ -238,7 +237,6 @@ TEST(MetricsLogManagerTest, StoreStagedLogTypes) {
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.FinishCurrentLog(); log_manager.FinishCurrentLog();
log_manager.StageNextLogForUpload(); log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
log_manager.PersistUnsentLogs(); log_manager.PersistUnsentLogs();
EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
...@@ -254,7 +252,6 @@ TEST(MetricsLogManagerTest, StoreStagedLogTypes) { ...@@ -254,7 +252,6 @@ TEST(MetricsLogManagerTest, StoreStagedLogTypes) {
"id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service))); "id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client, &pref_service)));
log_manager.FinishCurrentLog(); log_manager.FinishCurrentLog();
log_manager.StageNextLogForUpload(); log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
log_manager.PersistUnsentLogs(); log_manager.PersistUnsentLogs();
EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG)); EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
...@@ -282,10 +279,10 @@ TEST(MetricsLogManagerTest, LargeLogDiscarding) { ...@@ -282,10 +279,10 @@ TEST(MetricsLogManagerTest, LargeLogDiscarding) {
EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
} }
TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) { TEST(MetricsLogManagerTest, DiscardOrder) {
// Ensure that the correct log is discarded if new logs are pushed while
// a log is staged.
TestMetricsServiceClient client; TestMetricsServiceClient client;
// Ensure that provisional store works, and discards the correct log.
{ {
TestLogPrefService pref_service; TestLogPrefService pref_service;
MetricsLogManager log_manager(&pref_service, 0); MetricsLogManager log_manager(&pref_service, 0);
...@@ -297,64 +294,11 @@ TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) { ...@@ -297,64 +294,11 @@ TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) {
log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog( log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service))); "id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.StageNextLogForUpload(); log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE);
log_manager.FinishCurrentLog(); log_manager.FinishCurrentLog();
log_manager.DiscardLastProvisionalStore();
log_manager.PersistUnsentLogs();
EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
}
}
TEST(MetricsLogManagerTest, ProvisionalStoreNoop) {
TestMetricsServiceClient client;
// Ensure that trying to drop a sent log is a no-op, even if another log has
// since been staged.
{
TestLogPrefService pref_service;
MetricsLogManager log_manager(&pref_service, 0);
log_manager.LoadPersistedUnsentLogs();
log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.FinishCurrentLog();
log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE);
log_manager.StageNextLogForUpload();
log_manager.DiscardStagedLog(); log_manager.DiscardStagedLog();
log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.FinishCurrentLog();
log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
log_manager.DiscardLastProvisionalStore();
log_manager.PersistUnsentLogs();
EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
}
// Ensure that trying to drop more than once is a no-op
{
TestLogPrefService pref_service;
MetricsLogManager log_manager(&pref_service, 0);
log_manager.LoadPersistedUnsentLogs();
log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.FinishCurrentLog();
log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
log_manager.BeginLoggingWithLog(make_scoped_ptr(new MetricsLog(
"id", 0, MetricsLog::ONGOING_LOG, &client, &pref_service)));
log_manager.FinishCurrentLog();
log_manager.StageNextLogForUpload();
log_manager.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE);
log_manager.DiscardLastProvisionalStore();
log_manager.DiscardLastProvisionalStore();
log_manager.PersistUnsentLogs(); log_manager.PersistUnsentLogs();
EXPECT_EQ(0U, pref_service.TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG)); EXPECT_EQ(1U, pref_service.TypeCount(MetricsLog::ONGOING_LOG));
} }
} }
......
...@@ -773,16 +773,6 @@ void MetricsService::PushPendingLogsToPersistentStorage() { ...@@ -773,16 +773,6 @@ void MetricsService::PushPendingLogsToPersistentStorage() {
if (state_ < SENDING_INITIAL_STABILITY_LOG) if (state_ < SENDING_INITIAL_STABILITY_LOG)
return; // We didn't and still don't have time to get plugin list etc. return; // We didn't and still don't have time to get plugin list etc.
if (log_manager_.has_staged_log()) {
// We may race here, and send second copy of the log later.
metrics::PersistedLogs::StoreType store_type;
if (log_upload_in_progress_)
store_type = metrics::PersistedLogs::PROVISIONAL_STORE;
else
store_type = metrics::PersistedLogs::NORMAL_STORE;
log_manager_.StoreStagedLogAsUnsent(store_type);
}
DCHECK(!log_manager_.has_staged_log());
CloseCurrentLog(); CloseCurrentLog();
log_manager_.PersistUnsentLogs(); log_manager_.PersistUnsentLogs();
...@@ -1035,10 +1025,6 @@ void MetricsService::OnLogUploadComplete(int response_code) { ...@@ -1035,10 +1025,6 @@ void MetricsService::OnLogUploadComplete(int response_code) {
ResponseCodeToStatus(response_code), ResponseCodeToStatus(response_code),
NUM_RESPONSE_STATUSES); NUM_RESPONSE_STATUSES);
// If the upload was provisionally stored, drop it now that the upload is
// known to have gone through.
log_manager_.DiscardLastProvisionalStore();
bool upload_succeeded = response_code == 200; bool upload_succeeded = response_code == 200;
// Provide boolean for error recovery (allow us to ignore response_code). // Provide boolean for error recovery (allow us to ignore response_code).
......
...@@ -65,16 +65,6 @@ void PersistedLogs::LogHashPair::Init(const std::string& log_data) { ...@@ -65,16 +65,6 @@ void PersistedLogs::LogHashPair::Init(const std::string& log_data) {
hash = base::SHA1HashString(log_data); hash = base::SHA1HashString(log_data);
} }
void PersistedLogs::LogHashPair::Clear() {
compressed_log_data.clear();
hash.clear();
}
void PersistedLogs::LogHashPair::Swap(PersistedLogs::LogHashPair* input) {
compressed_log_data.swap(input->compressed_log_data);
hash.swap(input->hash);
}
PersistedLogs::PersistedLogs(PrefService* local_state, PersistedLogs::PersistedLogs(PrefService* local_state,
const char* pref_name, const char* pref_name,
const char* old_pref_name, const char* old_pref_name,
...@@ -87,7 +77,7 @@ PersistedLogs::PersistedLogs(PrefService* local_state, ...@@ -87,7 +77,7 @@ PersistedLogs::PersistedLogs(PrefService* local_state,
min_log_count_(min_log_count), min_log_count_(min_log_count),
min_log_bytes_(min_log_bytes), min_log_bytes_(min_log_bytes),
max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)), max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)),
last_provisional_store_index_(-1) { staged_log_index_(-1) {
DCHECK(local_state_); DCHECK(local_state_);
// One of the limit arguments must be non-zero. // One of the limit arguments must be non-zero.
DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0); DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0);
...@@ -125,33 +115,15 @@ void PersistedLogs::StageLog() { ...@@ -125,33 +115,15 @@ void PersistedLogs::StageLog() {
// hard-to-identify crashes much later. // hard-to-identify crashes much later.
CHECK(!list_.empty()); CHECK(!list_.empty());
DCHECK(!has_staged_log()); DCHECK(!has_staged_log());
staged_log_.Swap(&list_.back()); staged_log_index_ = list_.size() - 1;
list_.pop_back();
// If the staged log was the last provisional store, clear that.
if (static_cast<size_t>(last_provisional_store_index_) == list_.size())
last_provisional_store_index_ = -1;
DCHECK(has_staged_log()); DCHECK(has_staged_log());
} }
void PersistedLogs::DiscardStagedLog() { void PersistedLogs::DiscardStagedLog() {
DCHECK(has_staged_log()); DCHECK(has_staged_log());
staged_log_.Clear(); DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size());
} list_.erase(list_.begin() + staged_log_index_);
staged_log_index_ = -1;
void PersistedLogs::StoreStagedLogAsUnsent(StoreType store_type) {
list_.push_back(LogHashPair());
list_.back().Swap(&staged_log_);
if (store_type == PROVISIONAL_STORE)
last_provisional_store_index_ = list_.size() - 1;
}
void PersistedLogs::DiscardLastProvisionalStore() {
if (last_provisional_store_index_ == -1)
return;
DCHECK_LT(static_cast<size_t>(last_provisional_store_index_), list_.size());
list_.erase(list_.begin() + last_provisional_store_index_);
last_provisional_store_index_ = -1;
} }
void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const { void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const {
......
...@@ -37,11 +37,6 @@ class PersistedLogs { ...@@ -37,11 +37,6 @@ class PersistedLogs {
END_RECALL_STATUS // Number of bins to use to create the histogram. END_RECALL_STATUS // Number of bins to use to create the histogram.
}; };
enum StoreType {
NORMAL_STORE, // A standard store operation.
PROVISIONAL_STORE, // A store operation that can be easily reverted later.
};
// Constructs a PersistedLogs that stores data in |local_state| under the // Constructs a PersistedLogs that stores data in |local_state| under the
// preference |pref_name| and also reads from legacy pref |old_pref_name|. // preference |pref_name| and also reads from legacy pref |old_pref_name|.
// Calling code is responsible for ensuring that the lifetime of |local_state| // Calling code is responsible for ensuring that the lifetime of |local_state|
...@@ -76,35 +71,19 @@ class PersistedLogs { ...@@ -76,35 +71,19 @@ class PersistedLogs {
// Remove the staged log. // Remove the staged log.
void DiscardStagedLog(); void DiscardStagedLog();
// Saves the staged log, then clears staged_log().
// If |store_type| is PROVISIONAL_STORE, it can be dropped from storage with
// a later call to DiscardLastProvisionalStore (if it hasn't already been
// staged again).
// This is intended to be used when logs are being saved while an upload is in
// progress, in case the upload later succeeds.
// This can only be called if has_staged_log() is true.
void StoreStagedLogAsUnsent(StoreType store_type);
// Discards the last log stored with StoreStagedLogAsUnsent with |store_type|
// set to PROVISIONAL_STORE, as long as it hasn't already been re-staged. If
// the log is no longer present, this is a no-op.
void DiscardLastProvisionalStore();
// True if a log has been staged. // True if a log has been staged.
bool has_staged_log() const { bool has_staged_log() const { return staged_log_index_ != -1; };
return !staged_log_.compressed_log_data.empty();
}
// Returns the element in the front of the list. // Returns the element in the front of the list.
const std::string& staged_log() const { const std::string& staged_log() const {
DCHECK(has_staged_log()); DCHECK(has_staged_log());
return staged_log_.compressed_log_data; return list_[staged_log_index_].compressed_log_data;
} }
// Returns the element in the front of the list. // Returns the element in the front of the list.
const std::string& staged_log_hash() const { const std::string& staged_log_hash() const {
DCHECK(has_staged_log()); DCHECK(has_staged_log());
return staged_log_.hash; return list_[staged_log_index_].hash;
} }
// The number of elements currently stored. // The number of elements currently stored.
...@@ -149,12 +128,6 @@ class PersistedLogs { ...@@ -149,12 +128,6 @@ class PersistedLogs {
// Initializes the members based on uncompressed |log_data|. // Initializes the members based on uncompressed |log_data|.
void Init(const std::string& log_data); void Init(const std::string& log_data);
// Clears the struct members.
void Clear();
// Swap both log and hash from another LogHashPair.
void Swap(LogHashPair* input);
// Compressed log data - a serialized protobuf that's been gzipped. // Compressed log data - a serialized protobuf that's been gzipped.
std::string compressed_log_data; std::string compressed_log_data;
...@@ -165,16 +138,9 @@ class PersistedLogs { ...@@ -165,16 +138,9 @@ class PersistedLogs {
// corruption while they are stored in memory. // corruption while they are stored in memory.
std::vector<LogHashPair> list_; std::vector<LogHashPair> list_;
// The log staged for upload. // The index and type of the log staged for upload. If nothing has been
LogHashPair staged_log_; // staged, the index will be -1.
int staged_log_index_;
// The index and type of the last provisional store. If nothing has been
// provisionally stored, or the last provisional store has already been
// re-staged, the index will be -1;
// This is necessary because during an upload there are two logs (staged
// and current) and a client might store them in either order, so it's
// not necessarily the case that the provisional store is the last store.
int last_provisional_store_index_;
DISALLOW_COPY_AND_ASSIGN(PersistedLogs); DISALLOW_COPY_AND_ASSIGN(PersistedLogs);
}; };
......
...@@ -240,7 +240,7 @@ TEST_F(PersistedLogsTest, Staging) { ...@@ -240,7 +240,7 @@ TEST_F(PersistedLogsTest, Staging) {
EXPECT_EQ(persisted_logs.staged_log(), Compress("two")); EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
persisted_logs.StoreLog("three"); persisted_logs.StoreLog("three");
EXPECT_EQ(persisted_logs.staged_log(), Compress("two")); EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
EXPECT_EQ(persisted_logs.size(), 2U); EXPECT_EQ(persisted_logs.size(), 3U);
persisted_logs.DiscardStagedLog(); persisted_logs.DiscardStagedLog();
EXPECT_FALSE(persisted_logs.has_staged_log()); EXPECT_FALSE(persisted_logs.has_staged_log());
EXPECT_EQ(persisted_logs.size(), 2U); EXPECT_EQ(persisted_logs.size(), 2U);
...@@ -254,38 +254,15 @@ TEST_F(PersistedLogsTest, Staging) { ...@@ -254,38 +254,15 @@ TEST_F(PersistedLogsTest, Staging) {
EXPECT_EQ(persisted_logs.size(), 0U); EXPECT_EQ(persisted_logs.size(), 0U);
} }
TEST_F(PersistedLogsTest, ProvisionalStoreStandardFlow) { TEST_F(PersistedLogsTest, DiscardOrder) {
// Ensure that provisional store works, and discards the correct log. // Ensure that the correct log is discarded if new logs are pushed while
// a log is staged.
TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit); TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
persisted_logs.StoreLog("one"); persisted_logs.StoreLog("one");
persisted_logs.StageLog(); persisted_logs.StageLog();
persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE);
persisted_logs.StoreLog("two"); persisted_logs.StoreLog("two");
persisted_logs.DiscardLastProvisionalStore();
persisted_logs.SerializeLogs();
TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
result_persisted_logs.DeserializeLogs());
EXPECT_EQ(1U, result_persisted_logs.size());
result_persisted_logs.ExpectNextLog("two");
}
TEST_F(PersistedLogsTest, ProvisionalStoreNoop1) {
// Ensure that trying to drop a sent log is a no-op, even if another log has
// since been staged.
TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
persisted_logs.DeserializeLogs();
persisted_logs.StoreLog("one");
persisted_logs.StageLog();
persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE);
persisted_logs.StageLog();
persisted_logs.DiscardStagedLog(); persisted_logs.DiscardStagedLog();
persisted_logs.StoreLog("two");
persisted_logs.StageLog();
persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
persisted_logs.DiscardLastProvisionalStore();
persisted_logs.SerializeLogs(); persisted_logs.SerializeLogs();
TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit); TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
...@@ -295,28 +272,8 @@ TEST_F(PersistedLogsTest, ProvisionalStoreNoop1) { ...@@ -295,28 +272,8 @@ TEST_F(PersistedLogsTest, ProvisionalStoreNoop1) {
result_persisted_logs.ExpectNextLog("two"); result_persisted_logs.ExpectNextLog("two");
} }
TEST_F(PersistedLogsTest, ProvisionalStoreNoop2) {
// Ensure that trying to drop more than once is a no-op
TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
persisted_logs.DeserializeLogs();
persisted_logs.StoreLog("one");
persisted_logs.StageLog();
persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE);
persisted_logs.StoreLog("two");
persisted_logs.StageLog();
persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE);
persisted_logs.DiscardLastProvisionalStore();
persisted_logs.DiscardLastProvisionalStore();
persisted_logs.SerializeLogs();
TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
EXPECT_EQ(PersistedLogs::RECALL_SUCCESS,
result_persisted_logs.DeserializeLogs());
EXPECT_EQ(1U, result_persisted_logs.size());
result_persisted_logs.ExpectNextLog("one");
}
TEST_F(PersistedLogsTest, Encoding) { TEST_F(PersistedLogsTest, Hashes) {
const char kFooText[] = "foo"; const char kFooText[] = "foo";
const std::string foo_hash = base::SHA1HashString(kFooText); const std::string foo_hash = base::SHA1HashString(kFooText);
......
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