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
base-http: -> 2.13 #797
base-http: -> 2.13 #797
Conversation
finagle-base-http/src/test/scala/com/twitter/finagle/http/TraceInfoTest.scala
Outdated
Show resolved
Hide resolved
Codecov Report
@@ Coverage Diff @@
## develop #797 +/- ##
===========================================
+ Coverage 71.91% 75.82% +3.91%
===========================================
Files 857 789 -68
Lines 25042 23740 -1302
Branches 1816 1769 -47
===========================================
- Hits 18009 18002 -7
+ Misses 7033 5738 -1295
Continue to review full report at Codecov.
|
Hmm, how would you feel about breaking this up into two commits, one of which does the 2.13 work and one of which depends on the 2.13 one and has the Map changes? In the mean time we can just stub out the Map stuff with trivial implementations (like the Seq one we discussed). Also, @yufangong tested out removing the |
The simplest thing that could possibly work for a backing store for Headers would be a plain mutable map over a string wrapper that compares case-insensitively -- easier and probably faster than the Seq one. It'll cost you an allocation per header. I can do that pretty quickly. Re-evaluating whether something more sophisticated is a net positive can come at a later time. |
e44e738
to
e307f53
Compare
@mosesn this version includes both a trie-backed version and (by default) a hashmap backed version. I don't have the resources to benchmark everything. If you want to split out the trie implementation, you can just leave out **/TrieHeaderMap.scala and **/TrieHeaderMapBase.scala |
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 think this approach is fine, and it's OK to move forward with it. With that said, my preference would probably be to remove the constraints that HeaderMap and CookieMap actually extend scala Maps, unify our 2.12 and 2.13 implementations, and make this a less invasive change for now. The trie-based approach is definitely interesting though, so I'd like to continue exploring that no matter what (separately from the 2.13 upgrade).
What do you think we should do?
def iterated: Iterator[HeaderMap.NameValue] = { | ||
var cur = this | ||
new AbstractIterator[HeaderMap.NameValue](){ | ||
def hasNext = cur != null |
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.
type annos please
var cur = this | ||
new AbstractIterator[HeaderMap.NameValue](){ | ||
def hasNext = cur != null | ||
def next() = { |
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.
type annos please
|
||
// ---- Map/MapLike ----- | ||
|
||
/*final*/ def get(key: String): Option[String] = underlying.synchronized { |
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.
why is this commented out?
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.
There was a comment about that. It got lost I think, I'll bring it back. scala/bug#11749
@@ -16,12 +16,12 @@ class DefaultHeaderMapTest extends AbstractHeaderMapTest with ScalaCheckDrivenPr | |||
final def newHeaderMap(headers: (String, String)*): HeaderMap = DefaultHeaderMap(headers: _*) | |||
|
|||
test("apply()") { |
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.
can we push all of these tests down into AbstractHeaderMapTest?
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 really shouldn't read review comments bottom to top
@@ -3,7 +3,7 @@ package com.twitter.finagle.http | |||
import org.scalacheck.Gen | |||
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks | |||
|
|||
class DefaultHeaderMapTest extends AbstractHeaderMapTest with ScalaCheckDrivenPropertyChecks { | |||
class TrieHeaderMapTest extends AbstractHeaderMapTest with ScalaCheckDrivenPropertyChecks { |
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.
let's make sure we're testing the Map-backed HeaderMap too
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.
Yes, that sounds good. Is there a reason why these tests aren't on AbstractHeaderMapTest
? Should I duplicate them or move them there?
def values: Seq[String] = | ||
if (next == null) value :: Nil | ||
else { | ||
val result = new mutable.ListBuffer[String] += 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.
should we use List.newBuilder here too?
def names: List[String] = | ||
if (next == null) name :: Nil | ||
else { | ||
val result = new mutable.ListBuffer[String] += name |
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.
should we use List.newBuilder here too?
I agree that extending Map doesn't bring much in principle. The problem with no longer extending Map is that you may break downstream: Map is part of the pubic signature and is exposed through request/response as Headers. Is not extending map and possibly breaking downstream worth the benefit of not having to do that weird thing with version-dependent source directories? In the sbt world, I'd cautiously say no: there is very little version-specific code, and it's fairly discoverable. Not sure if the same goes for the pants/monorepo world. It probably also depends on whether any of your internal users use it as a Map. |
We typically use our internal repository as a model for how we expect that people are using our code in the wild, since it gives us a bunch of examples. @yufangong found that there are a pretty limited number of ways that people use Map-ish methods internally, so I think it should be safe to break. @yufangong can you elaborate on what you found in your investigation? I think my bigger concern with the current approach is the sheer number of changes you had to make to get it to work. Right now this is pretty challenging to review–I can't really see what you copied over from before (or changed minimally) and what you changed substantially. |
Just starting to take a look, seems like a lot of work/thought has been put into this. Thanks for digging in, @martijnhoekstra. I think I'm agreeing with @mosesn about breaking up the commit. Is there a smaller and simpler (even if not efficient) replacement implementation for HeaderMap that will allow us to break with HashTable? It looks to me that we'd need to spend a lot more time studying this trie-based approach, and maybe we can do that separately. |
I can think of a couple of things to make the diff here smaller and more manageable. I'm going to propose a few preliminary PR's, and we can see for individual changes whether they work out, or want to go a different direction. That's for all or your time and effort y'all! |
Diff-shrinker 1: #798 |
Diff-shrinker 2: #799 |
Diff-shrinker 3 will then introduce MapBackedHeaderMap on top of diff-shrinker 1 and 2, and then this PR can be rebased on top of that, do the 212/213 compat, and set MapBackedHeaderMap as the default for 2.13 (or for everything). |
Sorry, I missed this. I first try to remove the HashMap Headers extended and leave something as ???, some internal things failed because of that (not compiling errors), so I assume not many models are relying on the HashMap impl. This PR seems now breaking |
This PR shouldn't be breaking. The failed CI is the |
Diff-shrinker 3: #805 |
fec719f
to
71dff8a
Compare
rebased on top of #805 When that gets in, rebase again on develop, which is equivalent to cherry-picking 92d7d3e I'll be on holiday for the next week, and won't have my laptop on me (and have different things on my mind other than rebasing pull requests). @spockz could you maybe see if you can get this moving once #805 is merged? |
71dff8a
to
92d7d3e
Compare
92d7d3e
to
e1cfa70
Compare
rebased. Diff shrunk significantly. Is the builder approach of avoiding Vector and its builders still good? |
e1cfa70
to
f857c0f
Compare
@@ -22,7 +22,7 @@ class MapParamMapTest extends FunSuite { | |||
} | |||
|
|||
test("+") { | |||
val paramMap = MapParamMap() + ("a" -> "1") + ("b" -> "2") + ("a" -> "3") | |||
val paramMap = MapParamMap().setParam("a" -> "1").setParam("b" -> "2").setParam("a" -> "3") |
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.
This is really unfortunate, but +
is final and returns Map
.
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.
HashMap gets around it somehow. I think it's with StrictOptimizedMapOps, but I'm not sure.
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 got mixed up with +=
on mutable.Map
-- +
is not final at all, specifically to allow overriding with a narrower return type.
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.
oh, phew!
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.
phew indeed
8ec7299
to
7d57759
Compare
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.
Note: Interested observer - no input on code, just noticed some whitespace issues (also, almost all new files are missing a final newline)
finagle-base-http/src/main/scala/com/twitter/finagle/http/CookieMap.scala
Outdated
Show resolved
Hide resolved
finagle-base-http/src/main/scala/com/twitter/finagle/http/CookieMap.scala
Outdated
Show resolved
Hide resolved
finagle-base-http/src/main/scala/com/twitter/finagle/http/headers/JTreeMapBackedHeaderMap.scala
Outdated
Show resolved
Hide resolved
Surely, you mean to say that those files don't have spurious final newlines |
Git (and github) are quite clear that the lack of final newlines is not desired ( If it's an intentional choice then sure - but there is a final newline in |
Neither Git, nor Github complains to me. Maybe you have different settings, or different interpretations of the same interface. I added a commit I removing the spurious newline at the end of DefaultHeaderMap for consistencies sake though. |
Running
and similarly for all the other files missing a newline. Note the last line - that's git complaining roughly because of the posix standard. You'll also see it in your diff on github as a crossed red circle with a return arrow. |
I see it mentioning the lack of a newline at the end of the file, but not complaining about it. The Scala spec doesn't refer to POSIX with regards to its lines either. |
fwiw, whether files have newlines at the end of them or not has no bearing on whether they'll be merged in or not. @martijnhoekstra is this ready to rock? should we review it? |
yeah, this is ready to rock @mosesn |
awesome! we're heads down on a tech debt sprint this week, but we'll review your patch next week. thanks so much for going through all of this trouble, I'm pretty excited about where we're going to end up. |
@mosesn great to hear! The rest of finagle-http is pretty review-light. I've tested the commits by @spockz at https://github.com/martijnhoekstra/finagle/pull/1/commits , and as far as I'm concerned they look good to me to be cherry-picked in as well. Can we make that happen within the scope of this PR as well? It would be really nice if we can get finagle-http published this year still. |
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.
Forgive me if my comments don't make a lot of sense: I haven't spent much with the 2.13 yet.
@@ -7,7 +7,7 @@ import com.twitter.finagle.http.HeaderMap | |||
* Mutable, thread-safe [[HeaderMap]] implementation backed by a | |||
* HeaderHash. | |||
*/ | |||
private final class HashBackedHeaderMap extends http.HeaderMap { | |||
private[http] final class HashBackedHeaderMap extends http.HeaderMap { |
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 think the plan is to just get rid of this since the JTree backed header map is a better performer and scala-version agnostic, so hopefully that will cut down some noise.
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 other PR was merged keeping the old as the default, so I figured it was a good idea to keep it here too. @mosesn what's your take on that?
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 think Bryce's proposal makes sense, we're going to swap the default after we finish testing it, after all.
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.
Implemented in caf06dc
protected abstract class ParamMapVersionSpecific extends immutable.Map[String, String] { | ||
def setParam[B >: String](kv: (String, B)): ParamMap | ||
def updated[V1 >: String](key: String, value: V1): ParamMap = setParam(key, value) | ||
|
||
def clearParam(name: String): ParamMap | ||
def removed(name: String): ParamMap = clearParam(name) |
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.
Can setParam
and clearParam
be protected so as not to become part of the public api?
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've made those protected
@@ -128,13 +128,13 @@ class CookieMap private[finagle] (message: Message, cookieCodec: CookieCodec) | |||
this += ((cookie.name, cookie)) | |||
} | |||
|
|||
override def ++=(xs: TraversableOnce[(String, Cookie)]): this.type = { |
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.
This looks like it's replaced with addCookies
, and thus an API change.
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.
in 2.12, ++= comes from the override in CookieMapVersionSpecific
in 2.13, ++= comes from Map
where it is an alias for addAll
which is overridden in CookieMapVersionSpecific
protected abstract class CookieMapVersionSpecific(message: Message, cookieCodec: CookieCodec) { | ||
def this(message: Message) = this(message, CookieMap.cookieCodec) | ||
|
||
def addCookie(cookie: (String, Cookie)): this.type | ||
def addCookies(cookies: IterableOnce[(String, Cookie)]): this.type | ||
def removeCookie(name: String): this.type | ||
def removeCookies(names: IterableOnce[String]): this.type | ||
|
||
def addOne(cookie: (String, Cookie)): this.type = addCookie(cookie) | ||
def subtractOne(name: String): this.type = removeCookie(name) | ||
|
||
} |
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.
Can some of these be protected? And I'm not sure where the addCookies
and removeCookies
are used.
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.
Yeah, those sound like good plans.
f9b36e3
to
caf06dc
Compare
Hey @martijnhoekstra, it looks like there are some conflicts between the branch and develope (DefaultHeaderMap), can you do a rebase? Thank you! |
use strategy common extends versionspecific
caf06dc
to
e305e39
Compare
rebased |
test failures look spurious |
Sorry for the delay, just got back from holiday breaks. |
Calling The difficulty is balancing the requirements of staying on |
Oh so good to know, we neglected 2.13 performance before, this is a good sign for us to start building some perf tools running on 2.12 and 2.13. And yes, the regressions on 2.12 are showing pretty significant perf drops by using builder/result. so we will have to keep the |
@yufangong I created #818 |
****************************************************** PR here: #797 ****************************************************** Problem no 2.13 cross build Solution ~~HeaderMap: The big one. Rework into a trie as not to depend on HashTable's implementation.~~ Other maps: split into 2.12- and 2.13+ versions build: enable 2.13 Result Performance changes in 2.12 vs baseline are a mixed bag. ~~* Create: 2.5 times as fast~~ ~~* Create and add a single element: 1.3 times as fast~~ ~~* Create and add 24 life-like headers: 34% slower~~ ~~* Get one out of 20 random headers: 30% slower~~ ~~* Get one out of 24 life-like headers: 2.98 times as slow (!!)~~ ~~* Foreach: 2.5 times as fast~~ ~~* Iterating keys and values separtely is slow. There is likely still a lot to get there.~~ Signed-off-by: Yufan Gong <yufang@twitter.com> JIRA Issues: CSL-9126 Differential Revision: https://phabricator.twitter.biz/D405663
****************************************************** PR here: #797 ****************************************************** Problem no 2.13 cross build Solution ~~HeaderMap: The big one. Rework into a trie as not to depend on HashTable's implementation.~~ Other maps: split into 2.12- and 2.13+ versions build: enable 2.13 Result Performance changes in 2.12 vs baseline are a mixed bag. ~~* Create: 2.5 times as fast~~ ~~* Create and add a single element: 1.3 times as fast~~ ~~* Create and add 24 life-like headers: 34% slower~~ ~~* Get one out of 20 random headers: 30% slower~~ ~~* Get one out of 24 life-like headers: 2.98 times as slow (!!)~~ ~~* Foreach: 2.5 times as fast~~ ~~* Iterating keys and values separtely is slow. There is likely still a lot to get there.~~ Signed-off-by: Yufan Gong <yufang@twitter.com> GitOrigin-RevId: 4deae32e1f9e73d95ece50a9a9ce760006dc318f
Merged here: 3007e5d. |
Problem
no 2.13 cross build
Solution
* HeaderMap: The big one. Rework into a trie as not to depend on HashTable's implementation.Result
~Performance changes in 2.12 vs baseline are a mixed bag. ~
* Create: 2.5 times as fast* Create and add a single element: 1.3 times as fast* Create and add 24 life-like headers: 34% slower* Get one out of 20 random headers: 30% slower* Get one out of 24 life-like headers: 2.98 times as slow (!!)* Foreach: 2.5 times as fast* Iterating keys and values separtely is slow. There is likely still a lot to get there.