Complete channel initialization in the event loop

If addLast is called outside an event loop, then the handler is added in
the 'pending' state. Sending an event to the pipeline does not send it
to the last handler, but to the last _non-pending_ handler. We therefore
have to make sure to involve the event loop _before_ marking the channel
as ready to be used.

Thanks to @Reflexe who pointed me in the right direction.

Fixes #7464.

Closes #7509.

GERRIT_CHANGE_ID=
PiperOrigin-RevId: 235184010
diff --git a/src/main/java/com/google/devtools/build/lib/remote/blobstore/http/HttpBlobStore.java b/src/main/java/com/google/devtools/build/lib/remote/blobstore/http/HttpBlobStore.java
index 1b84c96..6c0c623 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/blobstore/http/HttpBlobStore.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/blobstore/http/HttpBlobStore.java
@@ -271,7 +271,16 @@
                   p.addLast(new HttpUploadHandler(creds));
                 }
 
-                channelReady.setSuccess(ch);
+                if (!ch.eventLoop().inEventLoop()) {
+                  // If addLast is called outside an event loop, then it doesn't complete until the
+                  // event loop is run again. In that case, a message sent to the last handler gets
+                  // delivered to the last non-pending handler, which will most likely end up
+                  // throwing UnsupportedMessageTypeException. Therefore, we only complete the
+                  // promise in the event loop.
+                  ch.eventLoop().execute(() -> channelReady.setSuccess(ch));
+                } else {
+                  channelReady.setSuccess(ch);
+                }
               } catch (Throwable t) {
                 channelReady.setFailure(t);
               }
@@ -332,7 +341,16 @@
                   p.addLast(new HttpDownloadHandler(creds));
                 }
 
-                channelReady.setSuccess(ch);
+                if (!ch.eventLoop().inEventLoop()) {
+                  // If addLast is called outside an event loop, then it doesn't complete until the
+                  // event loop is run again. In that case, a message sent to the last handler gets
+                  // delivered to the last non-pending handler, which will most likely end up
+                  // throwing UnsupportedMessageTypeException. Therefore, we only complete the
+                  // promise in the event loop.
+                  ch.eventLoop().execute(() -> channelReady.setSuccess(ch));
+                } else {
+                  channelReady.setSuccess(ch);
+                }
               } catch (Throwable t) {
                 channelReady.setFailure(t);
               }