Reduce thread contention for pooled CRYPTO_BUFFERs

We previously locked all CRYPTO_BUFFER_frees of pooled buffers, but we
only need to lock it when we observe a reference count of zero. When we
see zero, we then lock to synchronize with any threads that might try to
upgrade our weak reference.

Change-Id: I3c21f8a7cc3091457560cdfcd60c00159ba26400
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/90155
Reviewed-by: Xiangfei Ding <xfding@google.com>
Reviewed-by: Rudolf Polzer <rpolzer@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/pool/pool.cc b/crypto/pool/pool.cc
index 3c47143..ddc37fa 100644
--- a/crypto/pool/pool.cc
+++ b/crypto/pool/pool.cc
@@ -89,24 +89,24 @@
 }
 
 void CryptoBuffer::UpRefInternal() {
-  // This is safe in the case that |buf->pool| is NULL because it's just
-  // standard reference counting in that case.
-  //
-  // This is also safe if |buf->pool| is non-NULL because, if it were racing
-  // with |CRYPTO_BUFFER_free| then the two callers must have independent
-  // references already and so the reference count will never hit zero.
+  // Note that, unlike standard ref-counting, it is possible that |references_|
+  // is zero and this function increases the reference count to one. See
+  // DecRefInternal below.
   CRYPTO_refcount_inc(&references_);
 }
 
 void CryptoBuffer::DecRefInternal() {
-  // If there is a pool, decrementing the refcount must synchronize with it.
-  if (pool_handle_ == nullptr) {
-    if (!CRYPTO_refcount_dec_and_test_zero(&references_)) {
-      return;
-    }
-  } else {
+  if (!CRYPTO_refcount_dec_and_test_zero(&references_)) {
+    return;
+  }
+
+  if (pool_handle_ != nullptr) {
+    // Although the reference count is zero, there is still a weak reference
+    // from the pool. Some other thread may concurrently upgrade the weak
+    // reference to a strong reference. Synchronize with the pool and see if
+    // this has happened.
     MutexWriteLock lock(&pool_handle_->lock_);
-    if (!CRYPTO_refcount_dec_and_test_zero(&references_)) {
+    if (references_.load() != 0) {
       return;
     }
 
@@ -127,6 +127,10 @@
         (void)found;
       }
     }
+
+    // The weak reference is now removed. When we unlock the pool handle,
+    // another thread cannot upgrade to a strong reference, so we can free the
+    // buffer.
   }
 
   this->~CryptoBuffer();
@@ -178,6 +182,8 @@
       duplicate = nullptr;
     }
     if (duplicate != nullptr) {
+      // UpRef must be called under the lock. This converts the pool's weak
+      // reference to a strong reference. See CryptoBuffer::DecRefInternal.
       return UpRef(duplicate);
     }
   }
@@ -195,6 +201,8 @@
     duplicate = nullptr;
   }
   if (duplicate != nullptr) {
+    // UpRef must be called under the lock. This converts the pool's weak
+    // reference to a strong reference. See CryptoBuffer::DecRefInternal.
     return UpRef(duplicate);
   }