pw_rpc: Don't hold the RPC lock when calling AcquireBuffer

This updates RPC calls to release their buffer before calling
Channel::AcquireBuffer to prevent potential deadlocks if the
ChannelOutput has its own locking.

Change-Id: Ia9ee156ba3ddf48e8e5bb445549e7a89f45e5885
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/77960
Pigweed-Auto-Submit: Alexei Frolov <frolv@google.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_rpc/call.cc b/pw_rpc/call.cc
index 13e3dea..b49e900 100644
--- a/pw_rpc/call.cc
+++ b/pw_rpc/call.cc
@@ -112,16 +112,30 @@
   return SendPacket(type, response, status);
 }
 
-ByteSpan Call::PayloadBufferLocked() {
+ByteSpan Call::PayloadBuffer() {
+  rpc_lock().lock();
+
   // Only allow having one active buffer at a time.
   if (response_.empty()) {
-    response_ = channel().AcquireBuffer();
+    Channel& c = channel();
+    rpc_lock().unlock();
+
+    // Don't call AcquireBuffer with rpc_lock() held, as this may cause deadlock
+    // if the channel is also protected by a mutex.
+    Channel::OutputBuffer buffer = c.AcquireBuffer();
+
+    rpc_lock().lock();
+    response_ = std::move(buffer);
   }
 
   // The packet type is only used to size the payload buffer.
   // TODO(pwrev/506): Replace the packet header calculation with a constant
   //     rather than creating a packet.
-  return response_.payload(MakePacket(PacketType::CLIENT_STREAM, {}));
+  ByteSpan buffer =
+      response_.payload(MakePacket(PacketType::CLIENT_STREAM, {}));
+  rpc_lock().unlock();
+
+  return buffer;
 }
 
 Status Call::Write(ConstByteSpan payload) {
@@ -139,7 +153,9 @@
   const Packet packet = MakePacket(type, payload, status);
 
   if (!buffer().Contains(payload)) {
-    ByteSpan buffer = PayloadBufferLocked();
+    rpc_lock().unlock();
+    ByteSpan buffer = PayloadBuffer();
+    rpc_lock().lock();
 
     if (payload.size() > buffer.size()) {
       ReleasePayloadBufferLocked();
diff --git a/pw_rpc/public/pw_rpc/internal/call.h b/pw_rpc/public/pw_rpc/internal/call.h
index 1cdaab8..263bf47 100644
--- a/pw_rpc/public/pw_rpc/internal/call.h
+++ b/pw_rpc/public/pw_rpc/internal/call.h
@@ -160,10 +160,7 @@
 
   // Acquires a buffer into which to write a payload or returns a previously
   // acquired buffer. The Call MUST be active when this is called!
-  [[nodiscard]] ByteSpan PayloadBuffer() PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return PayloadBufferLocked();
-  }
+  [[nodiscard]] ByteSpan PayloadBuffer() PW_LOCKS_EXCLUDED(rpc_lock());
 
   // Releases the buffer without sending a packet.
   void ReleasePayloadBuffer() PW_LOCKS_EXCLUDED(rpc_lock()) {
@@ -280,8 +277,6 @@
        MethodType type,
        CallType call_type);
 
-  [[nodiscard]] ByteSpan PayloadBufferLocked()
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
   void ReleasePayloadBufferLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
 
   Packet MakePacket(PacketType type,