Commit 9954e8c3 authored by gab's avatar gab Committed by Commit bot

Remove explicit usage of SequencedWorkerPool from UploadList.

It merely needs a TaskRunner to PostTask() to.

This allows the unittest to merely use a base::Thread and alleviates
issue 646443.

Also did some modern style cleanup:
 - ThreadChecker => SequenceChecker (no state is thread-affine)
 - const scoped_refptr<>& => scoped_refptr<> + std::move
   (avoids a refcount bump when handing off ownership:
    https://groups.google.com/a/chromium.org/d/topic/chromium-dev/TlL1D-Djta0/discussion)

BUG=646443
NO_DEPENDENCY_CHECKS=true

Review-Url: https://codereview.chromium.org/2335193007
Cr-Commit-Position: refs/heads/master@{#418981}
parent 0c23a7f9
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/threading/sequenced_worker_pool.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
......
...@@ -4,17 +4,19 @@ ...@@ -4,17 +4,19 @@
#include "chrome/browser/crash_upload_list/crash_upload_list_android.h" #include "chrome/browser/crash_upload_list/crash_upload_list_android.h"
#include <utility>
#include "base/files/file.h" #include "base/files/file.h"
#include "base/files/file_enumerator.h" #include "base/files/file_enumerator.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/threading/sequenced_worker_pool.h" #include "base/task_runner.h"
#include "ui/base/text/bytes_formatting.h" #include "ui/base/text/bytes_formatting.h"
CrashUploadListAndroid::CrashUploadListAndroid( CrashUploadListAndroid::CrashUploadListAndroid(
Delegate* delegate, Delegate* delegate,
const base::FilePath& upload_log_path, const base::FilePath& upload_log_path,
const scoped_refptr<base::SequencedWorkerPool>& worker_pool) scoped_refptr<base::TaskRunner> task_runner)
: CrashUploadList(delegate, upload_log_path, worker_pool) {} : CrashUploadList(delegate, upload_log_path, std::move(task_runner)) {}
CrashUploadListAndroid::~CrashUploadListAndroid() {} CrashUploadListAndroid::~CrashUploadListAndroid() {}
......
...@@ -10,17 +10,16 @@ ...@@ -10,17 +10,16 @@
namespace base { namespace base {
class FilePath; class FilePath;
class SequencedWorkerPool; class TaskRunner;
} }
// A CrashUploadList that retrieves the list of uploaded reports from the // A CrashUploadList that retrieves the list of uploaded reports from the
// Android crash reporter. // Android crash reporter.
class CrashUploadListAndroid : public CrashUploadList { class CrashUploadListAndroid : public CrashUploadList {
public: public:
CrashUploadListAndroid( CrashUploadListAndroid(Delegate* delegate,
Delegate* delegate, const base::FilePath& upload_log_path,
const base::FilePath& upload_log_path, scoped_refptr<base::TaskRunner> task_runner);
const scoped_refptr<base::SequencedWorkerPool>& worker_pool);
protected: protected:
~CrashUploadListAndroid() override; ~CrashUploadListAndroid() override;
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
#include <stddef.h> #include <stddef.h>
#include "base/threading/sequenced_worker_pool.h" #include <utility>
#include "base/task_runner.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_constants.h"
...@@ -80,8 +82,8 @@ UploadList::UploadInfo::State ReportUploadStateToUploadInfoState( ...@@ -80,8 +82,8 @@ UploadList::UploadInfo::State ReportUploadStateToUploadInfoState(
CrashUploadListCrashpad::CrashUploadListCrashpad( CrashUploadListCrashpad::CrashUploadListCrashpad(
Delegate* delegate, Delegate* delegate,
const scoped_refptr<base::SequencedWorkerPool>& worker_pool) scoped_refptr<base::TaskRunner> task_runner)
: CrashUploadList(delegate, base::FilePath(), worker_pool) {} : CrashUploadList(delegate, base::FilePath(), std::move(task_runner)) {}
CrashUploadListCrashpad::~CrashUploadListCrashpad() {} CrashUploadListCrashpad::~CrashUploadListCrashpad() {}
......
...@@ -10,16 +10,15 @@ ...@@ -10,16 +10,15 @@
namespace base { namespace base {
class FilePath; class FilePath;
class SequencedWorkerPool; class TaskRunner;
} }
// A CrashUploadList that retrieves the list of uploaded reports from the // A CrashUploadList that retrieves the list of uploaded reports from the
// Crashpad database. // Crashpad database.
class CrashUploadListCrashpad : public CrashUploadList { class CrashUploadListCrashpad : public CrashUploadList {
public: public:
CrashUploadListCrashpad( CrashUploadListCrashpad(Delegate* delegate,
Delegate* delegate, scoped_refptr<base::TaskRunner> task_runner);
const scoped_refptr<base::SequencedWorkerPool>& worker_pool);
protected: protected:
~CrashUploadListCrashpad() override; ~CrashUploadListCrashpad() override;
......
...@@ -4,17 +4,17 @@ ...@@ -4,17 +4,17 @@
#include "components/upload_list/crash_upload_list.h" #include "components/upload_list/crash_upload_list.h"
#include <utility>
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/threading/sequenced_worker_pool.h"
// static // static
const char CrashUploadList::kReporterLogFilename[] = "uploads.log"; const char CrashUploadList::kReporterLogFilename[] = "uploads.log";
CrashUploadList::CrashUploadList( CrashUploadList::CrashUploadList(Delegate* delegate,
Delegate* delegate, const base::FilePath& upload_log_path,
const base::FilePath& upload_log_path, scoped_refptr<base::TaskRunner> task_runner)
const scoped_refptr<base::SequencedWorkerPool>& worker_pool) : UploadList(delegate, upload_log_path, std::move(task_runner)) {}
: UploadList(delegate, upload_log_path, worker_pool) {}
CrashUploadList::~CrashUploadList() {} CrashUploadList::~CrashUploadList() = default;
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
namespace base { namespace base {
class FilePath; class FilePath;
class SequencedWorkerPool; class TaskRunner;
} }
// An upload list manager for crash reports from breakpad. // An upload list manager for crash reports from breakpad.
...@@ -24,7 +24,7 @@ class CrashUploadList : public UploadList { ...@@ -24,7 +24,7 @@ class CrashUploadList : public UploadList {
// Creates a new crash upload list with the given callback delegate. // Creates a new crash upload list with the given callback delegate.
CrashUploadList(Delegate* delegate, CrashUploadList(Delegate* delegate,
const base::FilePath& upload_log_path, const base::FilePath& upload_log_path,
const scoped_refptr<base::SequencedWorkerPool>& worker_pool); scoped_refptr<base::TaskRunner> task_runner);
protected: protected:
~CrashUploadList() override; ~CrashUploadList() override;
......
...@@ -11,11 +11,12 @@ ...@@ -11,11 +11,12 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/location.h" #include "base/location.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h" #include "base/strings/string_split.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/threading/sequenced_worker_pool.h" #include "base/task_runner.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/sequenced_task_runner_handle.h"
UploadList::UploadInfo::UploadInfo(const std::string& upload_id, UploadList::UploadInfo::UploadInfo(const std::string& upload_id,
const base::Time& upload_time, const base::Time& upload_time,
...@@ -49,33 +50,31 @@ UploadList::UploadInfo::UploadInfo(const UploadInfo& upload_info) ...@@ -49,33 +50,31 @@ UploadList::UploadInfo::UploadInfo(const UploadInfo& upload_info)
state(upload_info.state), state(upload_info.state),
file_size(upload_info.file_size) {} file_size(upload_info.file_size) {}
UploadList::UploadInfo::~UploadInfo() {} UploadList::UploadInfo::~UploadInfo() = default;
UploadList::UploadList( UploadList::UploadList(Delegate* delegate,
Delegate* delegate, const base::FilePath& upload_log_path,
const base::FilePath& upload_log_path, scoped_refptr<base::TaskRunner> task_runner)
const scoped_refptr<base::SequencedWorkerPool>& worker_pool)
: delegate_(delegate), : delegate_(delegate),
upload_log_path_(upload_log_path), upload_log_path_(upload_log_path),
worker_pool_(worker_pool) {} task_runner_(std::move(task_runner)) {}
UploadList::~UploadList() {} UploadList::~UploadList() = default;
void UploadList::LoadUploadListAsynchronously() { void UploadList::LoadUploadListAsynchronously() {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(sequence_checker_.CalledOnValidSequence());
worker_pool_->PostTask( task_runner_->PostTask(
FROM_HERE, FROM_HERE, base::Bind(&UploadList::PerformLoadAndNotifyDelegate, this,
base::Bind(&UploadList::PerformLoadAndNotifyDelegate, base::SequencedTaskRunnerHandle::Get()));
this, base::ThreadTaskRunnerHandle::Get()));
} }
void UploadList::ClearDelegate() { void UploadList::ClearDelegate() {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(sequence_checker_.CalledOnValidSequence());
delegate_ = NULL; delegate_ = NULL;
} }
void UploadList::PerformLoadAndNotifyDelegate( void UploadList::PerformLoadAndNotifyDelegate(
const scoped_refptr<base::SequencedTaskRunner>& task_runner) { scoped_refptr<base::SequencedTaskRunner> task_runner) {
std::vector<UploadInfo> uploads; std::vector<UploadInfo> uploads;
LoadUploadList(&uploads); LoadUploadList(&uploads);
task_runner->PostTask( task_runner->PostTask(
...@@ -141,7 +140,7 @@ void UploadList::ParseLogEntries( ...@@ -141,7 +140,7 @@ void UploadList::ParseLogEntries(
} }
void UploadList::SetUploadsAndNotifyDelegate(std::vector<UploadInfo> uploads) { void UploadList::SetUploadsAndNotifyDelegate(std::vector<UploadInfo> uploads) {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(sequence_checker_.CalledOnValidSequence());
uploads_ = std::move(uploads); uploads_ = std::move(uploads);
if (delegate_) if (delegate_)
delegate_->OnUploadListAvailable(); delegate_->OnUploadListAvailable();
...@@ -149,16 +148,16 @@ void UploadList::SetUploadsAndNotifyDelegate(std::vector<UploadInfo> uploads) { ...@@ -149,16 +148,16 @@ void UploadList::SetUploadsAndNotifyDelegate(std::vector<UploadInfo> uploads) {
void UploadList::GetUploads(size_t max_count, void UploadList::GetUploads(size_t max_count,
std::vector<UploadInfo>* uploads) { std::vector<UploadInfo>* uploads) {
DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(sequence_checker_.CalledOnValidSequence());
std::copy(uploads_.begin(), std::copy(uploads_.begin(),
uploads_.begin() + std::min(uploads_.size(), max_count), uploads_.begin() + std::min(uploads_.size(), max_count),
std::back_inserter(*uploads)); std::back_inserter(*uploads));
} }
void UploadList::RequestSingleCrashUploadAsync(const std::string& local_id) { void UploadList::RequestSingleCrashUploadAsync(const std::string& local_id) {
DCHECK(sequence_checker_.CalledOnValidSequence());
#if defined(OS_WIN) || defined(OS_MACOSX) #if defined(OS_WIN) || defined(OS_MACOSX)
DCHECK(thread_checker_.CalledOnValidThread()); task_runner_->PostTask(
worker_pool_->PostTask(
FROM_HERE, FROM_HERE,
base::Bind(&UploadList::RequestSingleCrashUpload, this, local_id)); base::Bind(&UploadList::RequestSingleCrashUpload, this, local_id));
#endif #endif
......
...@@ -13,12 +13,12 @@ ...@@ -13,12 +13,12 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/threading/thread_checker.h" #include "base/sequence_checker.h"
#include "base/time/time.h" #include "base/time/time.h"
namespace base { namespace base {
class SequencedTaskRunner; class SequencedTaskRunner;
class SequencedWorkerPool; class TaskRunner;
} }
// Loads and parses an upload list text file of the format // Loads and parses an upload list text file of the format
...@@ -84,7 +84,7 @@ class UploadList : public base::RefCountedThreadSafe<UploadList> { ...@@ -84,7 +84,7 @@ class UploadList : public base::RefCountedThreadSafe<UploadList> {
// Creates a new upload list with the given callback delegate. // Creates a new upload list with the given callback delegate.
UploadList(Delegate* delegate, UploadList(Delegate* delegate,
const base::FilePath& upload_log_path, const base::FilePath& upload_log_path,
const scoped_refptr<base::SequencedWorkerPool>& worker_pool); scoped_refptr<base::TaskRunner> task_runner);
// Starts loading the upload list. OnUploadListAvailable will be called when // Starts loading the upload list. OnUploadListAvailable will be called when
// loading is complete. // loading is complete.
...@@ -118,7 +118,7 @@ class UploadList : public base::RefCountedThreadSafe<UploadList> { ...@@ -118,7 +118,7 @@ class UploadList : public base::RefCountedThreadSafe<UploadList> {
// Manages the background thread work for LoadUploadListAsynchronously(). // Manages the background thread work for LoadUploadListAsynchronously().
void PerformLoadAndNotifyDelegate( void PerformLoadAndNotifyDelegate(
const scoped_refptr<base::SequencedTaskRunner>& task_runner); scoped_refptr<base::SequencedTaskRunner> task_runner);
// Calls the delegate's callback method, if there is a delegate. Stores // Calls the delegate's callback method, if there is a delegate. Stores
// the newly loaded |uploads| into |uploads_| on the delegate's task runner. // the newly loaded |uploads| into |uploads_| on the delegate's task runner.
...@@ -128,15 +128,17 @@ class UploadList : public base::RefCountedThreadSafe<UploadList> { ...@@ -128,15 +128,17 @@ class UploadList : public base::RefCountedThreadSafe<UploadList> {
void ParseLogEntries(const std::vector<std::string>& log_entries, void ParseLogEntries(const std::vector<std::string>& log_entries,
std::vector<UploadInfo>* uploads); std::vector<UploadInfo>* uploads);
// |thread_checker_| ensures that |uploads_| is only set from the task runner // Ensures that this class' thread unsafe state is only accessed from the
// that created the UploadList. // sequence that owns this UploadList.
base::ThreadChecker thread_checker_; base::SequenceChecker sequence_checker_;
std::vector<UploadInfo> uploads_; std::vector<UploadInfo> uploads_;
Delegate* delegate_; Delegate* delegate_;
const base::FilePath upload_log_path_; const base::FilePath upload_log_path_;
scoped_refptr<base::SequencedWorkerPool> worker_pool_; const scoped_refptr<base::TaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(UploadList); DISALLOW_COPY_AND_ASSIGN(UploadList);
}; };
......
...@@ -11,11 +11,13 @@ ...@@ -11,11 +11,13 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/test/sequenced_worker_pool_owner.h" #include "base/task_runner.h"
#include "base/threading/thread.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -29,12 +31,14 @@ const char kTestCaptureTime[] = "2345678901"; ...@@ -29,12 +31,14 @@ const char kTestCaptureTime[] = "2345678901";
class UploadListTest : public testing::Test, class UploadListTest : public testing::Test,
public UploadList::Delegate { public UploadList::Delegate {
public: public:
UploadListTest() : worker_pool_owner_(1, "UploadListTest") {} UploadListTest() : worker_thread_("UploadListTest") {}
void SetUp() override { void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(worker_thread_.Start());
} }
protected:
void WriteUploadLog(const std::string& log_data) { void WriteUploadLog(const std::string& log_data) {
ASSERT_GT(base::WriteFile(log_path(), log_data.c_str(), ASSERT_GT(base::WriteFile(log_path(), log_data.c_str(),
static_cast<int>(log_data.size())), static_cast<int>(log_data.size())),
...@@ -53,9 +57,10 @@ class UploadListTest : public testing::Test, ...@@ -53,9 +57,10 @@ class UploadListTest : public testing::Test,
quit_closure_.Run(); quit_closure_.Run();
} }
const scoped_refptr<base::SequencedWorkerPool> worker_pool() { scoped_refptr<base::TaskRunner> task_runner() const {
return worker_pool_owner_.pool(); return worker_thread_.task_runner();
} }
base::FilePath log_path() { base::FilePath log_path() {
return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("uploads.log")); return temp_dir_.GetPath().Append(FILE_PATH_LITERAL("uploads.log"));
} }
...@@ -63,8 +68,10 @@ class UploadListTest : public testing::Test, ...@@ -63,8 +68,10 @@ class UploadListTest : public testing::Test,
private: private:
base::MessageLoop message_loop_; base::MessageLoop message_loop_;
base::ScopedTempDir temp_dir_; base::ScopedTempDir temp_dir_;
base::SequencedWorkerPoolOwner worker_pool_owner_; base::Thread worker_thread_;
base::Closure quit_closure_; base::Closure quit_closure_;
DISALLOW_COPY_AND_ASSIGN(UploadListTest);
}; };
// These tests test that UploadList can parse a vector of log entry strings of // These tests test that UploadList can parse a vector of log entry strings of
...@@ -80,7 +87,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadId) { ...@@ -80,7 +87,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadId) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -108,7 +115,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadIdLocalId) { ...@@ -108,7 +115,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadIdLocalId) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -137,7 +144,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadIdCaptureTime) { ...@@ -137,7 +144,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadIdCaptureTime) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -165,7 +172,7 @@ TEST_F(UploadListTest, ParseLocalIdCaptureTime) { ...@@ -165,7 +172,7 @@ TEST_F(UploadListTest, ParseLocalIdCaptureTime) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -197,7 +204,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadIdLocalIdCaptureTime) { ...@@ -197,7 +204,7 @@ TEST_F(UploadListTest, ParseUploadTimeUploadIdLocalIdCaptureTime) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -229,7 +236,7 @@ TEST_F(UploadListTest, ParseMultipleEntries) { ...@@ -229,7 +236,7 @@ TEST_F(UploadListTest, ParseMultipleEntries) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -266,7 +273,7 @@ TEST_F(UploadListTest, ParseWithState) { ...@@ -266,7 +273,7 @@ TEST_F(UploadListTest, ParseWithState) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
upload_list->LoadUploadListAsynchronously(); upload_list->LoadUploadListAsynchronously();
WaitForUploadList(); WaitForUploadList();
...@@ -298,7 +305,7 @@ TEST_F(UploadListTest, SimultaneousAccess) { ...@@ -298,7 +305,7 @@ TEST_F(UploadListTest, SimultaneousAccess) {
WriteUploadLog(test_entry); WriteUploadLog(test_entry);
scoped_refptr<UploadList> upload_list = scoped_refptr<UploadList> upload_list =
new UploadList(this, log_path(), worker_pool()); new UploadList(this, log_path(), task_runner());
// Queue up a bunch of loads, waiting only for the first one to complete. // Queue up a bunch of loads, waiting only for the first one to complete.
// Clearing the delegate prevents the QuitClosure from being Run more than // Clearing the delegate prevents the QuitClosure from being Run more than
......
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