* [RFC PATCH v1 0/2] Fixed number of chromium workers @ 2022-08-29 1:11 Ammar Faizi 2022-08-29 1:11 ` [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array Ammar Faizi 2022-08-29 1:11 ` [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function Ammar Faizi 0 siblings, 2 replies; 13+ messages in thread From: Ammar Faizi @ 2022-08-29 1:11 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List Hi Alviro, Currently, a single chnet instance uses a single dedicated chromium thread worker to perform an HTTP request. This doesn't scale well because we need to spawn a new thread for each HTTP request. Performing 4096 HTTP requests in parallel will spawn 4096 chromium thread workers, which is too expensive and consuming too much memory. A single chromium thread worker can handle multiple HTTP requests. This series creates a fixed number of chromium workers with ref count to spread the jobs fairly across the chromium thread workers. This greatly reduces the context switches and improve the performance. It also greatly reduces the memory usage. Implementation: 1) At initialization, when chnet_global_init() is called, create an array of pointers to `struct ch_thpool` and initialize those pointers to null. The number of elements in the array is taken from `std::thread::hardware_concurrency() - 1`. 2) When a new CHNet instance is created, its constructor calls `get_thread()` function which will initialize the array of pointers to `struct ch_thpool` if needed, then increment the ref count and it returns a pointer to the base::Thread class in `struct ch_thpool`. 3) When a CHNet instance is destroyed, it calls `put_thread()` function that will decrement the ref count of the `struct ch_thpool` and delete the object if the ref count reaches zero after getting decremented. [ The implementation detail that fairly spread the job across multiple chromium threads worker is in get_thread() function, please have a look. ] For the ring test case, the gained speedup is 33%. Without this series: ammarfaizi2@integral2:~/work/ncns$ time taskset -c 0-7 make -j8 test -s Running /home/ammarfaizi2/work/ncns/tests/cpp/ring.t real 0m28.184s user 0m52.368s sys 0m27.582s With this series: ammarfaizi2@integral2:~/work/ncns$ time taskset -c 0-7 make -j8 test -s Running /home/ammarfaizi2/work/ncns/tests/cpp/ring.t real 0m18.657s user 0m35.452s sys 0m2.146s Please review and comment! Signed-off-by: Ammar Faizi <[email protected]> --- Ammar Faizi (2): chnet: Prepare global struct ch_thpool array chnet: Implement `get_thread()` and `put_thread()` function chnet/chnet.cc | 128 ++++++++++++++++++++++++++++++++++++++++++++++++- chnet/chnet.h | 2 +- 2 files changed, 128 insertions(+), 2 deletions(-) base-commit: e1123a1e7b9526e4b12356bfed222386d4b00a80 -- Ammar Faizi ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array 2022-08-29 1:11 [RFC PATCH v1 0/2] Fixed number of chromium workers Ammar Faizi @ 2022-08-29 1:11 ` Ammar Faizi 2022-08-29 4:21 ` Alviro Iskandar Setiawan 2022-08-29 1:11 ` [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function Ammar Faizi 1 sibling, 1 reply; 13+ messages in thread From: Ammar Faizi @ 2022-08-29 1:11 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List This is a preparation patch to add "fixed number of chromium workers". Initialize array of pointers `struct ch_thpool` with size `std::thread::hardware_concurrency() - 1`. Signed-off-by: Ammar Faizi <[email protected]> --- chnet/chnet.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/chnet/chnet.cc b/chnet/chnet.cc index 155dba8..8662374 100644 --- a/chnet/chnet.cc +++ b/chnet/chnet.cc @@ -607,6 +607,51 @@ const char *CHNet::read_buf(void) return ch_->read_buf(); } +struct ch_thpool { +public: + base::Thread thread_; + uint32_t ref_count_; + uint32_t idx_; + + ch_thpool(void); +}; + +ch_thpool::ch_thpool(void): + thread_("chromium_thread"), + ref_count_(0) +{ + CHECK((void *)this == (void *)&thread_); + base::Thread::Options options(base::MessagePumpType::IO, 0); + CHECK(thread_.StartWithOptions(std::move(options))); +} + +static uint32_t g_max_ch_thpool; +static std::mutex g_thpool_lock_; +static struct ch_thpool **g_thpool; + +static void init_g_ch_thpool(void) +{ + struct ch_thpool **tmp; + + g_max_ch_thpool = std::thread::hardware_concurrency() - 1; + if (g_max_ch_thpool < 1) + g_max_ch_thpool = 1; + + tmp = (struct ch_thpool **)calloc(g_max_ch_thpool, sizeof(*tmp)); + g_thpool_lock_.lock(); + free(g_thpool); + g_thpool = tmp; + g_thpool_lock_.unlock(); +} + +static void destroy_g_ch_thpool(void) +{ + g_thpool_lock_.lock(); + free(g_thpool); + g_thpool = nullptr; + g_thpool_lock_.unlock(); +} + extern "C" { static base::AtExitManager *at_exit_manager; @@ -641,6 +686,7 @@ __cold void chnet_global_init(void) chnet_fix_chromium_boringssl(); SSL_library_init(); __chnet_global_init(); + init_g_ch_thpool(); has_initialized = true; init_lock.unlock(); } @@ -653,6 +699,7 @@ __cold void chnet_global_stop(void) delete cleanup; at_exit_manager = NULL; cleanup = NULL; + destroy_g_ch_thpool(); has_initialized = false; init_lock.unlock(); } -- Ammar Faizi ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array 2022-08-29 1:11 ` [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array Ammar Faizi @ 2022-08-29 4:21 ` Alviro Iskandar Setiawan 2022-08-29 4:47 ` Ammar Nofan Faizi 0 siblings, 1 reply; 13+ messages in thread From: Alviro Iskandar Setiawan @ 2022-08-29 4:21 UTC (permalink / raw) To: Ammar Faizi; +Cc: Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: > + g_max_ch_thpool = std::thread::hardware_concurrency() - 1; > + if (g_max_ch_thpool < 1) > + g_max_ch_thpool = 1; this is wrong, std::thread::hardware_concurrency() should be treated as a hint, if this happens to return 0, then the sub with 1 will make it ~0, that will definitely hit ENOMEM. this should be: g_max_ch_thpool = std::thread::hardware_concurrency(); if (g_max_ch_thpool <= 1) g_max_ch_thpool = 1; else g_max_ch_thpool -= 1; tq -- Viro ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array 2022-08-29 4:21 ` Alviro Iskandar Setiawan @ 2022-08-29 4:47 ` Ammar Nofan Faizi 0 siblings, 0 replies; 13+ messages in thread From: Ammar Nofan Faizi @ 2022-08-29 4:47 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On 8/29/22 11:21 AM, Alviro Iskandar Setiawan wrote: > On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >> + g_max_ch_thpool = std::thread::hardware_concurrency() - 1; >> + if (g_max_ch_thpool < 1) >> + g_max_ch_thpool = 1; > > this is wrong, std::thread::hardware_concurrency() should be treated > as a hint, if this happens to return 0, then the sub with 1 will make > it ~0, that will definitely hit ENOMEM. > > this should be: > > g_max_ch_thpool = std::thread::hardware_concurrency(); > if (g_max_ch_thpool <= 1) > g_max_ch_thpool = 1; > else > g_max_ch_thpool -= 1; Ah yes, you're right. Will fix it in the v2. -- Ammar Faizi ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 1:11 [RFC PATCH v1 0/2] Fixed number of chromium workers Ammar Faizi 2022-08-29 1:11 ` [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array Ammar Faizi @ 2022-08-29 1:11 ` Ammar Faizi 2022-08-29 4:41 ` Alviro Iskandar Setiawan 1 sibling, 1 reply; 13+ messages in thread From: Ammar Faizi @ 2022-08-29 1:11 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List Currently, a single chnet instance uses a single dedicated chromium thread worker to perform an HTTP request. This doesn't scale well because we need to spawn a new thread for each HTTP request. Performing 4096 HTTP requests in parallel will spawn 4096 chromium thread workers, which is too expensive and consuming too much memory. A single chromium thread worker can handle multiple HTTP requests. This series creates a fixed number of chromium workers with ref count to spread the jobs fairly across the chromium thread workers. This greatly reduces the context switches and improve the performance. It also greatly reduces the memory usage. Implementation: 1) At initialization, when chnet_global_init() is called, create an array of pointers to `struct ch_thpool` and initialize those pointers to null. The number of elements in the array is taken from `std::thread::hardware_concurrency() - 1`. 2) When a new CHNet instance is created, its constructor calls `get_thread()` function which will initialize the array of pointers to `struct ch_thpool` if needed, then increment the ref count and it returns a pointer to the base::Thread class in `struct ch_thpool`. 3) When a CHNet instance is destroyed, it calls `put_thread()` function that will decrement the ref count of the `struct ch_thpool` and delete the object if the ref count reaches zero after getting decremented. For the ring test case, the gained speedup is 33%. Without this patch: ammarfaizi2@integral2:~/work/ncns$ time taskset -c 0-7 make -j8 test -s Running /home/ammarfaizi2/work/ncns/tests/cpp/ring.t real 0m28.184s user 0m52.368s sys 0m27.582s With this patch: ammarfaizi2@integral2:~/work/ncns$ time taskset -c 0-7 make -j8 test -s Running /home/ammarfaizi2/work/ncns/tests/cpp/ring.t real 0m18.657s user 0m35.452s sys 0m2.146s Signed-off-by: Ammar Faizi <[email protected]> --- chnet/chnet.cc | 81 +++++++++++++++++++++++++++++++++++++++++++++++++- chnet/chnet.h | 2 +- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/chnet/chnet.cc b/chnet/chnet.cc index 8662374..782e4fe 100644 --- a/chnet/chnet.cc +++ b/chnet/chnet.cc @@ -12,6 +12,9 @@ #include <cstdlib> using namespace std::chrono_literals; +static base::Thread *get_thread(void); +static void put_thread(void *thread); + namespace net { CHNetSimplePayload::CHNetSimplePayload(const char *payload, size_t payload_len): @@ -251,7 +254,7 @@ net::DefineNetworkTrafficAnnotation("CHNetDelegate", R"( })"); CHNetDelegate::CHNetDelegate(void): - thread_("chromium_thread"), + thread_(*get_thread()), method_("GET"), err_("") { @@ -287,6 +290,7 @@ CHNetDelegate::~CHNetDelegate(void) r->PostTask(FROM_HERE, base::BindOnce(CHNetDelegateDestruct, &url_req_, &url_req_ctx_, &sig)); sig.Wait(); + put_thread(&thread_); } template <typename T, typename... Types> @@ -629,6 +633,81 @@ static uint32_t g_max_ch_thpool; static std::mutex g_thpool_lock_; static struct ch_thpool **g_thpool; + +static base::Thread *get_thread(void) +{ + const uint32_t max_ch_thpool = g_max_ch_thpool; + const uint32_t nr_ref_split = 2048; + struct ch_thpool **thp; + struct ch_thpool *ret = nullptr; + struct ch_thpool *tmp; + uint32_t min_ref_idx; + uint32_t min_ref; + uint32_t i; + + g_thpool_lock_.lock(); + thp = g_thpool; + if (!thp) { + g_thpool_lock_.unlock(); + return nullptr; + } + + tmp = thp[0]; + if (!tmp) { + ret = new struct ch_thpool; + ret->idx_ = 0; + thp[0] = ret; + goto out; + } + + min_ref = tmp->ref_count_; + min_ref_idx = 0; + for (i = 1; i < max_ch_thpool; i++) { + uint32_t ref; + + tmp = thp[i]; + if (!tmp) { + ret = new struct ch_thpool; + ret->idx_ = i; + thp[i] = ret; + goto out; + } + + ref = tmp->ref_count_; + if (ref < nr_ref_split) { + ret = tmp; + break; + } + + if (ref < min_ref) { + min_ref = ref; + min_ref_idx = i; + } + } + + if (!ret) + ret = thp[min_ref_idx]; + +out: + ret->ref_count_++; + g_thpool_lock_.unlock(); + return &ret->thread_; +} + +static void put_thread(void *thread) +{ + struct ch_thpool *th = (struct ch_thpool *)thread; + + g_thpool_lock_.lock(); + if (--th->ref_count_ == 0) { + if (g_thpool) + g_thpool[th->idx_] = nullptr; + + delete th; + } + g_thpool_lock_.unlock(); +} + static void init_g_ch_thpool(void) { struct ch_thpool **tmp; diff --git a/chnet/chnet.h b/chnet/chnet.h index 2332596..8f7b098 100644 --- a/chnet/chnet.h +++ b/chnet/chnet.h @@ -275,7 +275,7 @@ private: std::unique_ptr<URLRequestContext> url_req_ctx_; std::unique_ptr<URLRequest> url_req_; scoped_refptr<IOBufferWithSize> read_buf_; - base::Thread thread_; + base::Thread &thread_; std::atomic<int> read_ret_; std::atomic<int> status_; std::string method_; -- Ammar Faizi ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 1:11 ` [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function Ammar Faizi @ 2022-08-29 4:41 ` Alviro Iskandar Setiawan 2022-08-29 4:54 ` Ammar Nofan Faizi 0 siblings, 1 reply; 13+ messages in thread From: Alviro Iskandar Setiawan @ 2022-08-29 4:41 UTC (permalink / raw) To: Ammar Faizi; +Cc: Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: > @@ -251,7 +254,7 @@ net::DefineNetworkTrafficAnnotation("CHNetDelegate", R"( > })"); > > CHNetDelegate::CHNetDelegate(void): > - thread_("chromium_thread"), > + thread_(*get_thread()), > method_("GET"), > err_("") > { > @@ -287,6 +290,7 @@ CHNetDelegate::~CHNetDelegate(void) > r->PostTask(FROM_HERE, base::BindOnce(CHNetDelegateDestruct, &url_req_, > &url_req_ctx_, &sig)); > sig.Wait(); > + put_thread(&thread_); > } if @url_req_ and @url_req_ctx_ are both nullptr, this put_thread() won't be called and we have a ref count leak > template <typename T, typename... Types> > @@ -629,6 +633,81 @@ static uint32_t g_max_ch_thpool; > static std::mutex g_thpool_lock_; > static struct ch_thpool **g_thpool; > > + > +static base::Thread *get_thread(void) > +{ > + const uint32_t max_ch_thpool = g_max_ch_thpool; > + const uint32_t nr_ref_split = 2048; > + struct ch_thpool **thp; > + struct ch_thpool *ret = nullptr; > + struct ch_thpool *tmp; > + uint32_t min_ref_idx; > + uint32_t min_ref; > + uint32_t i; > + > + g_thpool_lock_.lock(); > + thp = g_thpool; > + if (!thp) { > + g_thpool_lock_.unlock(); > + return nullptr; > + } in what situation @thp can be nullptr? > + tmp = thp[0]; > + if (!tmp) { > + ret = new struct ch_thpool; > + ret->idx_ = 0; > + thp[0] = ret; > + goto out; > + } > + > + min_ref = tmp->ref_count_; > + min_ref_idx = 0; > + for (i = 1; i < max_ch_thpool; i++) { > + uint32_t ref; > + > + tmp = thp[i]; > + if (!tmp) { > + ret = new struct ch_thpool; > + ret->idx_ = i; > + thp[i] = ret; > + goto out; > + } > + > + ref = tmp->ref_count_; > + if (ref < nr_ref_split) { > + ret = tmp; > + break; > + } > + > + if (ref < min_ref) { > + min_ref = ref; > + min_ref_idx = i; > + } > + } > + > + if (!ret) > + ret = thp[min_ref_idx]; > + > +out: > + ret->ref_count_++; > + g_thpool_lock_.unlock(); > + return &ret->thread_; > +} this unlock() call will behave as a full memory barrier for that @ref_count_ increment, is this really needed? you can have the increment after unlock() tho tq -- Viro ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 4:41 ` Alviro Iskandar Setiawan @ 2022-08-29 4:54 ` Ammar Nofan Faizi 2022-08-29 5:17 ` Alviro Iskandar Setiawan 0 siblings, 1 reply; 13+ messages in thread From: Ammar Nofan Faizi @ 2022-08-29 4:54 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: > On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >> @@ -251,7 +254,7 @@ net::DefineNetworkTrafficAnnotation("CHNetDelegate", R"( >> })"); >> >> CHNetDelegate::CHNetDelegate(void): >> - thread_("chromium_thread"), >> + thread_(*get_thread()), >> method_("GET"), >> err_("") >> { >> @@ -287,6 +290,7 @@ CHNetDelegate::~CHNetDelegate(void) >> r->PostTask(FROM_HERE, base::BindOnce(CHNetDelegateDestruct, &url_req_, >> &url_req_ctx_, &sig)); >> sig.Wait(); >> + put_thread(&thread_); >> } > > if @url_req_ and @url_req_ctx_ are both nullptr, this put_thread() > won't be called and we have a ref count leak Yes, you're right. Will fix it in the v2 revision. >> template <typename T, typename... Types> >> @@ -629,6 +633,81 @@ static uint32_t g_max_ch_thpool; >> static std::mutex g_thpool_lock_; >> static struct ch_thpool **g_thpool; >> >> + >> +static base::Thread *get_thread(void) >> +{ >> + const uint32_t max_ch_thpool = g_max_ch_thpool; >> + const uint32_t nr_ref_split = 2048; >> + struct ch_thpool **thp; >> + struct ch_thpool *ret = nullptr; >> + struct ch_thpool *tmp; >> + uint32_t min_ref_idx; >> + uint32_t min_ref; >> + uint32_t i; >> + >> + g_thpool_lock_.lock(); >> + thp = g_thpool; >> + if (!thp) { >> + g_thpool_lock_.unlock(); >> + return nullptr; >> + } > > in what situation @thp can be nullptr? When the chnet_global_destroy() is called. >> + tmp = thp[0]; >> + if (!tmp) { >> + ret = new struct ch_thpool; >> + ret->idx_ = 0; >> + thp[0] = ret; >> + goto out; >> + } >> + >> + min_ref = tmp->ref_count_; >> + min_ref_idx = 0; >> + for (i = 1; i < max_ch_thpool; i++) { >> + uint32_t ref; >> + >> + tmp = thp[i]; >> + if (!tmp) { >> + ret = new struct ch_thpool; >> + ret->idx_ = i; >> + thp[i] = ret; >> + goto out; >> + } >> + >> + ref = tmp->ref_count_; >> + if (ref < nr_ref_split) { >> + ret = tmp; >> + break; >> + } >> + >> + if (ref < min_ref) { >> + min_ref = ref; >> + min_ref_idx = i; >> + } >> + } >> + >> + if (!ret) >> + ret = thp[min_ref_idx]; >> + >> +out: >> + ret->ref_count_++; >> + g_thpool_lock_.unlock(); >> + return &ret->thread_; >> +} > > this unlock() call will behave as a full memory barrier for that > @ref_count_ increment, is this really needed? you can have the > increment after unlock() tho No, the ref_count needs to be protected by a mutex. Otherwise, we have a use-after-free bug. Possible UAF scenario: Thread1 Thread2 ---- ------- --> get_thread() lock() unlock() --> put_thread() # preempted away lock() decrement delete unlock() increment # UAF!!! return return -- Ammar Faizi ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 4:54 ` Ammar Nofan Faizi @ 2022-08-29 5:17 ` Alviro Iskandar Setiawan 2022-08-29 5:24 ` Alviro Iskandar Setiawan 0 siblings, 1 reply; 13+ messages in thread From: Alviro Iskandar Setiawan @ 2022-08-29 5:17 UTC (permalink / raw) To: Ammar Nofan Faizi Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On Mon, Aug 29, 2022 at 11:54 AM Ammar Nofan Faizi wrote: > On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: >> On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >>> @@ -251,7 +254,7 @@ net::DefineNetworkTrafficAnnotation("CHNetDelegate", R"( >>> })"); >>> >>> CHNetDelegate::CHNetDelegate(void): >>> - thread_("chromium_thread"), >>> + thread_(*get_thread()), >>> method_("GET"), >>> err_("") >>> { >>> @@ -287,6 +290,7 @@ CHNetDelegate::~CHNetDelegate(void) >>> r->PostTask(FROM_HERE, base::BindOnce(CHNetDelegateDestruct, &url_req_, >>> &url_req_ctx_, &sig)); >>> sig.Wait(); >>> + put_thread(&thread_); >>> } >> >> if @url_req_ and @url_req_ctx_ are both nullptr, this put_thread() >> won't be called and we have a ref count leak > > Yes, you're right. Will fix it in the v2 revision. > >>> template <typename T, typename... Types> >>> @@ -629,6 +633,81 @@ static uint32_t g_max_ch_thpool; >>> static std::mutex g_thpool_lock_; >>> static struct ch_thpool **g_thpool; >>> >>> + >>> +static base::Thread *get_thread(void) >>> +{ >>> + const uint32_t max_ch_thpool = g_max_ch_thpool; >>> + const uint32_t nr_ref_split = 2048; >>> + struct ch_thpool **thp; >>> + struct ch_thpool *ret = nullptr; >>> + struct ch_thpool *tmp; >>> + uint32_t min_ref_idx; >>> + uint32_t min_ref; >>> + uint32_t i; >>> + >>> + g_thpool_lock_.lock(); >>> + thp = g_thpool; >>> + if (!thp) { >>> + g_thpool_lock_.unlock(); >>> + return nullptr; >>> + } >> >> in what situation @thp can be nullptr? > > When the chnet_global_destroy() is called. > >>> + tmp = thp[0]; >>> + if (!tmp) { >>> + ret = new struct ch_thpool; >>> + ret->idx_ = 0; >>> + thp[0] = ret; >>> + goto out; >>> + } >>> + >>> + min_ref = tmp->ref_count_; >>> + min_ref_idx = 0; >>> + for (i = 1; i < max_ch_thpool; i++) { >>> + uint32_t ref; >>> + >>> + tmp = thp[i]; >>> + if (!tmp) { >>> + ret = new struct ch_thpool; >>> + ret->idx_ = i; >>> + thp[i] = ret; >>> + goto out; >>> + } >>> + >>> + ref = tmp->ref_count_; >>> + if (ref < nr_ref_split) { >>> + ret = tmp; >>> + break; >>> + } >>> + >>> + if (ref < min_ref) { >>> + min_ref = ref; >>> + min_ref_idx = i; >>> + } >>> + } >>> + >>> + if (!ret) >>> + ret = thp[min_ref_idx]; >>> + >>> +out: >>> + ret->ref_count_++; >>> + g_thpool_lock_.unlock(); >>> + return &ret->thread_; >>> +} >> >> this unlock() call will behave as a full memory barrier for that >> @ref_count_ increment, is this really needed? you can have the >> increment after unlock() tho > > No, the ref_count needs to be protected by a mutex. Otherwise, we > have a use-after-free bug. > > Possible UAF scenario: > > Thread1 Thread2 > ---- ------- > --> get_thread() > lock() > unlock() --> put_thread() > # preempted away lock() > decrement > delete > unlock() > increment # UAF!!! return > return ic ic, i understand now, i didn't see that coming tq for explaining tq -- Viro ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 5:17 ` Alviro Iskandar Setiawan @ 2022-08-29 5:24 ` Alviro Iskandar Setiawan 2022-08-29 5:29 ` Ammar Nofan Faizi 0 siblings, 1 reply; 13+ messages in thread From: Alviro Iskandar Setiawan @ 2022-08-29 5:24 UTC (permalink / raw) To: Ammar Nofan Faizi Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On Mon, Aug 29, 2022 at 12:17 PM Alviro Iskandar Setiawan wrote: > On Mon, Aug 29, 2022 at 11:54 AM Ammar Nofan Faizi wrote: >> On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: >>> On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >>>> +static base::Thread *get_thread(void) >>>> +{ >>>> + const uint32_t max_ch_thpool = g_max_ch_thpool; >>>> + const uint32_t nr_ref_split = 2048; >>>> + struct ch_thpool **thp; >>>> + struct ch_thpool *ret = nullptr; >>>> + struct ch_thpool *tmp; >>>> + uint32_t min_ref_idx; >>>> + uint32_t min_ref; >>>> + uint32_t i; >>>> + >>>> + g_thpool_lock_.lock(); >>>> + thp = g_thpool; >>>> + if (!thp) { >>>> + g_thpool_lock_.unlock(); >>>> + return nullptr; >>>> + } >>> >>> in what situation @thp can be nullptr? >> >> When the chnet_global_destroy() is called. btw, i missed a case on the chnet construction, you do thread_(*get_thread()), if get_thread() returns a nullptr, the @thread_ will become a reference to a nullptr, it will definitely break, how do you handle this? tq -- Viro ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 5:24 ` Alviro Iskandar Setiawan @ 2022-08-29 5:29 ` Ammar Nofan Faizi 2022-08-29 5:38 ` Alviro Iskandar Setiawan 0 siblings, 1 reply; 13+ messages in thread From: Ammar Nofan Faizi @ 2022-08-29 5:29 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On 8/29/22 12:24 PM, Alviro Iskandar Setiawan wrote: > On Mon, Aug 29, 2022 at 12:17 PM Alviro Iskandar Setiawan wrote: >> On Mon, Aug 29, 2022 at 11:54 AM Ammar Nofan Faizi wrote: >>> On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: >>>> On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >>>>> +static base::Thread *get_thread(void) >>>>> +{ >>>>> + const uint32_t max_ch_thpool = g_max_ch_thpool; >>>>> + const uint32_t nr_ref_split = 2048; >>>>> + struct ch_thpool **thp; >>>>> + struct ch_thpool *ret = nullptr; >>>>> + struct ch_thpool *tmp; >>>>> + uint32_t min_ref_idx; >>>>> + uint32_t min_ref; >>>>> + uint32_t i; >>>>> + >>>>> + g_thpool_lock_.lock(); >>>>> + thp = g_thpool; >>>>> + if (!thp) { >>>>> + g_thpool_lock_.unlock(); >>>>> + return nullptr; >>>>> + } >>>> >>>> in what situation @thp can be nullptr? >>> >>> When the chnet_global_destroy() is called. > > btw, i missed a case on the chnet construction, you do > > thread_(*get_thread()), > > if get_thread() returns a nullptr, the @thread_ will become a > reference to a nullptr, it will definitely break, how do you handle > this? I don't think we need to handle that. If @thp ever be a nullptr in a get_thread() call, then it's a misuse of the API. We must never call get_thread() after chnet_global_destroy() is called. It doesn't make much sense to have this situation. -- Ammar Faizi ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 5:29 ` Ammar Nofan Faizi @ 2022-08-29 5:38 ` Alviro Iskandar Setiawan 2022-08-29 5:48 ` Ammar Nofan Faizi 0 siblings, 1 reply; 13+ messages in thread From: Alviro Iskandar Setiawan @ 2022-08-29 5:38 UTC (permalink / raw) To: Ammar Nofan Faizi Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On Mon, Aug 29, 2022 at 12:29 PM Ammar Nofan Faizi wrote: > On 8/29/22 12:24 PM, Alviro Iskandar Setiawan wrote: >> On Mon, Aug 29, 2022 at 12:17 PM Alviro Iskandar Setiawan wrote: >>> On Mon, Aug 29, 2022 at 11:54 AM Ammar Nofan Faizi wrote: >>>> On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: >>>>> On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >>>>>> +static base::Thread *get_thread(void) >>>>>> +{ >>>>>> + const uint32_t max_ch_thpool = g_max_ch_thpool; >>>>>> + const uint32_t nr_ref_split = 2048; >>>>>> + struct ch_thpool **thp; >>>>>> + struct ch_thpool *ret = nullptr; >>>>>> + struct ch_thpool *tmp; >>>>>> + uint32_t min_ref_idx; >>>>>> + uint32_t min_ref; >>>>>> + uint32_t i; >>>>>> + >>>>>> + g_thpool_lock_.lock(); >>>>>> + thp = g_thpool; >>>>>> + if (!thp) { >>>>>> + g_thpool_lock_.unlock(); >>>>>> + return nullptr; >>>>>> + } >>>>> >>>>> in what situation @thp can be nullptr? >>>> >>>> When the chnet_global_destroy() is called. >> >> btw, i missed a case on the chnet construction, you do >> >> thread_(*get_thread()), >> >> if get_thread() returns a nullptr, the @thread_ will become a >> reference to a nullptr, it will definitely break, how do you handle >> this? > > I don't think we need to handle that. If @thp ever be a nullptr in a > get_thread() call, then it's a misuse of the API. We must never call > get_thread() after chnet_global_destroy() is called. It doesn't make > much sense to have this situation. if this should never happen, then why do you do a null check there? your null check doesn't make much sense too! why are we returning nullptr if we exactly know that the caller will randomly fault if that happens, i can't accept your reasoning -- Viro ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 5:38 ` Alviro Iskandar Setiawan @ 2022-08-29 5:48 ` Ammar Nofan Faizi 2022-08-29 6:01 ` Alviro Iskandar Setiawan 0 siblings, 1 reply; 13+ messages in thread From: Ammar Nofan Faizi @ 2022-08-29 5:48 UTC (permalink / raw) To: Alviro Iskandar Setiawan Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On 8/29/22 12:38 PM, Alviro Iskandar Setiawan wrote: > On Mon, Aug 29, 2022 at 12:29 PM Ammar Nofan Faizi wrote: >> On 8/29/22 12:24 PM, Alviro Iskandar Setiawan wrote: >>> On Mon, Aug 29, 2022 at 12:17 PM Alviro Iskandar Setiawan wrote: >>>> On Mon, Aug 29, 2022 at 11:54 AM Ammar Nofan Faizi wrote: >>>>> On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: >>>>>> On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >>>>>>> +static base::Thread *get_thread(void) >>>>>>> +{ >>>>>>> + const uint32_t max_ch_thpool = g_max_ch_thpool; >>>>>>> + const uint32_t nr_ref_split = 2048; >>>>>>> + struct ch_thpool **thp; >>>>>>> + struct ch_thpool *ret = nullptr; >>>>>>> + struct ch_thpool *tmp; >>>>>>> + uint32_t min_ref_idx; >>>>>>> + uint32_t min_ref; >>>>>>> + uint32_t i; >>>>>>> + >>>>>>> + g_thpool_lock_.lock(); >>>>>>> + thp = g_thpool; >>>>>>> + if (!thp) { >>>>>>> + g_thpool_lock_.unlock(); >>>>>>> + return nullptr; >>>>>>> + } >>>>>> >>>>>> in what situation @thp can be nullptr? >>>>> >>>>> When the chnet_global_destroy() is called. >>> >>> btw, i missed a case on the chnet construction, you do >>> >>> thread_(*get_thread()), >>> >>> if get_thread() returns a nullptr, the @thread_ will become a >>> reference to a nullptr, it will definitely break, how do you handle >>> this? >> >> I don't think we need to handle that. If @thp ever be a nullptr in a >> get_thread() call, then it's a misuse of the API. We must never call >> get_thread() after chnet_global_destroy() is called. It doesn't make >> much sense to have this situation. > > if this should never happen, then why do you do a null check there? > your null check doesn't make much sense too! why are we returning > nullptr if we exactly know that the caller will randomly fault if that > happens, i can't accept your reasoning OK. Fine, let's omit that nullptr return. To assert that path, we perform a CHECK() assertion, so it will just crash the whole program with a full call traces if the contract is violated. This way also makes debugging process easier. Are you happy with this change? Incremental is: diff --git a/chnet/chnet.cc b/chnet/chnet.cc index 782e4fe..fc536d2 100644 --- a/chnet/chnet.cc +++ b/chnet/chnet.cc @@ -647,10 +647,7 @@ static base::Thread *get_thread(void) g_thpool_lock_.lock(); thp = g_thpool; - if (!thp) { - g_thpool_lock_.unlock(); - return nullptr; - } + CHECK(thp); tmp = thp[0]; if (!tmp) { -- Ammar Faizi ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function 2022-08-29 5:48 ` Ammar Nofan Faizi @ 2022-08-29 6:01 ` Alviro Iskandar Setiawan 0 siblings, 0 replies; 13+ messages in thread From: Alviro Iskandar Setiawan @ 2022-08-29 6:01 UTC (permalink / raw) To: Ammar Nofan Faizi Cc: Ammar Faizi, Muhammad Rizki, Kanna Scarlet, GNU/Weeb Mailing List On Mon, Aug 29, 2022 at 12:48 PM Ammar Nofan Faizi wrote: > On 8/29/22 12:38 PM, Alviro Iskandar Setiawan wrote: >> On Mon, Aug 29, 2022 at 12:29 PM Ammar Nofan Faizi wrote: >>> On 8/29/22 12:24 PM, Alviro Iskandar Setiawan wrote: >>>> On Mon, Aug 29, 2022 at 12:17 PM Alviro Iskandar Setiawan wrote: >>>>> On Mon, Aug 29, 2022 at 11:54 AM Ammar Nofan Faizi wrote: >>>>>> On 8/29/22 11:41 AM, Alviro Iskandar Setiawan wrote: >>>>>>> On Mon, Aug 29, 2022 at 8:11 AM Ammar Faizi wrote: >>>>>>>> +static base::Thread *get_thread(void) >>>>>>>> +{ >>>>>>>> + const uint32_t max_ch_thpool = g_max_ch_thpool; >>>>>>>> + const uint32_t nr_ref_split = 2048; >>>>>>>> + struct ch_thpool **thp; >>>>>>>> + struct ch_thpool *ret = nullptr; >>>>>>>> + struct ch_thpool *tmp; >>>>>>>> + uint32_t min_ref_idx; >>>>>>>> + uint32_t min_ref; >>>>>>>> + uint32_t i; >>>>>>>> + >>>>>>>> + g_thpool_lock_.lock(); >>>>>>>> + thp = g_thpool; >>>>>>>> + if (!thp) { >>>>>>>> + g_thpool_lock_.unlock(); >>>>>>>> + return nullptr; >>>>>>>> + } >>>>>>> >>>>>>> in what situation @thp can be nullptr? >>>>>> >>>>>> When the chnet_global_destroy() is called. >>>> >>>> btw, i missed a case on the chnet construction, you do >>>> >>>> thread_(*get_thread()), >>>> >>>> if get_thread() returns a nullptr, the @thread_ will become a >>>> reference to a nullptr, it will definitely break, how do you handle >>>> this? >>> >>> I don't think we need to handle that. If @thp ever be a nullptr in a >>> get_thread() call, then it's a misuse of the API. We must never call >>> get_thread() after chnet_global_destroy() is called. It doesn't make >>> much sense to have this situation. >> >> if this should never happen, then why do you do a null check there? >> your null check doesn't make much sense too! why are we returning >> nullptr if we exactly know that the caller will randomly fault if that >> happens, i can't accept your reasoning > > OK. > > Fine, let's omit that nullptr return. To assert that path, we perform a > CHECK() assertion, so it will just crash the whole program with a full > call traces if the contract is violated. This way also makes debugging > process easier. Are you happy with this change? > > Incremental is: > > diff --git a/chnet/chnet.cc b/chnet/chnet.cc > index 782e4fe..fc536d2 100644 > --- a/chnet/chnet.cc > +++ b/chnet/chnet.cc > @@ -647,10 +647,7 @@ static base::Thread *get_thread(void) > > g_thpool_lock_.lock(); > thp = g_thpool; > - if (!thp) { > - g_thpool_lock_.unlock(); > - return nullptr; > - } > + CHECK(thp); > > tmp = thp[0]; > if (!tmp) { oc, ack, it looks fine to me now tq -- Viro ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2022-08-29 6:02 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2022-08-29 1:11 [RFC PATCH v1 0/2] Fixed number of chromium workers Ammar Faizi 2022-08-29 1:11 ` [RFC PATCH v1 1/2] chnet: Prepare global struct ch_thpool array Ammar Faizi 2022-08-29 4:21 ` Alviro Iskandar Setiawan 2022-08-29 4:47 ` Ammar Nofan Faizi 2022-08-29 1:11 ` [RFC PATCH v1 2/2] chnet: Implement `get_thread()` and `put_thread()` function Ammar Faizi 2022-08-29 4:41 ` Alviro Iskandar Setiawan 2022-08-29 4:54 ` Ammar Nofan Faizi 2022-08-29 5:17 ` Alviro Iskandar Setiawan 2022-08-29 5:24 ` Alviro Iskandar Setiawan 2022-08-29 5:29 ` Ammar Nofan Faizi 2022-08-29 5:38 ` Alviro Iskandar Setiawan 2022-08-29 5:48 ` Ammar Nofan Faizi 2022-08-29 6:01 ` Alviro Iskandar Setiawan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox