Skip to content
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

HttpResponse.of(ResponseHeaders,Publisher<HttpObject>) #3237

Merged
merged 10 commits into from
Feb 1, 2021

Conversation

tumile
Copy link
Contributor

@tumile tumile commented Dec 18, 2020

Motivation:
Provide an easy way to build a HttpResponse with ResponseHeaders and a Publisher<HttpObject>.

Modifications:
Added PrependingPublisher that can prepend an object to a Publisher of the same type.

Result:
Closes #3089

@tumile
Copy link
Contributor Author

tumile commented Dec 18, 2020

Not sure why this happens, it seems irrelevant 😄
https://ci.appveyor.com/project/line/armeria/builds/36905264/job/6pv80j0idqg0g8vd#L638

11:41:07.703 [Thread-3] DEBUG c.l.armeria.client.ClientFactory - Closed the default client factories
TomcatServiceDestroyedConnectorTest > serviceUnavailableAfterConnectorIsDestroyed() FAILED
    java.lang.AssertionError: 
    Expecting:
     <200 OK>
    and actual:
     <503 Service Unavailable>
    to refer to the same object
        at com.linecorp.armeria.server.tomcat.TomcatServiceDestroyedConnectorTest.serviceUnavailableAfterConnectorIsDestroyed(TomcatServiceDestroyedConnectorTest.java:55)

@minwoox
Copy link
Member

minwoox commented Dec 18, 2020

Please forget about it. It's a flaky test. 😅

@trustin trustin added this to the 1.4.0 milestone Dec 24, 2020
Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Sorry for a late review, @tumile. Have a great holiday ☃️

@codecov
Copy link

codecov bot commented Dec 24, 2020

Codecov Report

Merging #3237 (39302b8) into master (a62e8d8) will increase coverage by 0.01%.
The diff coverage is 56.96%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #3237      +/-   ##
============================================
+ Coverage     74.11%   74.13%   +0.01%     
- Complexity    12873    13244     +371     
============================================
  Files          1119     1158      +39     
  Lines         48746    50367    +1621     
  Branches       6229     6442     +213     
============================================
+ Hits          36129    37339    +1210     
- Misses         9437     9741     +304     
- Partials       3180     3287     +107     
Impacted Files Coverage Δ Complexity Δ
...rp/armeria/client/ClientRequestContextWrapper.java 0.00% <ø> (ø) 0.00 <0.00> (ø)
...linecorp/armeria/common/RequestContextWrapper.java 7.50% <0.00%> (+7.50%) 1.00 <0.00> (+1.00)
...p/armeria/server/ServiceRequestContextWrapper.java 0.00% <ø> (ø) 0.00 <0.00> (ø)
.../linecorp/armeria/server/tomcat/TomcatService.java 58.74% <28.57%> (-2.11%) 32.00 <2.00> (+2.00) ⬇️
...va/com/linecorp/armeria/internal/consul/Check.java 33.33% <33.33%> (ø) 27.00 <27.00> (?)
...inecorp/armeria/internal/consul/CatalogClient.java 50.00% <54.16%> (ø) 9.00 <9.00> (?)
...ia/internal/common/stream/PrependingPublisher.java 55.71% <55.71%> (ø) 2.00 <2.00> (?)
.../armeria/server/consul/ConsulUpdatingListener.java 58.20% <56.92%> (ø) 12.00 <12.00> (?)
...linecorp/armeria/internal/consul/HealthClient.java 60.97% <60.97%> (ø) 6.00 <6.00> (?)
...eria/client/consul/ConsulEndpointGroupBuilder.java 76.00% <66.66%> (ø) 8.00 <4.00> (?)
... and 177 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ed83cab...39302b8. Read the comment docs.

@tumile
Copy link
Contributor Author

tumile commented Dec 26, 2020

@trustin Hope you and the team have a great holiday too! And hope that Armeria has another year of wonderful growth!

But to start it out we have another npmInstall failure 🤣. Just out of curiosity why are we not keeping site as a separate repository like https://github.com/spring-io/sagan?

@trustin
Copy link
Member

trustin commented Dec 28, 2020

Just out of curiosity why are we not keeping site as a separate repository like spring-io/sagan?

Sorry about the inconveniences. 😢 It's because we want to refer to some type information in our documentation.

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments that violate Reactive Streams Specifications. Those things could be checked by Reactive Streams TCK.

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM, once @minwoox's comments are addressed.

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! @tumile

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are almost there. 😄

final long demand = this.demand;
final long newDemand = demand >= Long.MAX_VALUE - n ? Long.MAX_VALUE : demand + n;
if (demandUpdater.compareAndSet(this, demand, newDemand)) {
if (demand > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we just remove this condition?

Copy link
Contributor Author

@tumile tumile Jan 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We check this because if the previous demand is non-zero then there is already an ongoing request and we stop, otherwise it could lead to unbounded recursion request -> onNext -> request ->.... If I remove this rule 3.3 will fail 😄 .

Copy link
Member

@minwoox minwoox Jan 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops could you elaborate more on request -> onNext -> request ->..., please?
If we just return here, there's a chance the upstream.request(...) is not called.
Let's say that thread A calls Subscription.request(2) twice.
When the second Subscription.request(2) is called and if it's at line 84.
Meanwhile, thread B is trying demandUpdater.compareAndSet(this, demand, 0) at line 138.
If the thread B fails to compareAndSet, then thread A might set the demand as 3 and there's no more upstream.request(demand); call

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the race condition be solved by adding a for loop in onSubscribe()?

// Keep retrying to send a request signal until demain is zero
for (;;) {
    final long demand = this.demand;
    if (demand == 0) {
        break;
    }
    if (demandUpdater.compareAndSet(this, demand, 0)) {
        subscription.request(demand);
    }
}

Copy link
Contributor Author

@tumile tumile Jan 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minwoox thanks for the detailed example. There's indeed a race condition. I think @ikhoon's solution should resolve that. Yet if (demand > 0) is for guarding stack overflow, so it should still be here? 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably might get this wrong, 😄 but if's it's for guarding a stack overflow,
what happens when the thread calls upstream.request(demand) here?https://github.com/line/armeria/pull/3237/files#diff-a8dfc683de291a0ccbc6e0ab3c2bae463e462050ac18beca18b3adf6c14c1b5fR110
Isn't it the same?

Copy link
Contributor Author

@tumile tumile Feb 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I get what you say 😄 . The condition is there to satisfy the TCK, which assumes the calls are on the same thread. In fact rule 3.3 just "recommended" the stack depth to be 1. I don't think in practice we would overflow by any means, especially in Armeria.

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, thanks a lot for your patience, @tumile. 😄

@trustin trustin changed the title HttpResponse.of(ResponseHeaders,Publisher<HttpData>) HttpResponse.of(ResponseHeaders,Publisher<HttpObject>) Feb 1, 2021
@trustin trustin merged commit 4d0aa33 into line:master Feb 1, 2021
@tumile
Copy link
Contributor Author

tumile commented Feb 1, 2021

Thanks everyone! I didn't think this could turn out to be so complicated. Couldn't have finished it without your support 🙇

@minwoox
Copy link
Member

minwoox commented Feb 1, 2021

I didn't think this could turn out to be so complicated.

Neither did I. 🤣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

HttpResponse.of(ResponseHeaders,Publisher<HttpData>)
4 participants