-
Notifications
You must be signed in to change notification settings - Fork 921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SurroundingPublisher
and HttpResponse.of(headers, pub, trailers)
#4727
Conversation
1936c24
to
3912ed5
Compare
core/src/test/java/com/linecorp/armeria/common/HttpResponseBuilderTest.java
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, looks good! 😄
Left some suggestions.
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/AppendingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/PublisherBasedHttpResponse.java
Outdated
Show resolved
Hide resolved
📌 Sorry once again for my late 😭 I'll must update within this weekend, until 04/02!! |
AppendingPublisher
and HttpResponse.of(headers, pub, trailers)
SurroundingPublisher
and HttpResponse.of(headers, pub, trailers)
e6742ed
to
23c2bdf
Compare
@minwoox , can I get review? 🙇 |
Let me review this first. Minwoo is working on another issue. |
core/src/main/java/com/linecorp/armeria/common/HttpResponse.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/common/PublisherBasedHttpResponse.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
ab6abc7
to
97e6d9a
Compare
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Outdated
Show resolved
Hide resolved
core/src/main/java/com/linecorp/armeria/internal/common/stream/SurroundingPublisher.java
Show resolved
Hide resolved
5130087
to
97c5b1d
Compare
Hmm I'm chekcing failed test on my local env~ I'll find root cause and fix it soon 🙇 |
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #4727 +/- ##
============================================
+ Coverage 74.30% 74.32% +0.02%
- Complexity 19600 19625 +25
============================================
Files 1682 1683 +1
Lines 72260 72585 +325
Branches 9242 9304 +62
============================================
+ Hits 53695 53952 +257
- Misses 14222 14265 +43
- Partials 4343 4368 +25
☔ View full report in Codecov by Sentry. |
assert upstreamRequested > 0; | ||
if (upstreamRequested < Long.MAX_VALUE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class ChannelSendOperator {
@Override
public void subscribe(Subscriber<? super T> writeSubscriber) {
synchronized (this) {
Assert.state(this.writeSubscriber == null, "Only one write subscriber supported");
this.writeSubscriber = writeSubscriber;
if (this.error != null || this.completed) { // completed: true
this.writeSubscriber.onSubscribe(Operators.emptySubscription());
emitCachedSignals(); // 👈👈
}
private boolean emitCachedSignals() {
T item = this.item;
this.item = null;
if (item != null) {
requiredWriteSubscriber().onNext(item); // 👈👈 although there's no request, onNext cached item!
}
}
@ikhoon , I found that spring's ChannelSendOperator
calls downstream.onNext(item)
without any request on below case!
- ChannelSendOperator.
subscribe()
- ChannelSendOperator is already
completed
- emitCachedSignals()
- downstream.onNext(item) // although there's no request, onNext() is invoked!
"ChannelSendOperator(upstream) can downstream.onNext(item) without any request" -> it sounds weird, but we can't change spring ChannelSendOperator's behavior so I just removed assert upstreamRequested > 0;
on L336 ~! PTAL 🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, changes still look good to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"ChannelSendOperator(upstream) can downstream.onNext(item) without any request"
It might be not related to this PR but the assertion found the bug. Let me add some logs and debug to know why ChannelSendOperator
was completed before subscribe()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ikhoon nim gentle ping 🙇
Can you check debug logs on broken test? I think I still can't check debug logs on CI/CD result.
I want to merge this PR and handle next PRs!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to download artifacts to see debug logs on the summary page of the GitHub Actions job.
Otherwise, let me try to merge #5104 quickly to easily see the test logs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@injae-kim You can now check out the test results on Gradle build scans.
https://github.com/line/armeria/actions/runs/5901062192
https://ge.armeria.dev/s/r2qfke4r6ueky/tests/task/:spring:boot2-webflux-autoconfigure:shadedTest/details/com.linecorp.armeria.spring.web.reactive.MatrixVariablesTest/foo()?top-execution=1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test results on Gradle build scans.
oh it's cool! thanks a lot 🙇 I'll check test result and share to you~!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspected the problem is a bug in ChannelSendOperator
that violates the most important rule 1.1. https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.4/README.md#1.1
So I forked ChannelSendOperator
and modified not to publish the cached item before a request is made. https://github.com/line/armeria/pull/4727/files#r1299525470
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem occurs only in Spring Boot 2 WebFlux. The following command can reproduce it locally.
./gradlew :spring:boot3-webflux-autoconfigure:testClasses :spring:boot2-webflux-autoconfigure:test --tests "*.MatrixVariablesTest"
ChannelSendOperator
implementation has not changed between Spring Boot 2 and Spring Boot 3. The main difference was spring-projects/spring-framework#28398 which avoids collecting Flux
before passing to ChannelSendOperator
. Consequently, WriteBarrier.onComplete()
was not invoked before WriteBarrier.subscribe()
is called and backpressure looks respected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really thanks for your detailed investigation 🙇 If there's something I have to do, please tell me!
|
||
// Forked from https://github.com/spring-projects/spring-framework/blob/1e3099759e2d823b6dd1c0c43895abcbe3e02a12/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java | ||
// and modified at L370 for not publishing the cache item before receiving request(n) from the subscriber. | ||
private final Function<Publisher<T>, Publisher<Void>> writeFunction; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual diff with the upstream code.
--- spring-framework/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java 2023-01-15 20:37:55
+++ armeria/spring/boot3-webflux-autoconfigure/src/main/java/com/linecorp/armeria/spring/web/reactive/ChannelSendOperator.java 2023-08-21 11:37:11
@@ -1,4 +1,19 @@
/*
+ * Copyright 2023 LINE Corporation
+ *
+ * LINE Corporation licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,13 +29,18 @@
* limitations under the License.
*/
-package org.springframework.http.server.reactive;
+package com.linecorp.armeria.spring.web.reactive;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
import reactor.core.CoreSubscriber;
import reactor.core.Scannable;
import reactor.core.publisher.Flux;
@@ -28,11 +48,6 @@
import reactor.core.publisher.Operators;
import reactor.util.context.Context;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.core.io.buffer.DataBufferUtils;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
-
/**
* Given a write function that accepts a source {@code Publisher<T>} to write
* with and returns {@code Publisher<Void>} for the result, this operator helps
@@ -48,6 +63,8 @@
*/
public class ChannelSendOperator<T> extends Mono<Void> implements Scannable {
+ // Forked from https://github.com/spring-projects/spring-framework/blob/1e3099759e2d823b6dd1c0c43895abcbe3e02a12/spring-web/src/main/java/org/springframework/http/server/reactive/ChannelSendOperator.java
+ // and modified at L370 for not publishing the cache item before receiving request(n) from the subscriber.
private final Function<Publisher<T>, Publisher<Void>> writeFunction;
private final Flux<T> source;
@@ -350,7 +367,7 @@
synchronized (this) {
Assert.state(this.writeSubscriber == null, "Only one write subscriber supported");
this.writeSubscriber = writeSubscriber;
- if (this.error != null || this.completed) {
+ if (this.error != null || (this.completed && this.item == null)) {
this.writeSubscriber.onSubscribe(Operators.emptySubscription());
emitCachedSignals();
}
Related issue #4816 ### Motivation: > As a future work of #4727, users might want to dynamically emit the last message depending on whether the upstream publisher completes successfully or exceptionally. - On #4816, it's good to add `StreamMessage.endWith(finalizer)` ### Modifications: - Add `StreamMessage.endWith(finalizer)` ### Result: - Closes #4816 - Now user can dynamically emit the last value depending on whether the `StreamMessage` completes successfully or exceptionally by using `StreamMessage.endWith(finalizer)`
Related issue line#4816 ### Motivation: > As a future work of line#4727, users might want to dynamically emit the last message depending on whether the upstream publisher completes successfully or exceptionally. - On line#4816, it's good to add `StreamMessage.endWith(finalizer)` ### Modifications: - Add `StreamMessage.endWith(finalizer)` ### Result: - Closes line#4816 - Now user can dynamically emit the last value depending on whether the `StreamMessage` completes successfully or exceptionally by using `StreamMessage.endWith(finalizer)`
Related Issue #3959
Motivation:
HttpResponse
#3941, we addedPrependingPublisher
to publishResponseHeaders
and body in aStreamMessage
SurroundingPublisher
to publish body andHTTP trailers
too!Modifications:
SurroundingPublisher
to append trailersHttpResponse.of(header, pub, trailers)
that usingSurroundingPublisher
Result:
HTTP trailers
easily by usingSurroundingPublisher