diff --git a/.gitignore b/.gitignore index cb0c517fff..02f7ff993d 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *# *.log *.orig +*.jfr *.iml *.ipr *.iws diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4d41c6a848..795216dc39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,6 +85,8 @@ The TL;DR; of the above very precise workflow version is: 4. Keep polishing it until received enough LGTM 5. Profit! +Note that the Akka sbt project is large, so `sbt` needs to be run with lots of heap (1-2 Gb). This can be specified using a command line argument `sbt -mem 2048` or in the environment variable `SBT_OPTS` but then as a regular JVM memory flag, for example `SBT_OPTS=-Xmx2G`, on some platforms you can also edit the global defaults for sbt in `/usr/local/etc/sbtopts`. + ## The `validatePullRequest` task The Akka build includes a special task called `validatePullRequest` which investigates the changes made as well as dirty @@ -179,6 +181,19 @@ For more info, or for a starting point for new projects, look at the [Lightbend For larger projects that have invested a lot of time and resources into their current documentation and samples scheme (like for example Play), it is understandable that it will take some time to migrate to this new model. In these cases someone from the project needs to take the responsibility of manual QA and verifier for the documentation and samples. +### JavaDoc + +Akka generates JavaDoc-style API documentation using the [genjavadoc](https://github.com/typesafehub/genjavadoc) sbt plugin, since the sources are written mostly in Scala. + +Generating JavaDoc is not enabled by default, as it's not needed on day-to-day development as it's expected to just work. +If you'd like to check if you links and formatting looks good in JavaDoc (and not only in ScalaDoc), you can generate it by running: + +``` +sbt -Dakka.genjavadoc.enabled=true javaunidoc:doc +``` + +Which will generate JavaDoc style docs in `./target/javaunidoc/index.html` + ## External Dependencies All the external runtime dependencies for the project, including transitive dependencies, must have an open source license that is equal to, or compatible with, [Apache 2](http://www.apache.org/licenses/LICENSE-2.0). @@ -224,7 +239,7 @@ Example: Akka uses [Jenkins GitHub pull request builder plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin) that automatically merges the code, builds it, runs the tests and comments on the Pull Request in GitHub. -Upon a submission of a Pull Request the Github pull request builder plugin will post a following comment: +Upon a submission of a Pull Request the GitHub pull request builder plugin will post a following comment: Can one of the repo owners verify this patch? @@ -258,6 +273,21 @@ Thus we ask Java contributions to follow these simple guidelines: - `{` on same line as method name - in all other aspects, follow the [Oracle Java Style Guide](http://www.oracle.com/technetwork/java/codeconvtoc-136057.html) +### Preferred ways to use timeouts in tests + +Avoid short test timeouts, since Jenkins server may GC heavily causing spurious test failures. GC pause or other hiccup of 2 seconds is common in our CI environment. Please note that usually giving a larger timeout *does not slow down the tests*, as in an `expectMessage` call for example it usually will complete quickly. + +There is a number of ways timeouts can be defined in Akka tests. The following ways to use timeouts are recommended (in order of preference): + +* `remaining` is first choice (requires `within` block) +* `remainingOrDefault` is second choice +* `3.seconds` is third choice if not using testkit +* lower timeouts must come with a very good reason (e.g. Awaiting on a known to be "already completed" `Future`) + +Special care should be given `expectNoMsg` calls, which indeed will wait the entire timeout before continuing, therefore a shorter timeout should be used in those, for example `200` or `300.millis`. + +You can read up on remaining and friends in [TestKit.scala](https://github.com/akka/akka/blob/master/akka-testkit/src/main/scala/akka/testkit/TestKit.scala) + ## Contributing Modules For external contributions of entire features, the normal way is to establish it diff --git a/README.md b/README.md index 339887d464..4b6c42c083 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Reference Documentation ----------------------- The reference documentation is available at [doc.akka.io](http://doc.akka.io), -for [Scala](http://doc.akka.io/docs/akka/current/scala.html) and [Java](http://doc.akka.io/docs/akka/current/scala.html). +for [Scala](http://doc.akka.io/docs/akka/current/scala.html) and [Java](http://doc.akka.io/docs/akka/current/java.html). Community @@ -31,9 +31,10 @@ You can join these groups and chats to discuss and ask Akka related questions: In addition to that, you may enjoy following: +- The [news](http://akka.io/news) section of the page, which is updated whenever a new version is released - The [Akka Team Blog](http://blog.akka.io) - [@akkateam](https://twitter.com/akkateam) on Twitter -- Questions tagged [#akka on StackOverflow](stackoverflow.com/questions/tagged/akka) +- Questions tagged [#akka on StackOverflow](http://stackoverflow.com/questions/tagged/akka) Contributing ------------ @@ -42,7 +43,7 @@ Contributions are *very* welcome! If you see an issue that you'd like to see fixed, the best way to make it happen is to help out by submitting a PullRequest implementing it. Refer to the [CONTRIBUTING.md](https://github.com/akka/akka/blob/master/CONTRIBUTING.md) file for more details about the workflow, -and general hints how to prepare your pull request. You can also chat ask for clarifications or guidance in github issues directly, +and general hints how to prepare your pull request. You can also chat ask for clarifications or guidance in GitHub issues directly, or in the akka/dev chat if a more real time communication would be of benefit. A chat room is available for all questions related to *developing and contributing* to Akka: diff --git a/akka-actor-tests/src/test/java/akka/actor/ActorCreationTest.java b/akka-actor-tests/src/test/java/akka/actor/ActorCreationTest.java index 0f2b4ed585..35e835aa90 100644 --- a/akka-actor-tests/src/test/java/akka/actor/ActorCreationTest.java +++ b/akka-actor-tests/src/test/java/akka/actor/ActorCreationTest.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.stream.IntStream; import akka.testkit.TestActors; +import org.junit.Assert; import org.junit.Test; import akka.japi.Creator; @@ -209,7 +210,7 @@ public class ActorCreationTest extends JUnitSuite { public void testWrongAnonymousClassStaticCreator() { try { Props.create(new C() {}); // has implicit reference to outer class - fail("Should have detected this is not a real static class, and thrown"); + org.junit.Assert.fail("Should have detected this is not a real static class, and thrown"); } catch (IllegalArgumentException e) { assertEquals("cannot use non-static local Creator to create actors; make it static (e.g. local to a static method) or top-level", e.getMessage()); } @@ -278,7 +279,7 @@ public class ActorCreationTest extends JUnitSuite { // captures enclosing class }; Props.create(anonymousCreatorFromStaticMethod); - fail("Should have detected this is not a real static class, and thrown"); + org.junit.Assert.fail("Should have detected this is not a real static class, and thrown"); } catch (IllegalArgumentException e) { assertEquals("cannot use non-static local Creator to create actors; make it static (e.g. local to a static method) or top-level", e.getMessage()); } @@ -296,7 +297,7 @@ public class ActorCreationTest extends JUnitSuite { assertEquals(TestActor.class, p.actorClass()); try { TestActor.propsUsingLamdaWithoutClass(17); - fail("Should have detected lambda erasure, and thrown"); + org.junit.Assert.fail("Should have detected lambda erasure, and thrown"); } catch (IllegalArgumentException e) { assertEquals("erased Creator types are unsupported, use Props.create(actorClass, creator) instead", e.getMessage()); diff --git a/akka-actor-tests/src/test/java/akka/japi/JavaAPITestBase.java b/akka-actor-tests/src/test/java/akka/japi/JavaAPITestBase.java index 10c40159f8..d97f4302e5 100644 --- a/akka-actor-tests/src/test/java/akka/japi/JavaAPITestBase.java +++ b/akka-actor-tests/src/test/java/akka/japi/JavaAPITestBase.java @@ -41,14 +41,14 @@ public class JavaAPITestBase extends JUnitSuite { String s : Option.some("abc")) { return; } - fail("for-loop not entered"); + org.junit.Assert.fail("for-loop not entered"); } @Test public void shouldNotEnterForLoop() { for (@SuppressWarnings("unused") Object o : Option.none()) { - fail("for-loop entered"); + org.junit.Assert.fail("for-loop entered"); } } diff --git a/akka-actor-tests/src/test/java/akka/pattern/CircuitBreakerTest.java b/akka-actor-tests/src/test/java/akka/pattern/CircuitBreakerTest.java new file mode 100644 index 0000000000..c3e5da1606 --- /dev/null +++ b/akka-actor-tests/src/test/java/akka/pattern/CircuitBreakerTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009-2016 Lightbend Inc. + */ +package akka.pattern; + +import akka.actor.*; +import akka.testkit.AkkaJUnitActorSystemResource; +import akka.testkit.AkkaSpec; +import org.junit.ClassRule; +import org.junit.Test; +import org.scalatest.junit.JUnitSuite; +import scala.compat.java8.FutureConverters; +import scala.concurrent.Await; +import scala.concurrent.duration.FiniteDuration; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; + +public class CircuitBreakerTest extends JUnitSuite { + + @ClassRule + public static AkkaJUnitActorSystemResource actorSystemResource = + new AkkaJUnitActorSystemResource("JavaAPI", AkkaSpec.testConf()); + + private final ActorSystem system = actorSystemResource.getSystem(); + + @Test + public void useCircuitBreakerWithCompletableFuture() throws Exception { + final FiniteDuration fiveSeconds = FiniteDuration.create(5, TimeUnit.SECONDS); + final FiniteDuration fiveHundredMillis = FiniteDuration.create(500, TimeUnit.MILLISECONDS); + final CircuitBreaker breaker = new CircuitBreaker(system.dispatcher(), system.scheduler(), 1, fiveSeconds, fiveHundredMillis); + + final CompletableFuture f = new CompletableFuture<>(); + f.complete("hello"); + final CompletionStage res = breaker.callWithCircuitBreakerCS(() -> f); + assertEquals("hello", Await.result(FutureConverters.toScala(res), fiveSeconds)); + } + +} diff --git a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala index 46cd02589d..d30e02b2fa 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/DeployerSpec.scala @@ -60,6 +60,12 @@ object DeployerSpec { "/*/some" { router = scatter-gather-pool } + "/double/**" { + router = random-pool + } + "/double/more/**" { + router = round-robin-pool + } } """, ConfigParseOptions.defaults) @@ -74,7 +80,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { "be able to parse 'akka.actor.deployment._' with all default values" in { val service = "/service1" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service.split("/").drop(1)) + val deployment = system.asInstanceOf[ExtendedActorSystem].provider.deployer.lookup(service.split("/").drop(1)) deployment should ===(Some( Deploy( @@ -88,13 +94,13 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { "use None deployment for undefined service" in { val service = "/undefined" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service.split("/").drop(1)) + val deployment = system.asInstanceOf[ExtendedActorSystem].provider.deployer.lookup(service.split("/").drop(1)) deployment should ===(None) } "be able to parse 'akka.actor.deployment._' with dispatcher config" in { val service = "/service3" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service.split("/").drop(1)) + val deployment = system.asInstanceOf[ExtendedActorSystem].provider.deployer.lookup(service.split("/").drop(1)) deployment should ===(Some( Deploy( @@ -108,7 +114,7 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { "be able to parse 'akka.actor.deployment._' with mailbox config" in { val service = "/service4" - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service.split("/").drop(1)) + val deployment = system.asInstanceOf[ExtendedActorSystem].provider.deployer.lookup(service.split("/").drop(1)) deployment should ===(Some( Deploy( @@ -186,8 +192,15 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { assertRouting("/somewildcardmatch/some", ScatterGatherFirstCompletedPool(nrOfInstances = 1, within = 2 seconds), "/*/some") } + "be able to use double wildcards" in { + assertRouting("/double/wildcardmatch", RandomPool(1), "/double/**") + assertRouting("/double/wildcardmatch/anothermatch", RandomPool(1), "/double/**") + assertRouting("/double/more/anothermatch", RoundRobinPool(1), "/double/more/**") + assertNoRouting("/double") + } + "have correct router mappings" in { - val mapping = system.asInstanceOf[ActorSystemImpl].provider.deployer.routerTypeMapping + val mapping = system.asInstanceOf[ExtendedActorSystem].provider.deployer.routerTypeMapping mapping("from-code") should ===(classOf[akka.routing.NoRouter].getName) mapping("round-robin-pool") should ===(classOf[akka.routing.RoundRobinPool].getName) mapping("round-robin-group") should ===(classOf[akka.routing.RoundRobinGroup].getName) @@ -203,8 +216,13 @@ class DeployerSpec extends AkkaSpec(DeployerSpec.deployerConf) { mapping("consistent-hashing-group") should ===(classOf[akka.routing.ConsistentHashingGroup].getName) } + def assertNoRouting(service: String): Unit = { + val deployment = system.asInstanceOf[ExtendedActorSystem].provider.deployer.lookup(service.split("/").drop(1)) + deployment shouldNot be(defined) + } + def assertRouting(service: String, expected: RouterConfig, expectPath: String): Unit = { - val deployment = system.asInstanceOf[ActorSystemImpl].provider.deployer.lookup(service.split("/").drop(1)) + val deployment = system.asInstanceOf[ExtendedActorSystem].provider.deployer.lookup(service.split("/").drop(1)) deployment.map(_.path).getOrElse("NOT FOUND") should ===(expectPath) deployment.get.routerConfig.getClass should ===(expected.getClass) deployment.get.scope should ===(NoScopeGiven) diff --git a/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala b/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala index 445d52bf67..2ada2abda8 100644 --- a/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/actor/ReceiveTimeoutSpec.scala @@ -102,7 +102,12 @@ class ReceiveTimeoutSpec extends AkkaSpec { } })) - val ticks = system.scheduler.schedule(100.millis, 100.millis, timeoutActor, TransperentTick)(system.dispatcher) + val ticks = system.scheduler.schedule(100.millis, 100.millis, new Runnable { + override def run() = { + timeoutActor ! TransperentTick + timeoutActor ! Identify(None) + } + })(system.dispatcher) Await.ready(timeoutLatch, TestLatch.DefaultTimeout) ticks.cancel() diff --git a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala index 2a4ae83b05..fd520eee22 100644 --- a/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/dispatch/FutureSpec.scala @@ -719,7 +719,14 @@ class FutureSpec extends AkkaSpec with Checkers with BeforeAndAfterAll with Defa Await.result(p.future, timeout.duration) should ===(message) } } - "always cast successfully using mapTo" in { f((future, message) ⇒ (evaluating { Await.result(future.mapTo[java.lang.Thread], timeout.duration) } should produce[java.lang.Exception]).getMessage should ===(message)) } + "always cast successfully using mapTo" in { + f((future, message) ⇒ { + val exception = the[java.lang.Exception] thrownBy { + Await.result(future.mapTo[java.lang.Thread], timeout.duration) + } + exception.getMessage should ===(message) + }) + } } implicit def arbFuture: Arbitrary[Future[Int]] = Arbitrary(for (n ← arbitrary[Int]) yield Future(n)) diff --git a/akka-actor-tests/src/test/scala/akka/io/SimpleDnsCacheSpec.scala b/akka-actor-tests/src/test/scala/akka/io/SimpleDnsCacheSpec.scala index af796c6e0b..b45f547fe0 100644 --- a/akka-actor-tests/src/test/scala/akka/io/SimpleDnsCacheSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/io/SimpleDnsCacheSpec.scala @@ -3,9 +3,9 @@ package akka.io import java.net.InetAddress import java.util.concurrent.atomic.AtomicLong -import org.scalatest.{ ShouldMatchers, WordSpec } +import org.scalatest.{ Matchers, WordSpec } -class SimpleDnsCacheSpec extends WordSpec with ShouldMatchers { +class SimpleDnsCacheSpec extends WordSpec with Matchers { "Cache" should { "not reply with expired but not yet swept out entries" in { val localClock = new AtomicLong(0) @@ -15,11 +15,11 @@ class SimpleDnsCacheSpec extends WordSpec with ShouldMatchers { val cacheEntry = Dns.Resolved("test.local", Seq(InetAddress.getByName("127.0.0.1"))) cache.put(cacheEntry, 5000) - cache.cached("test.local") should equal(Some(cacheEntry)) + cache.cached("test.local") should ===(Some(cacheEntry)) localClock.set(4999) - cache.cached("test.local") should equal(Some(cacheEntry)) + cache.cached("test.local") should ===(Some(cacheEntry)) localClock.set(5000) - cache.cached("test.local") should equal(None) + cache.cached("test.local") should ===(None) } "sweep out expired entries on cleanup()" in { @@ -30,16 +30,16 @@ class SimpleDnsCacheSpec extends WordSpec with ShouldMatchers { val cacheEntry = Dns.Resolved("test.local", Seq(InetAddress.getByName("127.0.0.1"))) cache.put(cacheEntry, 5000) - cache.cached("test.local") should equal(Some(cacheEntry)) + cache.cached("test.local") should ===(Some(cacheEntry)) localClock.set(5000) - cache.cached("test.local") should equal(None) + cache.cached("test.local") should ===(None) localClock.set(0) - cache.cached("test.local") should equal(Some(cacheEntry)) + cache.cached("test.local") should ===(Some(cacheEntry)) localClock.set(5000) cache.cleanup() - cache.cached("test.local") should equal(None) + cache.cached("test.local") should ===(None) localClock.set(0) - cache.cached("test.local") should equal(None) + cache.cached("test.local") should ===(None) } } } diff --git a/akka-actor-tests/src/test/scala/akka/io/TcpConnectionSpec.scala b/akka-actor-tests/src/test/scala/akka/io/TcpConnectionSpec.scala index 1dcacf1b0d..5272f0b471 100644 --- a/akka-actor-tests/src/test/scala/akka/io/TcpConnectionSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/io/TcpConnectionSpec.scala @@ -449,10 +449,10 @@ class TcpConnectionSpec extends AkkaSpec(""" assertThisConnectionActorTerminated() val buffer = ByteBuffer.allocate(1) - val thrown = evaluating { + val thrown = the[IOException] thrownBy { windowsWorkaroundToDetectAbort() serverSideChannel.read(buffer) - } should produce[IOException] + } thrown.getMessage should ===(ConnectionResetByPeerMessage) } } diff --git a/akka-actor-tests/src/test/scala/akka/pattern/CircuitBreakerSpec.scala b/akka-actor-tests/src/test/scala/akka/pattern/CircuitBreakerSpec.scala index a0df91c16c..35784cf58f 100644 --- a/akka-actor-tests/src/test/scala/akka/pattern/CircuitBreakerSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/pattern/CircuitBreakerSpec.scala @@ -133,10 +133,10 @@ class CircuitBreakerSpec extends AkkaSpec with BeforeAndAfter { val breaker = CircuitBreakerSpec.shortCallTimeoutCb() Future { breaker().withSyncCircuitBreaker { - Thread.sleep(500.millis.dilated.toMillis) + Thread.sleep(1.second.dilated.toMillis) } } - within(300.millis) { + within(900.millis) { awaitCond(breaker().currentFailureCount == 1, 100.millis.dilated) } } @@ -219,7 +219,7 @@ class CircuitBreakerSpec extends AkkaSpec with BeforeAndAfter { val breaker = CircuitBreakerSpec.shortCallTimeoutCb() val fut = breaker().withCircuitBreaker(Future { - Thread.sleep(150.millis.dilated.toMillis); + Thread.sleep(150.millis.dilated.toMillis) throwException }) checkLatch(breaker.openLatch) diff --git a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala index 57dac8c482..1e2fce7b4c 100644 --- a/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala +++ b/akka-actor-tests/src/test/scala/akka/util/ByteStringSpec.scala @@ -10,6 +10,7 @@ import java.lang.Float.floatToRawIntBits import java.nio.{ ByteBuffer, ByteOrder } import java.nio.ByteOrder.{ BIG_ENDIAN, LITTLE_ENDIAN } +import akka.util.ByteString.{ ByteString1, ByteString1C, ByteStrings } import org.apache.commons.codec.binary.Hex.encodeHex import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.{ Arbitrary, Gen } @@ -20,6 +21,12 @@ import scala.collection.mutable.Builder class ByteStringSpec extends WordSpec with Matchers with Checkers { + // // uncomment when developing locally to get better coverage + // implicit override val generatorDrivenConfig = + // PropertyCheckConfig( + // minSuccessful = 1000, + // minSize = 0, maxSize = 100) + def genSimpleByteString(min: Int, max: Int) = for { n ← Gen.choose(min, max) b ← Gen.containerOfN[Array, Byte](n, arbitrary[Byte]) @@ -56,14 +63,21 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { } yield (xs, from, until) } - def testSer(obj: AnyRef) = { + def serialize(obj: AnyRef): Array[Byte] = { val os = new ByteArrayOutputStream val bos = new ObjectOutputStream(os) bos.writeObject(obj) - val arr = os.toByteArray - val is = new ObjectInputStream(new ByteArrayInputStream(arr)) + os.toByteArray + } - is.readObject == obj + def deserialize(bytes: Array[Byte]): AnyRef = { + val is = new ObjectInputStream(new ByteArrayInputStream(bytes)) + + is.readObject + } + + def testSer(obj: AnyRef) = { + deserialize(serialize(obj)) == obj } def hexFromSer(obj: AnyRef) = { @@ -281,10 +295,113 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { reference.toSeq == builder.result } + "ByteString1" must { + "drop(0)" in { + ByteString1.fromString("").drop(0) should ===(ByteString.empty) + ByteString1.fromString("a").drop(0) should ===(ByteString("a")) + } + "drop(1)" in { + ByteString1.fromString("").drop(1) should ===(ByteString("")) + ByteString1.fromString("a").drop(1) should ===(ByteString("")) + ByteString1.fromString("ab").drop(1) should ===(ByteString("b")) + ByteString1.fromString("xaaa").drop(1) should ===(ByteString("aaa")) + ByteString1.fromString("xaab").drop(1).take(2) should ===(ByteString("aa")) + ByteString1.fromString("0123456789").drop(5).take(4).drop(1).take(2) should ===(ByteString("67")) + } + "drop(n)" in { + ByteString1.fromString("ab").drop(2) should ===(ByteString("")) + ByteString1.fromString("ab").drop(3) should ===(ByteString("")) + } + } + "ByteString1C" must { + "drop(0)" in { + ByteString1C.fromString("").drop(0) should ===(ByteString.empty) + ByteString1C.fromString("a").drop(0) should ===(ByteString("a")) + } + "drop(1)" in { + ByteString1C.fromString("").drop(1) should ===(ByteString("")) + ByteString1C.fromString("a").drop(1) should ===(ByteString("")) + ByteString1C.fromString("ab").drop(1) should ===(ByteString("b")) + } + "drop(n)" in { + ByteString1C.fromString("ab").drop(2) should ===(ByteString("")) + ByteString1C.fromString("ab").drop(3) should ===(ByteString("")) + } + "take" in { + ByteString1.fromString("abcdefg").drop(1).take(0) should ===(ByteString("")) + ByteString1.fromString("abcdefg").drop(1).take(-1) should ===(ByteString("")) + ByteString1.fromString("abcdefg").drop(1).take(-2) should ===(ByteString("")) + ByteString1.fromString("abcdefg").drop(2) should ===(ByteString("cdefg")) + ByteString1.fromString("abcdefg").drop(2).take(1) should ===(ByteString("c")) + } + } + "ByteStrings" must { + "drop(0)" in { + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(0) should ===(ByteString.empty) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(0) should ===(ByteString("a")) + (ByteString1C.fromString("") ++ ByteString1.fromString("a")).drop(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("a")).drop(0) should ===(ByteString("aa")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(0) should ===(ByteString("")) + } + "drop(1)" in { + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(1) should ===(ByteString("bcd")) + ByteStrings(Vector(ByteString1.fromString("xaaa"))).drop(1) should ===(ByteString("aaa")) + } + "drop(n)" in { + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).drop(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(3) should ===(ByteString("d")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(4) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(5) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(10) should ===(ByteString("")) + + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).drop(-2) should ===(ByteString("abcd")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("")).drop(-2) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("")).drop(Int.MinValue) should ===(ByteString("ab")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("ab")).dropRight(Int.MinValue) should ===(ByteString("ab")) + } + "slice" in { + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).slice(0, 1) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString(""), ByteString1.fromString("a")).slice(1, 1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(2, 2) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(2, 3) should ===(ByteString("c")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(2, 4) should ===(ByteString("cd")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(3, 4) should ===(ByteString("d")) + ByteStrings(ByteString1.fromString("ab"), ByteString1.fromString("cd")).slice(10, 100) should ===(ByteString("")) + } + "dropRight" in { + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(0) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(-1) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(Int.MinValue) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("")).dropRight(Int.MaxValue) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(1) should ===(ByteString("ab")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(2) should ===(ByteString("a")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).dropRight(3) should ===(ByteString("")) + } + "take" in { + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(0) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-1) should ===(ByteString("")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(-2) should ===(ByteString("")) + (ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")) ++ ByteString1.fromString("defg")).drop(2) should ===(ByteString("cdefg")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(2).take(1) should ===(ByteString("c")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).take(100) should ===(ByteString("abc")) + ByteStrings(ByteString1.fromString("a"), ByteString1.fromString("bc")).drop(1).take(100) should ===(ByteString("bc")) + } + } + "A ByteString" must { "have correct size" when { "concatenating" in { check((a: ByteString, b: ByteString) ⇒ (a ++ b).size == a.size + b.size) } "dropping" in { check((a: ByteString, b: ByteString) ⇒ (a ++ b).drop(b.size).size == a.size) } + "taking" in { check((a: ByteString, b: ByteString) ⇒ (a ++ b).take(a.size) == a) } + "takingRight" in { check((a: ByteString, b: ByteString) ⇒ (a ++ b).takeRight(b.size) == b) } + "droppnig then taking" in { check((a: ByteString, b: ByteString) ⇒ (b ++ a ++ b).drop(b.size).take(a.size) == a) } + "droppingRight" in { check((a: ByteString, b: ByteString) ⇒ (b ++ a ++ b).drop(b.size).dropRight(b.size) == a) } } "be sequential" when { @@ -301,6 +418,21 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { (a ++ b ++ c) == xs } } + def excerciseRecombining(xs: ByteString, from: Int, until: Int) = { + val (tmp, c) = xs.splitAt(until) + val (a, b) = tmp.splitAt(from) + (a ++ b ++ c) should ===(xs) + } + "recombining - edge cases" in { + excerciseRecombining(ByteStrings(Vector(ByteString1(Array[Byte](1)), ByteString1(Array[Byte](2)))), -2147483648, 112121212) + excerciseRecombining(ByteStrings(Vector(ByteString1(Array[Byte](100)))), 0, 2) + excerciseRecombining(ByteStrings(Vector(ByteString1(Array[Byte](100)))), -2147483648, 2) + excerciseRecombining(ByteStrings(Vector(ByteString1.fromString("ab"), ByteString1.fromString("cd"))), 0, 1) + excerciseRecombining(ByteString1.fromString("abc").drop(1).take(1), -324234, 234232) + excerciseRecombining(ByteString("a"), 0, 2147483647) + excerciseRecombining(ByteStrings(Vector(ByteString1.fromString("ab"), ByteString1.fromString("cd"))).drop(2), 2147483647, 1) + excerciseRecombining(ByteString1.fromString("ab").drop1(1), Int.MaxValue, Int.MaxValue) + } } "behave as expected" when { @@ -322,7 +454,7 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { check { (a: ByteString) ⇒ a.asByteBuffers.foldLeft(ByteString.empty) { (bs, bb) ⇒ bs ++ ByteString(bb) } == a } check { (a: ByteString) ⇒ a.asByteBuffers.forall(_.isReadOnly) } check { (a: ByteString) ⇒ - import scala.collection.JavaConverters.iterableAsScalaIterableConverter; + import scala.collection.JavaConverters.iterableAsScalaIterableConverter a.asByteBuffers.zip(a.getByteBuffers().asScala).forall(x ⇒ x._1 == x._2) } } @@ -404,6 +536,13 @@ class ByteStringSpec extends WordSpec with Matchers with Checkers { testSer(bs) } } + + "with a large concatenated bytestring" in { + // coverage for #20901 + val original = ByteString(Array.fill[Byte](1000)(1)) ++ ByteString(Array.fill[Byte](1000)(2)) + + deserialize(serialize(original)) shouldEqual original + } } } diff --git a/akka-actor-tests/src/test/scala/akka/util/WildcardIndexSpec.scala b/akka-actor-tests/src/test/scala/akka/util/WildcardIndexSpec.scala new file mode 100644 index 0000000000..33ff1e8d4a --- /dev/null +++ b/akka-actor-tests/src/test/scala/akka/util/WildcardIndexSpec.scala @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2016 Lightbend Inc. + */ + +package akka.util + +import org.scalatest.{ Matchers, WordSpec } + +class WildcardIndexSpec extends WordSpec with Matchers { + + "wildcard index" must { + "allow to insert elements using Arrays of strings" in { + emptyIndex.insert(Array("a", "b"), 1) shouldBe a[WildcardIndex[_]] + emptyIndex.insert(Array("a"), 1) shouldBe a[WildcardIndex[_]] + emptyIndex.insert(Array.empty[String], 1) shouldBe a[WildcardIndex[_]] + } + + "allow to find inserted elements" in { + val tree = emptyIndex.insert(Array("a"), 1).insert(Array("a", "b"), 2).insert(Array("a", "c"), 3) + tree.find(Array("a", "b")).get shouldBe 2 + tree.find(Array("a")).get shouldBe 1 + tree.find(Array("x")) shouldBe None + tree.find(Array.empty[String]) shouldBe None + } + + "match all elements in the subArray when it contains a wildcard" in { + val tree1 = emptyIndex.insert(Array("a"), 1).insert(Array("a", "*"), 1) + tree1.find(Array("z")) shouldBe None + tree1.find(Array("a")).get shouldBe 1 + tree1.find(Array("a", "b")).get shouldBe 1 + tree1.find(Array("a", "x")).get shouldBe 1 + + val tree2 = emptyIndex.insert(Array("a", "*"), 1).insert(Array("a", "*", "c"), 2) + tree2.find(Array("z")) shouldBe None + tree2.find(Array("a", "b")).get shouldBe 1 + tree2.find(Array("a", "x")).get shouldBe 1 + tree2.find(Array("a", "x", "c")).get shouldBe 2 + tree2.find(Array("a", "x", "y")) shouldBe None + } + + "never find anything when emptyIndex" in { + emptyIndex.find(Array("a")) shouldBe None + emptyIndex.find(Array("a", "b")) shouldBe None + emptyIndex.find(Array.empty[String]) shouldBe None + } + + "match all remaining elements when it contains a terminal double wildcard" in { + val tree1 = emptyIndex.insert(Array("a", "**"), 1) + tree1.find(Array("z")) shouldBe None + tree1.find(Array("a", "b")).get shouldBe 1 + tree1.find(Array("a", "x")).get shouldBe 1 + tree1.find(Array("a", "x", "y")).get shouldBe 1 + + val tree2 = emptyIndex.insert(Array("**"), 1) + tree2.find(Array("anything", "I", "want")).get shouldBe 1 + tree2.find(Array("anything")).get shouldBe 1 + } + + "ignore non-terminal double wildcards" in { + val tree = emptyIndex.insert(Array("a", "**", "c"), 1) + tree.find(Array("a", "x", "y", "c")) shouldBe None + tree.find(Array("a", "x", "y")) shouldBe None + } + } + + private val emptyIndex = WildcardIndex[Int]() +} diff --git a/akka-actor/src/main/scala/akka/actor/Actor.scala b/akka-actor/src/main/scala/akka/actor/Actor.scala index 7be9612952..f0e766e410 100644 --- a/akka-actor/src/main/scala/akka/actor/Actor.scala +++ b/akka-actor/src/main/scala/akka/actor/Actor.scala @@ -61,7 +61,7 @@ case object Kill extends Kill { * is returned in the `ActorIdentity` message as `correlationId`. */ @SerialVersionUID(1L) -final case class Identify(messageId: Any) extends AutoReceivedMessage +final case class Identify(messageId: Any) extends AutoReceivedMessage with NotInfluenceReceiveTimeout /** * Reply to [[akka.actor.Identify]]. Contains diff --git a/akka-actor/src/main/scala/akka/actor/Deployer.scala b/akka-actor/src/main/scala/akka/actor/Deployer.scala index 9431139801..6b47dd0240 100644 --- a/akka-actor/src/main/scala/akka/actor/Deployer.scala +++ b/akka-actor/src/main/scala/akka/actor/Deployer.scala @@ -7,7 +7,7 @@ package akka.actor import java.util.concurrent.atomic.AtomicReference import akka.routing._ -import akka.util.WildcardTree +import akka.util.WildcardIndex import com.typesafe.config._ import scala.annotation.tailrec @@ -132,7 +132,7 @@ private[akka] class Deployer(val settings: ActorSystem.Settings, val dynamicAcce import scala.collection.JavaConverters._ private val resizerEnabled: Config = ConfigFactory.parseString("resizer.enabled=on") - private val deployments = new AtomicReference(WildcardTree[Deploy]()) + private val deployments = new AtomicReference(WildcardIndex[Deploy]()) private val config = settings.config.getConfig("akka.actor.deployment") protected val default = config.getConfig("default") val routerTypeMapping: Map[String, String] = @@ -146,20 +146,18 @@ private[akka] class Deployer(val settings: ActorSystem.Settings, val dynamicAcce case _ ⇒ None } foreach deploy - def lookup(path: ActorPath): Option[Deploy] = lookup(path.elements.drop(1).iterator) + def lookup(path: ActorPath): Option[Deploy] = lookup(path.elements.drop(1)) - def lookup(path: Iterable[String]): Option[Deploy] = lookup(path.iterator) - - def lookup(path: Iterator[String]): Option[Deploy] = deployments.get().find(path).data + def lookup(path: Iterable[String]): Option[Deploy] = deployments.get().find(path) def deploy(d: Deploy): Unit = { - @tailrec def add(path: Array[String], d: Deploy, w: WildcardTree[Deploy] = deployments.get): Unit = { - for (i ← 0 until path.length) path(i) match { + @tailrec def add(path: Array[String], d: Deploy, w: WildcardIndex[Deploy] = deployments.get): Unit = { + for (i ← path.indices) path(i) match { case "" ⇒ throw new InvalidActorNameException(s"Actor name in deployment [${d.path}] must not be empty") case el ⇒ ActorPath.validatePathElement(el, fullPath = d.path) } - if (!deployments.compareAndSet(w, w.insert(path.iterator, d))) add(path, d) + if (!deployments.compareAndSet(w, w.insert(path, d))) add(path, d) } add(d.path.split("/").drop(1), d) diff --git a/akka-actor/src/main/scala/akka/actor/FSM.scala b/akka-actor/src/main/scala/akka/actor/FSM.scala index 242083a730..6d4d0f11d7 100644 --- a/akka-actor/src/main/scala/akka/actor/FSM.scala +++ b/akka-actor/src/main/scala/akka/actor/FSM.scala @@ -224,6 +224,7 @@ object FSM { * Finite State Machine actor trait. Use as follows: * *
+ *   object A {
  *     trait State
  *     case class One extends State
  *     case class Two extends State
@@ -785,7 +786,7 @@ trait LoggingFSM[S, D] extends FSM[S, D] { this: Actor ⇒
         case a: ActorRef          ⇒ a.toString
         case _                    ⇒ "unknown"
       }
-      log.debug("processing " + event + " from " + srcstr)
+      log.debug("processing {} from {} in state {}", event, srcstr, stateName)
     }
 
     if (logDepth > 0) {
diff --git a/akka-actor/src/main/scala/akka/event/ActorClassificationUnsubscriber.scala b/akka-actor/src/main/scala/akka/event/ActorClassificationUnsubscriber.scala
index d9cada0d42..6aa9b13dbc 100644
--- a/akka-actor/src/main/scala/akka/event/ActorClassificationUnsubscriber.scala
+++ b/akka-actor/src/main/scala/akka/event/ActorClassificationUnsubscriber.scala
@@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger
  *
  * Watches all actors which subscribe on the given event stream, and unsubscribes them from it when they are Terminated.
  */
-private[akka] class ActorClassificationUnsubscriber(bus: ManagedActorClassification, debug: Boolean) extends Actor with Stash {
+protected[akka] class ActorClassificationUnsubscriber(bus: ManagedActorClassification, debug: Boolean) extends Actor with Stash {
 
   import ActorClassificationUnsubscriber._
 
diff --git a/akka-actor/src/main/scala/akka/event/EventStreamUnsubscriber.scala b/akka-actor/src/main/scala/akka/event/EventStreamUnsubscriber.scala
index 74158a5853..1c95f3f844 100644
--- a/akka-actor/src/main/scala/akka/event/EventStreamUnsubscriber.scala
+++ b/akka-actor/src/main/scala/akka/event/EventStreamUnsubscriber.scala
@@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger
  * subscribe calls * because of the need of linearizing the history message sequence and the possibility of sometimes
  * watching a few actors too much - we opt for the 2nd choice here.
  */
-private[akka] class EventStreamUnsubscriber(eventStream: EventStream, debug: Boolean = false) extends Actor {
+protected[akka] class EventStreamUnsubscriber(eventStream: EventStream, debug: Boolean = false) extends Actor {
 
   import EventStreamUnsubscriber._
 
diff --git a/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala b/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala
index 5f34ac2e5a..6460ee039c 100644
--- a/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala
+++ b/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala
@@ -3,18 +3,24 @@
  */
 package akka.pattern
 
-import java.util.concurrent.atomic.{ AtomicInteger, AtomicLong, AtomicBoolean }
+import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger, AtomicLong }
+
 import akka.AkkaException
 import akka.actor.Scheduler
 import akka.util.Unsafe
+
 import scala.util.control.NoStackTrace
-import java.util.concurrent.{ Callable, CopyOnWriteArrayList }
-import scala.concurrent.{ ExecutionContext, Future, Promise, Await }
+import java.util.concurrent.{ Callable, CompletionStage, CopyOnWriteArrayList }
+
+import scala.concurrent.{ Await, ExecutionContext, Future, Promise }
 import scala.concurrent.duration._
 import scala.concurrent.TimeoutException
 import scala.util.control.NonFatal
 import scala.util.Success
 import akka.dispatch.ExecutionContexts.sameThreadExecutionContext
+import akka.japi.function.Creator
+
+import scala.compat.java8.FutureConverters
 
 /**
  * Companion object providing factory methods for Circuit Breaker which runs callbacks in caller's thread
@@ -123,6 +129,18 @@ class CircuitBreaker(scheduler: Scheduler, maxFailures: Int, callTimeout: Finite
    */
   def callWithCircuitBreaker[T](body: Callable[Future[T]]): Future[T] = withCircuitBreaker(body.call)
 
+  /**
+   * Java API (8) for [[#withCircuitBreaker]]
+   *
+   * @param body Call needing protected
+   * @return [[java.util.concurrent.CompletionStage]] containing the call result or a
+   *   `scala.concurrent.TimeoutException` if the call timed out
+   */
+  def callWithCircuitBreakerCS[T](body: Callable[CompletionStage[T]]): CompletionStage[T] =
+    FutureConverters.toJava[T](callWithCircuitBreaker(new Callable[Future[T]] {
+      override def call(): Future[T] = FutureConverters.toScala(body.call())
+    }))
+
   /**
    * Wraps invocations of synchronous calls that need to be protected
    *
diff --git a/akka-actor/src/main/scala/akka/routing/ConsistentHashing.scala b/akka-actor/src/main/scala/akka/routing/ConsistentHashing.scala
index 78dd4ff505..4e3461fb69 100644
--- a/akka-actor/src/main/scala/akka/routing/ConsistentHashing.scala
+++ b/akka-actor/src/main/scala/akka/routing/ConsistentHashing.scala
@@ -91,7 +91,7 @@ object ConsistentHashingRouter {
    * INTERNAL API
    */
   private[akka] def hashMappingAdapter(mapper: ConsistentHashMapper): ConsistentHashMapping = {
-    case message if (mapper.hashKey(message).asInstanceOf[AnyRef] ne null) ⇒
+    case message if mapper.hashKey(message).asInstanceOf[AnyRef] ne null ⇒
       mapper.hashKey(message)
   }
 
diff --git a/akka-actor/src/main/scala/akka/routing/RouterConfig.scala b/akka-actor/src/main/scala/akka/routing/RouterConfig.scala
index 27f4df9085..1385ca9ba5 100644
--- a/akka-actor/src/main/scala/akka/routing/RouterConfig.scala
+++ b/akka-actor/src/main/scala/akka/routing/RouterConfig.scala
@@ -273,7 +273,9 @@ abstract class CustomRouterConfig extends RouterConfig {
 }
 
 /**
- * Router configuration which has no default, i.e. external configuration is required.
+ * Wraps a [[akka.actor.Props]] to mark the actor as externally configurable to be used with a router.
+ * If a [[akka.actor.Props]] is not wrapped with [[FromConfig]] then the actor will ignore the router part of the deployment section
+ * in the configuration.
  */
 case object FromConfig extends FromConfig {
   /**
@@ -290,7 +292,9 @@ case object FromConfig extends FromConfig {
 }
 
 /**
- * Java API: Router configuration which has no default, i.e. external configuration is required.
+ * Java API: Wraps a [[akka.actor.Props]] to mark the actor as externally configurable to be used with a router.
+ * If a [[akka.actor.Props]] is not wrapped with [[FromConfig]] then the actor will ignore the router part of the deployment section
+ * in the configuration.
  *
  * This can be used when the dispatcher to be used for the head Router needs to be configured
  * (defaults to default-dispatcher).
diff --git a/akka-actor/src/main/scala/akka/util/ByteIterator.scala b/akka-actor/src/main/scala/akka/util/ByteIterator.scala
index b763ef93fe..e3a3ffdbab 100644
--- a/akka-actor/src/main/scala/akka/util/ByteIterator.scala
+++ b/akka-actor/src/main/scala/akka/util/ByteIterator.scala
@@ -234,6 +234,7 @@ object ByteIterator {
       new MultiByteArrayIterator(clonedIterators)
     }
 
+    /** For performance sensitive code, call take() directly on ByteString (it's optimised there) */
     final override def take(n: Int): this.type = {
       var rest = n
       val builder = new ListBuffer[ByteArrayIterator]
@@ -249,7 +250,8 @@ object ByteIterator {
       normalize()
     }
 
-    @tailrec final override def drop(n: Int): this.type =
+    /** For performance sensitive code, call drop() directly on ByteString (it's optimised there) */
+    final override def drop(n: Int): this.type =
       if ((n > 0) && !isEmpty) {
         val nCurrent = math.min(n, current.len)
         current.drop(n)
@@ -341,7 +343,9 @@ object ByteIterator {
     def getDoubles(xs: Array[Double], offset: Int, n: Int)(implicit byteOrder: ByteOrder): this.type =
       getToArray(xs, offset, n, 8) { getDouble(byteOrder) } { current.getDoubles(_, _, _)(byteOrder) }
 
-    def copyToBuffer(buffer: ByteBuffer): Int = {
+    /** For performance sensitive code, call copyToBuffer() directly on ByteString (it's optimised there) */
+    override def copyToBuffer(buffer: ByteBuffer): Int = {
+      // the fold here is better than indexing into the LinearSeq
       val n = iterators.foldLeft(0) { _ + _.copyToBuffer(buffer) }
       normalize()
       n
@@ -635,6 +639,7 @@ abstract class ByteIterator extends BufferedIterator[Byte] {
    * @param buffer a ByteBuffer to copy bytes to
    * @return the number of bytes actually copied
    */
+  /** For performance sensitive code, call take() directly on ByteString (it's optimised there) */
   def copyToBuffer(buffer: ByteBuffer): Int
 
   /**
diff --git a/akka-actor/src/main/scala/akka/util/ByteString.scala b/akka-actor/src/main/scala/akka/util/ByteString.scala
index 8ccfa2518b..6438d95c48 100644
--- a/akka-actor/src/main/scala/akka/util/ByteString.scala
+++ b/akka-actor/src/main/scala/akka/util/ByteString.scala
@@ -7,14 +7,15 @@ package akka.util
 import java.io.{ ObjectInputStream, ObjectOutputStream }
 import java.nio.{ ByteBuffer, ByteOrder }
 import java.lang.{ Iterable ⇒ JIterable }
-import scala.annotation.varargs
+
+import scala.annotation.{ tailrec, varargs }
 import scala.collection.IndexedSeqOptimized
 import scala.collection.mutable.{ Builder, WrappedArray }
 import scala.collection.immutable
-import scala.collection.immutable.{ IndexedSeq, VectorBuilder }
+import scala.collection.immutable.{ IndexedSeq, VectorBuilder, VectorIterator }
 import scala.collection.generic.CanBuildFrom
 import scala.reflect.ClassTag
-import java.nio.charset.StandardCharsets
+import java.nio.charset.{ Charset, StandardCharsets }
 
 object ByteString {
 
@@ -103,13 +104,14 @@ object ByteString {
     }
 
   private[akka] object ByteString1C extends Companion {
+    def fromString(s: String): ByteString1C = new ByteString1C(s.getBytes)
     def apply(bytes: Array[Byte]): ByteString1C = new ByteString1C(bytes)
     val SerializationIdentity = 1.toByte
 
     def readFromInputStream(is: ObjectInputStream): ByteString1C = {
       val length = is.readInt()
       val arr = new Array[Byte](length)
-      is.read(arr, 0, length)
+      is.readFully(arr, 0, length)
       ByteString1C(arr)
     }
   }
@@ -123,37 +125,74 @@ object ByteString {
 
     override def length: Int = bytes.length
 
+    // Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
     override def iterator: ByteIterator.ByteArrayIterator = ByteIterator.ByteArrayIterator(bytes, 0, bytes.length)
 
-    private[akka] def toByteString1: ByteString1 = ByteString1(bytes)
+    /** INTERNAL API */
+    private[akka] def toByteString1: ByteString1 = ByteString1(bytes, 0, bytes.length)
 
+    /** INTERNAL API */
     private[akka] def byteStringCompanion = ByteString1C
 
-    def asByteBuffer: ByteBuffer = toByteString1.asByteBuffer
+    override def asByteBuffer: ByteBuffer = toByteString1.asByteBuffer
 
-    def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer)
+    override def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer)
 
-    def decodeString(charset: String): String =
+    override def decodeString(charset: String): String =
       if (isEmpty) "" else new String(bytes, charset)
 
-    def ++(that: ByteString): ByteString =
+    override def decodeString(charset: Charset): String =
+      if (isEmpty) "" else new String(bytes, charset)
+
+    override def ++(that: ByteString): ByteString = {
       if (that.isEmpty) this
       else if (this.isEmpty) that
       else toByteString1 ++ that
+    }
+
+    override def take(n: Int): ByteString =
+      if (n <= 0) ByteString.empty
+      else toByteString1.take(n)
+
+    override def dropRight(n: Int): ByteString =
+      if (n <= 0) this
+      else toByteString1.dropRight(n)
+
+    override def drop(n: Int): ByteString =
+      if (n <= 0) this
+      else toByteString1.drop(n)
 
     override def slice(from: Int, until: Int): ByteString =
-      if ((from != 0) || (until != length)) toByteString1.slice(from, until)
-      else this
+      if ((from == 0) && (until == length)) this
+      else if (from > length) ByteString.empty
+      else toByteString1.slice(from, until)
 
-    private[akka] def writeToOutputStream(os: ObjectOutputStream): Unit =
+    private[akka] override def writeToOutputStream(os: ObjectOutputStream): Unit =
       toByteString1.writeToOutputStream(os)
+
+    override def copyToBuffer(buffer: ByteBuffer): Int =
+      writeToBuffer(buffer, offset = 0)
+
+    /** INTERNAL API: Specialized for internal use, writing multiple ByteString1C into the same ByteBuffer. */
+    private[akka] def writeToBuffer(buffer: ByteBuffer, offset: Int): Int = {
+      val copyLength = Math.min(buffer.remaining, offset + length)
+      if (copyLength > 0) {
+        buffer.put(bytes, offset, copyLength)
+        drop(copyLength)
+      }
+      copyLength
+    }
+
   }
 
+  /** INTERNAL API: ByteString backed by exactly one array, with start / end markers */
   private[akka] object ByteString1 extends Companion {
     val empty: ByteString1 = new ByteString1(Array.empty[Byte])
-    def apply(bytes: Array[Byte]): ByteString1 = ByteString1(bytes, 0, bytes.length)
+    def fromString(s: String): ByteString1 = apply(s.getBytes)
+    def apply(bytes: Array[Byte]): ByteString1 = apply(bytes, 0, bytes.length)
     def apply(bytes: Array[Byte], startIndex: Int, length: Int): ByteString1 =
-      if (length == 0) empty else new ByteString1(bytes, startIndex, length)
+      if (length == 0) empty
+      else new ByteString1(bytes, Math.max(0, startIndex), Math.max(0, length))
 
     val SerializationIdentity = 0.toByte
 
@@ -170,6 +209,7 @@ object ByteString {
 
     def apply(idx: Int): Byte = bytes(checkRangeConvert(idx))
 
+    // Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
     override def iterator: ByteIterator.ByteArrayIterator =
       ByteIterator.ByteArrayIterator(bytes, startIndex, startIndex + length)
 
@@ -189,6 +229,48 @@ object ByteString {
 
     private[akka] def byteStringCompanion = ByteString1
 
+    override def dropRight(n: Int): ByteString =
+      dropRight1(n)
+
+    /** INTERNAL API */
+    private[akka] def dropRight1(n: Int): ByteString1 =
+      if (n <= 0) this
+      else if (length - n <= 0) ByteString1.empty
+      else new ByteString1(bytes, startIndex, length - n)
+
+    override def drop(n: Int): ByteString =
+      if (n <= 0) this else drop1(n)
+
+    /** INTERNAL API */
+    private[akka] def drop1(n: Int): ByteString1 = {
+      val nextStartIndex = startIndex + n
+      if (nextStartIndex >= bytes.length) ByteString1.empty
+      else ByteString1(bytes, nextStartIndex, length - n)
+    }
+
+    override def take(n: Int): ByteString =
+      if (n <= 0) ByteString.empty
+      else ByteString1(bytes, startIndex, Math.min(n, length))
+
+    override def slice(from: Int, until: Int): ByteString = {
+      if (from <= 0 && until >= length) this // we can do < / > since we're Compact
+      else if (until <= from) ByteString1.empty
+      else ByteString1(bytes, startIndex + from, until - from)
+    }
+
+    override def copyToBuffer(buffer: ByteBuffer): Int =
+      writeToBuffer(buffer)
+
+    /** INTERNAL API: Specialized for internal use, writing multiple ByteString1C into the same ByteBuffer. */
+    private[akka] def writeToBuffer(buffer: ByteBuffer): Int = {
+      val copyLength = Math.min(buffer.remaining, length)
+      if (copyLength > 0) {
+        buffer.put(bytes, startIndex, copyLength)
+        drop(copyLength)
+      }
+      copyLength
+    }
+
     def compact: CompactByteString =
       if (isCompact) ByteString1C(bytes) else ByteString1C(toArray)
 
@@ -200,7 +282,10 @@ object ByteString {
 
     def asByteBuffers: scala.collection.immutable.Iterable[ByteBuffer] = List(asByteBuffer)
 
-    def decodeString(charset: String): String =
+    override def decodeString(charset: String): String =
+      new String(if (length == bytes.length) bytes else toArray, charset)
+
+    override def decodeString(charset: Charset): String = // avoids Charset.forName lookup in String internals
       new String(if (length == bytes.length) bytes else toArray, charset)
 
     def ++(that: ByteString): ByteString = {
@@ -283,8 +368,9 @@ object ByteString {
    */
   final class ByteStrings private (private[akka] val bytestrings: Vector[ByteString1], val length: Int) extends ByteString with Serializable {
     if (bytestrings.isEmpty) throw new IllegalArgumentException("bytestrings must not be empty")
+    if (bytestrings.head.isEmpty) throw new IllegalArgumentException("bytestrings.head must not be empty")
 
-    def apply(idx: Int): Byte =
+    def apply(idx: Int): Byte = {
       if (0 <= idx && idx < length) {
         var pos = 0
         var seen = 0
@@ -294,7 +380,9 @@ object ByteString {
         }
         bytestrings(pos)(idx - seen)
       } else throw new IndexOutOfBoundsException(idx.toString)
+    }
 
+    /** Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead */
     override def iterator: ByteIterator.MultiByteArrayIterator =
       ByteIterator.MultiByteArrayIterator(bytestrings.toStream map { _.iterator })
 
@@ -312,6 +400,14 @@ object ByteString {
 
     def isCompact: Boolean = if (bytestrings.length == 1) bytestrings.head.isCompact else false
 
+    override def copyToBuffer(buffer: ByteBuffer): Int = {
+      @tailrec def copyItToTheBuffer(buffer: ByteBuffer, i: Int, written: Int): Int =
+        if (i < bytestrings.length) copyItToTheBuffer(buffer, i + 1, written + bytestrings(i).writeToBuffer(buffer))
+        else written
+
+      copyItToTheBuffer(buffer, 0, 0)
+    }
+
     def compact: CompactByteString = {
       if (isCompact) bytestrings.head.compact
       else {
@@ -331,11 +427,83 @@ object ByteString {
 
     def decodeString(charset: String): String = compact.decodeString(charset)
 
+    def decodeString(charset: Charset): String =
+      compact.decodeString(charset)
+
     private[akka] def writeToOutputStream(os: ObjectOutputStream): Unit = {
       os.writeInt(bytestrings.length)
       bytestrings.foreach(_.writeToOutputStream(os))
     }
 
+    override def take(n: Int): ByteString = {
+      @tailrec def take0(n: Int, b: ByteStringBuilder, bs: Vector[ByteString1]): ByteString =
+        if (bs.isEmpty || n <= 0) b.result
+        else {
+          val head = bs.head
+          if (n <= head.length) b.append(head.take(n)).result
+          else take0(n - head.length, b.append(head), bs.tail)
+        }
+
+      if (n <= 0) ByteString.empty
+      else if (n >= length) this
+      else take0(n, ByteString.newBuilder, bytestrings)
+    }
+
+    override def dropRight(n: Int): ByteString =
+      if (n <= 0) this
+      else {
+        val last = bytestrings.last
+        if (n < last.length) new ByteStrings(bytestrings.init :+ last.dropRight1(n), length - n)
+        else {
+          val remaining = bytestrings.init
+          if (remaining.isEmpty) ByteString.empty
+          else {
+            val s = new ByteStrings(remaining, length - last.length)
+            val remainingToBeDropped = n - last.length
+            s.dropRight(remainingToBeDropped)
+          }
+        }
+      }
+
+    override def slice(from: Int, until: Int): ByteString =
+      if ((from == 0) && (until == length)) this
+      else if (from > length || until <= from) ByteString.empty
+      else drop(from).dropRight(length - until)
+
+    override def drop(n: Int): ByteString =
+      if (n <= 0) this
+      else if (n > length) ByteString.empty
+      else drop0(n)
+
+    private def drop0(n: Int): ByteString = {
+      var continue = true
+      var fullDrops = 0
+      var remainingToDrop = n
+      do {
+        // impl note: could be optimised a bit by using VectorIterator instead, 
+        //            however then we're forced to call .toVector which halfs performance
+        //            We can work around that, as there's a Scala private method "remainingVector" which is fast, 
+        //            but let's not go into calling private APIs here just yet.
+        val currentLength = bytestrings(fullDrops).length
+        if (remainingToDrop >= currentLength) {
+          fullDrops += 1
+          remainingToDrop -= currentLength
+        } else continue = false
+      } while (remainingToDrop > 0 && continue)
+
+      val remainingByteStrings = bytestrings.drop(fullDrops)
+      if (remainingByteStrings.isEmpty) ByteString.empty
+      else if (remainingToDrop > 0) {
+        val h: ByteString1 = remainingByteStrings.head.drop1(remainingToDrop)
+        val bs = remainingByteStrings.tail
+
+        if (h.isEmpty)
+          if (bs.isEmpty) ByteString.empty
+          else new ByteStrings(bs, length - n)
+        else new ByteStrings(h +: bs, length - n)
+      } else ByteStrings(remainingByteStrings, length - n)
+    }
+
     protected def writeReplace(): AnyRef = new SerializationProxy(this)
   }
 
@@ -386,6 +554,8 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
   // *must* be overridden by derived classes. This construction is necessary
   // to specialize the return type, as the method is already implemented in
   // a parent trait.
+  // 
+  // Avoid `iterator` in performance sensitive code, call ops directly on ByteString instead
   override def iterator: ByteIterator = throw new UnsupportedOperationException("Method iterator is not implemented in ByteString")
 
   override def head: Byte = apply(0)
@@ -393,14 +563,19 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
   override def last: Byte = apply(length - 1)
   override def init: ByteString = dropRight(1)
 
-  override def slice(from: Int, until: Int): ByteString =
-    if ((from == 0) && (until == length)) this
-    else iterator.slice(from, until).toByteString
-
-  override def take(n: Int): ByteString = slice(0, n)
+  // *must* be overridden by derived classes.
+  override def take(n: Int): ByteString = throw new UnsupportedOperationException("Method slice is not implemented in ByteString")
   override def takeRight(n: Int): ByteString = slice(length - n, length)
-  override def drop(n: Int): ByteString = slice(n, length)
-  override def dropRight(n: Int): ByteString = slice(0, length - n)
+
+  // these methods are optimized in derived classes utilising the maximum knowlage about data layout available to them:
+  // *must* be overridden by derived classes.
+  override def slice(from: Int, until: Int): ByteString = throw new UnsupportedOperationException("Method slice is not implemented in ByteString")
+
+  // *must* be overridden by derived classes.
+  override def drop(n: Int): ByteString = throw new UnsupportedOperationException("Method drop is not implemented in ByteString")
+
+  // *must* be overridden by derived classes.
+  override def dropRight(n: Int): ByteString = throw new UnsupportedOperationException("Method dropRight is not implemented in ByteString")
 
   override def takeWhile(p: Byte ⇒ Boolean): ByteString = iterator.takeWhile(p).toByteString
   override def dropWhile(p: Byte ⇒ Boolean): ByteString = iterator.dropWhile(p).toByteString
@@ -425,7 +600,7 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
    *
    * @return this ByteString copied into a byte array
    */
-  protected[ByteString] def toArray: Array[Byte] = toArray[Byte] // protected[ByteString] == public to Java but hidden to Scala * fnizz *
+  protected[ByteString] def toArray: Array[Byte] = toArray[Byte]
 
   override def toArray[B >: Byte](implicit arg0: ClassTag[B]): Array[B] = iterator.toArray
   override def copyToArray[B >: Byte](xs: Array[B], start: Int, len: Int): Unit =
@@ -452,7 +627,8 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
    * @param buffer a ByteBuffer to copy bytes to
    * @return the number of bytes actually copied
    */
-  def copyToBuffer(buffer: ByteBuffer): Int = iterator.copyToBuffer(buffer)
+  // *must* be overridden by derived classes. 
+  def copyToBuffer(buffer: ByteBuffer): Int = throw new UnsupportedOperationException("Method copyToBuffer is not implemented in ByteString")
 
   /**
    * Create a new ByteString with all contents compacted into a single,
@@ -504,9 +680,16 @@ sealed abstract class ByteString extends IndexedSeq[Byte] with IndexedSeqOptimiz
 
   /**
    * Decodes this ByteString using a charset to produce a String.
+   * If you have a [[Charset]] instance available, use `decodeString(charset: java.nio.charset.Charset` instead.
    */
   def decodeString(charset: String): String
 
+  /**
+   * Decodes this ByteString using a charset to produce a String.
+   * Avoids Charset.forName lookup in String internals, thus is preferable to `decodeString(charset: String)`.
+   */
+  def decodeString(charset: Charset): String
+
   /**
    * map method that will automatically cast Int back into Byte.
    */
@@ -568,8 +751,8 @@ object CompactByteString {
    * an Array.
    */
   def fromArray(array: Array[Byte], offset: Int, length: Int): CompactByteString = {
-    val copyOffset = math.max(offset, 0)
-    val copyLength = math.max(math.min(array.length - copyOffset, length), 0)
+    val copyOffset = Math.max(offset, 0)
+    val copyLength = Math.max(Math.min(array.length - copyOffset, length), 0)
     if (copyLength == 0) empty
     else {
       val copyArray = new Array[Byte](copyLength)
@@ -666,6 +849,8 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
 
   override def ++=(xs: TraversableOnce[Byte]): this.type = {
     xs match {
+      case b: ByteString if b.isEmpty ⇒
+      // do nothing
       case b: ByteString1C ⇒
         clearTemp()
         _builder += b.toByteString1
@@ -708,7 +893,7 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
   /**
    * Java API: append a ByteString to this builder.
    */
-  def append(bs: ByteString): this.type = this ++= bs
+  def append(bs: ByteString): this.type = if (bs.isEmpty) this else this ++= bs
 
   /**
    * Add a single Byte to this builder.
@@ -875,7 +1060,7 @@ final class ByteStringBuilder extends Builder[Byte, ByteString] {
     fillByteBuffer(len * 8, byteOrder) { _.asDoubleBuffer.put(array, start, len) }
 
   def clear(): Unit = {
-    _builder.clear
+    _builder.clear()
     _length = 0
     _tempLength = 0
   }
diff --git a/akka-actor/src/main/scala/akka/util/WildcardIndex.scala b/akka-actor/src/main/scala/akka/util/WildcardIndex.scala
new file mode 100644
index 0000000000..1c5cec32f2
--- /dev/null
+++ b/akka-actor/src/main/scala/akka/util/WildcardIndex.scala
@@ -0,0 +1,81 @@
+/**
+ * Copyright (C) 2009-2016 Lightbend Inc. 
+ */
+
+package akka.util
+
+import scala.annotation.tailrec
+import scala.collection.immutable.HashMap
+
+private[akka] final case class WildcardIndex[T](wildcardTree: WildcardTree[T] = WildcardTree[T](), doubleWildcardTree: WildcardTree[T] = WildcardTree[T]()) {
+
+  val empty = WildcardTree[T]()
+
+  def insert(elems: Array[String], d: T): WildcardIndex[T] = elems.lastOption match {
+    case Some("**") ⇒ copy(doubleWildcardTree = doubleWildcardTree.insert(elems.iterator, d))
+    case Some(_)    ⇒ copy(wildcardTree = wildcardTree.insert(elems.iterator, d))
+    case _          ⇒ this
+  }
+
+  def find(elems: Iterable[String]): Option[T] =
+    (if (wildcardTree.isEmpty) {
+      if (doubleWildcardTree.isEmpty) {
+        empty
+      } else {
+        doubleWildcardTree.findWithTerminalDoubleWildcard(elems.iterator)
+      }
+    } else {
+      val withSingleWildcard = wildcardTree.findWithSingleWildcard(elems.iterator)
+      if (withSingleWildcard.isEmpty) {
+        doubleWildcardTree.findWithTerminalDoubleWildcard(elems.iterator)
+      } else {
+        withSingleWildcard
+      }
+    }).data
+
+}
+
+private[akka] object WildcardTree {
+  private val empty = new WildcardTree[Nothing]()
+  def apply[T](): WildcardTree[T] = empty.asInstanceOf[WildcardTree[T]]
+}
+
+private[akka] final case class WildcardTree[T](data: Option[T] = None, children: Map[String, WildcardTree[T]] = HashMap[String, WildcardTree[T]]()) {
+
+  lazy val isEmpty: Boolean = data.isEmpty && children.isEmpty
+
+  def insert(elems: Iterator[String], d: T): WildcardTree[T] =
+    if (!elems.hasNext) {
+      copy(data = Some(d))
+    } else {
+      val e = elems.next()
+      copy(children = children.updated(e, children.getOrElse(e, WildcardTree[T]()).insert(elems, d)))
+    }
+
+  @tailrec def findWithSingleWildcard(elems: Iterator[String]): WildcardTree[T] =
+    if (!elems.hasNext) this
+    else {
+      children.get(elems.next()) match {
+        case Some(branch) ⇒ branch.findWithSingleWildcard(elems)
+        case None ⇒ children.get("*") match {
+          case Some(branch) ⇒ branch.findWithSingleWildcard(elems)
+          case None         ⇒ WildcardTree[T]()
+        }
+      }
+    }
+
+  @tailrec def findWithTerminalDoubleWildcard(elems: Iterator[String], alt: WildcardTree[T] = WildcardTree[T]()): WildcardTree[T] = {
+    if (!elems.hasNext) this
+    else {
+      val newAlt = children.getOrElse("**", alt)
+      children.get(elems.next()) match {
+        case Some(branch) ⇒ branch.findWithTerminalDoubleWildcard(elems, newAlt)
+        case None ⇒ children.get("*") match {
+          case Some(branch) ⇒ branch.findWithTerminalDoubleWildcard(elems, newAlt)
+          case None         ⇒ newAlt
+        }
+      }
+    }
+  }
+}
+
diff --git a/akka-actor/src/main/scala/akka/util/WildcardTree.scala b/akka-actor/src/main/scala/akka/util/WildcardTree.scala
deleted file mode 100644
index 15f3e6d30c..0000000000
--- a/akka-actor/src/main/scala/akka/util/WildcardTree.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (C) 2009-2016 Lightbend Inc. 
- */
-
-package akka.util
-
-import annotation.tailrec
-import collection.immutable.HashMap
-
-private[akka] object WildcardTree {
-  private val empty = new WildcardTree[Nothing]()
-  def apply[T](): WildcardTree[T] = empty.asInstanceOf[WildcardTree[T]]
-}
-private[akka] final case class WildcardTree[T](data: Option[T] = None, children: Map[String, WildcardTree[T]] = HashMap[String, WildcardTree[T]]()) {
-
-  def insert(elems: Iterator[String], d: T): WildcardTree[T] =
-    if (!elems.hasNext) {
-      copy(data = Some(d))
-    } else {
-      val e = elems.next()
-      copy(children = children.updated(e, children.get(e).getOrElse(WildcardTree()).insert(elems, d)))
-    }
-
-  @tailrec final def find(elems: Iterator[String]): WildcardTree[T] =
-    if (!elems.hasNext) this
-    else {
-      (children.get(elems.next()) orElse children.get("*")) match {
-        case Some(branch) ⇒ branch.find(elems)
-        case None         ⇒ WildcardTree()
-      }
-    }
-}
diff --git a/akka-bench-jmh/src/main/scala/akka/http/HttpBlueprintBenchmark.scala b/akka-bench-jmh/src/main/scala/akka/http/HttpBlueprintBenchmark.scala
new file mode 100644
index 0000000000..d9925b8a82
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/http/HttpBlueprintBenchmark.scala
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2015-2016 Lightbend Inc. 
+ */
+
+package akka.http
+
+import java.util.concurrent.{ CountDownLatch, TimeUnit }
+
+import akka.NotUsed
+import akka.actor.ActorSystem
+import akka.http.impl.util.ByteStringRendering
+import akka.http.scaladsl.{ Http, HttpExt }
+import akka.http.scaladsl.Http.ServerBinding
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.unmarshalling._
+import akka.stream._
+import akka.stream.TLSProtocol.{ SslTlsInbound, SslTlsOutbound }
+import akka.stream.scaladsl._
+import akka.stream.stage.{ GraphStage, GraphStageLogic }
+import akka.util.ByteString
+import com.typesafe.config.ConfigFactory
+import org.openjdk.jmh.annotations._
+import org.openjdk.jmh.infra.Blackhole
+
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+import scala.util.Try
+
+/*
+Baseline: 
+ 
+ [info] Benchmark                                      Mode  Cnt       Score       Error  Units
+ [info] HttpBlueprintBenchmark.run_10000_reqs         thrpt   20  197972.659 ± 14512.694  ops/s
+ */
+@State(Scope.Benchmark)
+@OutputTimeUnit(TimeUnit.SECONDS)
+@BenchmarkMode(Array(Mode.Throughput))
+class HttpBlueprintBenchmark {
+
+  val config = ConfigFactory.parseString(
+    """
+      akka {
+        loglevel = "WARNING"
+        
+        stream.materializer {
+        
+          # default: sync-processing-limit = 1000
+          sync-processing-limit = 1000
+          
+          # default: output-burst-limit = 10000
+          output-burst-limit = 1000
+          
+          # default: initial-input-buffer-size = 4
+          initial-input-buffer-size = 4
+          
+          # default: max-input-buffer-size = 16
+          max-input-buffer-size = 16
+          
+        }
+        
+        http {
+          # default: request-timeout = 20s 
+          request-timeout = infinite # disabled
+          # request-timeout = 20s 
+        }
+      }""".stripMargin
+  ).withFallback(ConfigFactory.load())
+
+  implicit val system: ActorSystem = ActorSystem("HttpBenchmark", config)
+
+  val materializer: ActorMaterializer = ActorMaterializer()
+  val notFusingMaterializer = ActorMaterializer(materializer.settings.withAutoFusing(false))
+
+  val request: HttpRequest = HttpRequest()
+  val requestRendered = ByteString(
+    "GET / HTTP/1.1\r\n" +
+      "Accept: */*\r\n" +
+      "Accept-Encoding: gzip, deflate\r\n" +
+      "Connection: keep-alive\r\n" +
+      "Host: example.com\r\n" +
+      "User-Agent: HTTPie/0.9.3\r\n" +
+      "\r\n"
+  )
+
+  val response: HttpResponse = HttpResponse()
+  val responseRendered: ByteString = ByteString(
+    s"HTTP/1.1 200 OK\r\n" +
+      s"Content-Length: 0\r\n" +
+      s"\r\n"
+  )
+
+  def TCPPlacebo(requests: Int): Flow[ByteString, ByteString, NotUsed] =
+    Flow.fromSinkAndSource(
+      Flow[ByteString].takeWhile(it => !(it.utf8String contains "Connection: close")) to Sink.ignore,
+      Source.repeat(requestRendered).take(requests)
+    )
+
+  def layer: BidiFlow[HttpResponse, SslTlsOutbound, SslTlsInbound, HttpRequest, NotUsed] = Http().serverLayer()(materializer)
+  def server(requests: Int): Flow[HttpResponse, HttpRequest, _] = layer atop TLSPlacebo() join TCPPlacebo(requests)
+
+  val reply = Flow[HttpRequest].map { _ => response }
+
+  @TearDown
+  def shutdown(): Unit = {
+    Await.result(system.terminate(), 5.seconds)
+  }
+
+  val nothingHere: Flow[HttpRequest, HttpResponse, NotUsed] =
+    Flow.fromSinkAndSource(Sink.cancelled, Source.empty)
+
+  @Benchmark
+  @OperationsPerInvocation(100000)
+  def run_10000_reqs() = {
+    val n = 100000
+    val latch = new CountDownLatch(n)
+
+    val replyCountdown = reply map { x =>
+      latch.countDown()
+      x
+    }
+    server(n).joinMat(replyCountdown)(Keep.right).run()(materializer)
+
+    latch.await()
+  }
+
+}
+
diff --git a/akka-bench-jmh/src/main/scala/akka/http/HttpRequestParsingBenchmark.scala b/akka-bench-jmh/src/main/scala/akka/http/HttpRequestParsingBenchmark.scala
new file mode 100644
index 0000000000..4233a5115b
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/http/HttpRequestParsingBenchmark.scala
@@ -0,0 +1,102 @@
+/**
+ * Copyright (C) 2015-2016 Lightbend Inc. 
+ */
+package akka.http
+
+import java.util.concurrent.TimeUnit
+import javax.net.ssl.SSLContext
+
+import akka.Done
+import akka.actor.ActorSystem
+import akka.event.NoLogging
+import akka.http.impl.engine.parsing.{ HttpHeaderParser, HttpRequestParser }
+import akka.http.scaladsl.settings.ParserSettings
+import akka.event.NoLogging
+import akka.stream.ActorMaterializer
+import akka.stream.TLSProtocol.SessionBytes
+import akka.stream.scaladsl._
+import akka.util.ByteString
+import org.openjdk.jmh.annotations.{ OperationsPerInvocation, _ }
+import org.openjdk.jmh.infra.Blackhole
+
+import scala.concurrent.duration._
+import scala.concurrent.{ Await, Future }
+
+@State(Scope.Benchmark)
+@OutputTimeUnit(TimeUnit.SECONDS)
+@BenchmarkMode(Array(Mode.Throughput))
+class HttpRequestParsingBenchmark {
+
+  implicit val system: ActorSystem = ActorSystem("HttpRequestParsingBenchmark")
+  implicit val materializer = ActorMaterializer()(system)
+  val parserSettings = ParserSettings(system)
+  val parser = new HttpRequestParser(parserSettings, false, HttpHeaderParser(parserSettings, NoLogging)())
+  val dummySession = SSLContext.getDefault.createSSLEngine.getSession
+
+  @Param(Array("small", "large"))
+  var req: String = ""
+
+  def request = req match {
+    case "small" => requestBytesSmall
+    case "large" => requestBytesLarge
+  }
+
+  val requestBytesSmall: SessionBytes = SessionBytes(
+    dummySession,
+    ByteString(
+      """|GET / HTTP/1.1
+         |Accept: */*
+         |Accept-Encoding: gzip, deflate
+         |Connection: keep-alive
+         |Host: example.com
+         |User-Agent: HTTPie/0.9.3
+         |
+         |""".stripMargin.replaceAll("\n", "\r\n")
+    )
+  )
+
+  val requestBytesLarge: SessionBytes = SessionBytes(
+    dummySession,
+    ByteString(
+      """|GET /json HTTP/1.1
+         |Host: server
+         |User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+         |Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+         |Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+         |Accept-Language: en-US,en;q=0.5
+         |Connection: keep-alive
+         |
+         |""".stripMargin.replaceAll("\n", "\r\n")
+    )
+  )
+
+  /*
+  // before:
+  [info] Benchmark                                                 (req)   Mode  Cnt            Score       Error  Units
+  [info] HttpRequestParsingBenchmark.parse_10000_requests          small  thrpt   20      358 982.157 ± 93745.863  ops/s
+  [info] HttpRequestParsingBenchmark.parse_10000_requests          large  thrpt   20      388 335.666 ± 16990.715  ops/s
+  
+  // after:
+  [info] HttpRequestParsingBenchmark.parse_10000_requests_val      small  thrpt   20      623 975.879 ± 6191.897  ops/s
+  [info] HttpRequestParsingBenchmark.parse_10000_requests_val      large  thrpt   20      507 460.283 ± 4735.843  ops/s
+  */
+
+  val httpMessageParser = Flow.fromGraph(parser)
+
+  def flow(bytes: SessionBytes, n: Int): RunnableGraph[Future[Done]] =
+    Source.repeat(request).take(n)
+      .via(httpMessageParser)
+      .toMat(Sink.ignore)(Keep.right)
+
+  @Benchmark
+  @OperationsPerInvocation(10000)
+  def parse_10000_requests_val(blackhole: Blackhole): Unit = {
+    val done = flow(requestBytesSmall, 10000).run()
+    Await.ready(done, 32.days)
+  }
+
+  @TearDown
+  def shutdown(): Unit = {
+    Await.result(system.terminate(), 5.seconds)
+  }
+}
diff --git a/akka-bench-jmh/src/main/scala/akka/http/HttpResponseRenderingBenchmark.scala b/akka-bench-jmh/src/main/scala/akka/http/HttpResponseRenderingBenchmark.scala
new file mode 100644
index 0000000000..8e5aa5c10a
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/http/HttpResponseRenderingBenchmark.scala
@@ -0,0 +1,250 @@
+/**
+ * Copyright (C) 2015-2016 Lightbend Inc. 
+ */
+
+package akka.http
+
+import java.util.concurrent.{ CountDownLatch, TimeUnit }
+
+import akka.NotUsed
+import akka.actor.ActorSystem
+import akka.event.NoLogging
+import akka.http.impl.engine.rendering.ResponseRenderingOutput.HttpData
+import akka.http.impl.engine.rendering.{ HttpResponseRendererFactory, ResponseRenderingContext, ResponseRenderingOutput }
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.model.headers.Server
+import akka.http.scaladsl.unmarshalling.Unmarshal
+import akka.stream._
+import akka.stream.scaladsl._
+import akka.stream.stage.{ GraphStageLogic, GraphStageWithMaterializedValue, InHandler }
+import akka.util.ByteString
+import com.typesafe.config.ConfigFactory
+import org.openjdk.jmh.annotations._
+import org.openjdk.jmh.infra.Blackhole
+
+import scala.concurrent.duration._
+import scala.concurrent.{ Await, Future }
+import scala.util.Try
+
+@State(Scope.Benchmark)
+@OutputTimeUnit(TimeUnit.SECONDS)
+@BenchmarkMode(Array(Mode.Throughput))
+class HttpResponseRenderingBenchmark extends HttpResponseRendererFactory(
+  serverHeader = Some(Server("Akka HTTP 2.4.x")),
+  responseHeaderSizeHint = 64,
+  log = NoLogging
+) {
+
+  val config = ConfigFactory.parseString(
+    """
+      akka {
+        loglevel = "ERROR"
+      }""".stripMargin
+  ).withFallback(ConfigFactory.load())
+
+  implicit val system = ActorSystem("HttpResponseRenderingBenchmark", config)
+  implicit val materializer = ActorMaterializer()
+
+  import system.dispatcher
+
+  val requestRendered = ByteString(
+    "GET / HTTP/1.1\r\n" +
+      "Accept: */*\r\n" +
+      "Accept-Encoding: gzip, deflate\r\n" +
+      "Connection: keep-alive\r\n" +
+      "Host: example.com\r\n" +
+      "User-Agent: HTTPie/0.9.3\r\n" +
+      "\r\n"
+  )
+
+  def TCPPlacebo(requests: Int): Flow[ByteString, ByteString, NotUsed] =
+    Flow.fromSinkAndSource(
+      Flow[ByteString].takeWhile(it => !(it.utf8String contains "Connection: close")) to Sink.ignore,
+      Source.repeat(requestRendered).take(requests)
+    )
+
+  def TlsPlacebo = TLSPlacebo()
+
+  val requestRendering: Flow[HttpRequest, String, NotUsed] =
+    Http()
+      .clientLayer(headers.Host("blah.com"))
+      .atop(TlsPlacebo)
+      .join {
+        Flow[ByteString].map { x ⇒
+          val response = s"HTTP/1.1 200 OK\r\nContent-Length: ${x.size}\r\n\r\n"
+          ByteString(response) ++ x
+        }
+      }
+      .mapAsync(1)(response => Unmarshal(response).to[String])
+
+  def renderResponse: Future[String] = Source.single(HttpRequest(uri = "/foo"))
+    .via(requestRendering)
+    .runWith(Sink.head)
+
+  var request: HttpRequest = _
+  var pool: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), _] = _
+
+  @TearDown
+  def shutdown(): Unit = {
+    Await.ready(Http().shutdownAllConnectionPools(), 1.second)
+    Await.result(system.terminate(), 5.seconds)
+  }
+
+  /*
+  [info] Benchmark                                               Mode  Cnt              Score              Error  Units
+  [info] HttpResponseRenderingBenchmark.header_date_val         thrpt   20  2 704 169 260 029.906 ± 234456086114.237  ops/s
+  
+  // def, normal time
+  [info] HttpResponseRenderingBenchmark.header_date_def             thrpt   20    178 297 625 609.638 ± 7429280865.659  ops/s
+  [info] HttpResponseRenderingBenchmark.response_ok_simple_val      thrpt   20          1 258 119.673 ± 58399.454  ops/s
+  [info] HttpResponseRenderingBenchmark.response_ok_simple_def      thrpt   20            687 576.928 ± 94813.618  ops/s
+  
+  // clock nanos
+  [info] HttpResponseRenderingBenchmark.response_ok_simple_clock    thrpt   20          1 676 438.649 ± 33976.590  ops/s
+  [info] HttpResponseRenderingBenchmark.response_ok_simple_clock    thrpt   40          1 199 462.263 ± 222226.304  ops/s
+  
+  // ------
+  
+  // before optimisig collectFirst
+  [info] HttpResponseRenderingBenchmark.json_response        thrpt   20  1 782 572.845 ± 16572.625  ops/s
+  [info] HttpResponseRenderingBenchmark.simple_response      thrpt   20  1 611 802.216 ± 19557.151  ops/s
+  
+  // after removing collectFirst and Option from renderHeaders
+  // not much of a difference, but hey, less Option allocs
+  [info] HttpResponseRenderingBenchmark.json_response        thrpt   20  1 785 152.896 ± 15210.299  ops/s
+  [info] HttpResponseRenderingBenchmark.simple_response      thrpt   20  1 783 800.184 ± 14938.415  ops/s
+  
+  // -----
+
+  // baseline for this optimisation is the above results (after collectFirst).
+  
+  // after introducing pre-rendered ContentType headers:
+  
+  normal clock
+  [info] HttpResponseRenderingBenchmark.json_long_raw_response  thrpt   20  1738558.895 ± 159612.661  ops/s
+  [info] HttpResponseRenderingBenchmark.json_response           thrpt   20  1714176.824 ± 100011.642  ops/s
+
+  "fast clock"
+  [info] HttpResponseRenderingBenchmark.json_long_raw_response  thrpt   20  1 528 632.480 ± 44934.827  ops/s
+  [info] HttpResponseRenderingBenchmark.json_response           thrpt   20  1 517 383.792 ± 28256.716  ops/s
+  
+   */
+
+  /**
+   * HTTP/1.1 200 OK
+   * Server: Akka HTTP 2.4.x
+   * Date: Tue, 26 Jul 2016 15:26:53 GMT
+   * Content-Type: text/plain; charset=UTF-8
+   * Content-Length: 6
+   *
+   * ENTITY
+   */
+  val simpleResponse =
+    ResponseRenderingContext(
+      response = HttpResponse(
+        200,
+        headers = Nil,
+        entity = HttpEntity("ENTITY")
+      ),
+      requestMethod = HttpMethods.GET
+    )
+
+  /**
+   * HTTP/1.1 200 OK
+   * Server: Akka HTTP 2.4.x
+   * Date: Tue, 26 Jul 2016 15:26:53 GMT
+   * Content-Type: application/json
+   * Content-Length: 27
+   *
+   * {"message":"Hello, World!"}
+   */
+  val jsonResponse =
+    ResponseRenderingContext(
+      response = HttpResponse(
+        200,
+        headers = Nil,
+        entity = HttpEntity(ContentTypes.`application/json`, """{"message":"Hello, World!"}""")
+      ),
+      requestMethod = HttpMethods.GET
+    )
+
+  /**
+   * HTTP/1.1 200 OK
+   * Server: Akka HTTP 2.4.x
+   * Date: Tue, 26 Jul 2016 15:26:53 GMT
+   * Content-Type: application/json
+   * Content-Length: 315
+   *
+   * [{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+   */
+  val jsonLongRawResponse =
+    ResponseRenderingContext(
+      response = HttpResponse(
+        200,
+        headers = Nil,
+        entity = HttpEntity(ContentTypes.`application/json`, """[{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]""")
+      ),
+      requestMethod = HttpMethods.GET
+    )
+
+  @Benchmark
+  @Threads(8)
+  @OperationsPerInvocation(100 * 1000)
+  def simple_response(blackhole: Blackhole): Unit =
+    renderToImpl(simpleResponse, blackhole, n = 100 * 1000).await()
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def json_response(blackhole: Blackhole): Unit =
+    renderToImpl(jsonResponse, blackhole, n = 100 * 1000).await()
+
+  /*
+  Difference between 27 and 315 bytes long JSON is:
+  
+  [info] Benchmark                                                   Mode  Cnt        Score       Error  Units
+  [info] HttpResponseRenderingBenchmark.json_long_raw_response      thrpt   20  1 932 331.049 ± 64125.621  ops/s
+  [info] HttpResponseRenderingBenchmark.json_response               thrpt   20  1 973 232.941 ± 18568.314  ops/s
+   */
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def json_long_raw_response(blackhole: Blackhole): Unit =
+    renderToImpl(jsonLongRawResponse, blackhole, n = 100 * 1000).await()
+
+  class JitSafeLatch[A](blackhole: Blackhole, n: Int) extends GraphStageWithMaterializedValue[SinkShape[A], CountDownLatch] {
+    val in = Inlet[A]("JitSafeLatch.in")
+    override val shape = SinkShape(in)
+
+    override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, CountDownLatch) = {
+      val latch = new CountDownLatch(n)
+      val logic = new GraphStageLogic(shape) with InHandler {
+
+        override def preStart(): Unit = pull(in)
+        override def onPush(): Unit = {
+          if (blackhole ne null) blackhole.consume(grab(in))
+          latch.countDown()
+          pull(in)
+        }
+
+        setHandler(in, this)
+      }
+
+      (logic, latch)
+    }
+  }
+
+  def renderToImpl(ctx: ResponseRenderingContext, blackhole: Blackhole, n: Int)(implicit mat: Materializer): CountDownLatch = {
+    val latch =
+      (Source.repeat(ctx).take(n) ++ Source.maybe[ResponseRenderingContext]) // never send upstream completion
+        .via(renderer.named("renderer"))
+        .runWith(new JitSafeLatch[ResponseRenderingOutput](blackhole, n))
+
+    latch
+  }
+
+  // TODO benchmark with stable override
+  override def currentTimeMillis(): Long = System.currentTimeMillis()
+  //  override def currentTimeMillis(): Long = System.currentTimeMillis() // DateTime(2011, 8, 25, 9, 10, 29).clicks // provide a stable date for testing
+
+}
+
diff --git a/akka-bench-jmh/src/main/scala/akka/stream/FusedGraphsBenchmark.scala b/akka-bench-jmh/src/main/scala/akka/stream/FusedGraphsBenchmark.scala
new file mode 100644
index 0000000000..f2dea5401f
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/stream/FusedGraphsBenchmark.scala
@@ -0,0 +1,331 @@
+/**
+ * Copyright (C) 2014-2016 Lightbend Inc. 
+ */
+
+package akka.stream
+
+import java.util.concurrent.{ CountDownLatch, TimeUnit }
+
+import akka.NotUsed
+import akka.actor.ActorSystem
+import akka.stream.impl.fusing.GraphStages
+import akka.stream.scaladsl._
+import akka.stream.stage._
+import org.openjdk.jmh.annotations.{ OperationsPerInvocation, _ }
+
+import scala.concurrent.Await
+import scala.concurrent.duration._
+
+object FusedGraphsBenchmark {
+  val ElementCount = 100 * 1000
+
+  @volatile var blackhole: org.openjdk.jmh.infra.Blackhole = _
+}
+
+// Just to avoid allocations and still have a way to do some work in stages. The value itself does not matter
+// so no issues with sharing (the result does not make any sense, but hey)
+class MutableElement(var value: Int)
+
+class TestSource(elems: Array[MutableElement]) extends GraphStage[SourceShape[MutableElement]] {
+  val out = Outlet[MutableElement]("TestSource.out")
+  override val shape = SourceShape(out)
+
+  override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with OutHandler {
+    private[this] var left = FusedGraphsBenchmark.ElementCount - 1
+
+    override def onPull(): Unit = {
+      if (left >= 0) {
+        push(out, elems(left))
+        left -= 1
+      } else completeStage()
+    }
+
+    setHandler(out, this)
+  }
+}
+
+class JitSafeCompletionLatch extends GraphStageWithMaterializedValue[SinkShape[MutableElement], CountDownLatch] {
+  val in = Inlet[MutableElement]("JitSafeCompletionLatch.in")
+  override val shape = SinkShape(in)
+
+  override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, CountDownLatch) = {
+    val latch = new CountDownLatch(1)
+    val logic = new GraphStageLogic(shape) with InHandler {
+      private[this] var sum = 0
+
+      override def preStart(): Unit = pull(in)
+      override def onPush(): Unit = {
+        sum += grab(in).value
+        pull(in)
+      }
+
+      override def onUpstreamFinish(): Unit = {
+        // Do not ignore work along the chain
+        FusedGraphsBenchmark.blackhole.consume(sum)
+        latch.countDown()
+        completeStage()
+      }
+
+      setHandler(in, this)
+    }
+
+    (logic, latch)
+  }
+}
+
+class IdentityStage extends GraphStage[FlowShape[MutableElement, MutableElement]] {
+  val in = Inlet[MutableElement]("Identity.in")
+  val out = Outlet[MutableElement]("Identity.out")
+  override val shape = FlowShape(in, out)
+
+  override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) with InHandler with OutHandler {
+    override def onPush(): Unit = push(out, grab(in))
+    override def onPull(): Unit = pull(in)
+
+    setHandlers(in, out, this)
+  }
+}
+
+@State(Scope.Benchmark)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@BenchmarkMode(Array(Mode.Throughput))
+class FusedGraphsBenchmark {
+  import FusedGraphsBenchmark._
+
+  implicit val system = ActorSystem("test")
+  var materializer: ActorMaterializer = _
+  var testElements: Array[MutableElement] = _
+
+  var singleIdentity: RunnableGraph[CountDownLatch] = _
+  var chainOfIdentities: RunnableGraph[CountDownLatch] = _
+  var singleMap: RunnableGraph[CountDownLatch] = _
+  var chainOfMaps: RunnableGraph[CountDownLatch] = _
+  var repeatTakeMapAndFold: RunnableGraph[CountDownLatch] = _
+  var singleBuffer: RunnableGraph[CountDownLatch] = _
+  var chainOfBuffers: RunnableGraph[CountDownLatch] = _
+  var broadcastZip: RunnableGraph[CountDownLatch] = _
+  var balanceMerge: RunnableGraph[CountDownLatch] = _
+  var broadcastZipBalanceMerge: RunnableGraph[CountDownLatch] = _
+
+  @Setup
+  def setup(): Unit = {
+    val settings = ActorMaterializerSettings(system)
+      .withFuzzing(false)
+      .withSyncProcessingLimit(Int.MaxValue)
+      .withAutoFusing(false) // We fuse manually in this test in the setup
+
+    materializer = ActorMaterializer(settings)
+    testElements = Array.fill(ElementCount)(new MutableElement(0))
+    val addFunc = (x: MutableElement) => { x.value += 1; x }
+
+    val testSource = Source.fromGraph(new TestSource(testElements))
+    val testSink = Sink.fromGraph(new JitSafeCompletionLatch)
+
+    def fuse(r: RunnableGraph[CountDownLatch]): RunnableGraph[CountDownLatch] = {
+      RunnableGraph.fromGraph(Fusing.aggressive(r))
+    }
+
+    val identityStage = new IdentityStage
+
+    singleIdentity =
+      fuse(
+        testSource
+          .via(identityStage)
+          .toMat(testSink)(Keep.right)
+      )
+
+    chainOfIdentities =
+      fuse(
+        testSource
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .via(identityStage)
+          .toMat(testSink)(Keep.right)
+      )
+
+    singleMap =
+      fuse(
+        testSource
+          .map(addFunc)
+          .toMat(testSink)(Keep.right)
+      )
+
+    chainOfMaps =
+      fuse(
+        testSource
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .map(addFunc)
+          .toMat(testSink)(Keep.right)
+      )
+
+    repeatTakeMapAndFold =
+      fuse(
+        Source.repeat(new MutableElement(0))
+          .take(ElementCount)
+          .map(addFunc)
+          .map(addFunc)
+          .fold(new MutableElement(0))((acc, x) => { acc.value += x.value; acc })
+          .toMat(testSink)(Keep.right)
+      )
+
+    singleBuffer =
+      fuse(
+        testSource
+          .buffer(10, OverflowStrategy.backpressure)
+          .toMat(testSink)(Keep.right)
+      )
+
+    chainOfBuffers =
+      fuse(
+        testSource
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .buffer(10, OverflowStrategy.backpressure)
+          .toMat(testSink)(Keep.right)
+      )
+
+    val broadcastZipFlow: Flow[MutableElement, MutableElement, NotUsed] = Flow.fromGraph(GraphDSL.create() { implicit b =>
+      import GraphDSL.Implicits._
+
+      val bcast = b.add(Broadcast[MutableElement](2))
+      val zip = b.add(Zip[MutableElement, MutableElement]())
+
+      bcast ~> zip.in0
+      bcast ~> zip.in1
+
+      FlowShape(bcast.in, zip.out.map(_._1).outlet)
+    })
+
+    val balanceMergeFlow: Flow[MutableElement, MutableElement, NotUsed] = Flow.fromGraph(GraphDSL.create() { implicit b =>
+      import GraphDSL.Implicits._
+
+      val balance = b.add(Balance[MutableElement](2))
+      val merge = b.add(Merge[MutableElement](2))
+
+      balance ~> merge
+      balance ~> merge
+
+      FlowShape(balance.in, merge.out)
+    })
+
+    broadcastZip =
+      fuse(
+        testSource
+          .via(broadcastZipFlow)
+          .toMat(testSink)(Keep.right)
+      )
+
+    balanceMerge =
+      fuse(
+        testSource
+          .via(balanceMergeFlow)
+          .toMat(testSink)(Keep.right)
+      )
+
+    broadcastZipBalanceMerge =
+      fuse(
+        testSource
+          .via(broadcastZipFlow)
+          .via(balanceMergeFlow)
+          .toMat(testSink)(Keep.right)
+      )
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def single_identity(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    singleIdentity.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def chain_of_identities(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    chainOfIdentities.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def single_map(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    singleMap.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def chain_of_maps(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    chainOfMaps.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def repeat_take_map_and_fold(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    repeatTakeMapAndFold.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def single_buffer(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    singleBuffer.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def chain_of_buffers(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    chainOfBuffers.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def broadcast_zip(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    broadcastZip.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def balance_merge(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    balanceMerge.run()(materializer).await()
+  }
+
+  @Benchmark
+  @OperationsPerInvocation(100 * 1000)
+  def boradcast_zip_balance_merge(blackhole: org.openjdk.jmh.infra.Blackhole): Unit = {
+    FusedGraphsBenchmark.blackhole = blackhole
+    broadcastZipBalanceMerge.run()(materializer).await()
+  }
+
+  @TearDown
+  def shutdown(): Unit = {
+    Await.result(system.terminate(), 5.seconds)
+  }
+
+}
diff --git a/akka-bench-jmh/src/main/scala/akka/stream/JsonFramingBenchmark.scala b/akka-bench-jmh/src/main/scala/akka/stream/JsonFramingBenchmark.scala
new file mode 100644
index 0000000000..8e7cfe884e
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/stream/JsonFramingBenchmark.scala
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009-2015 Typesafe Inc. 
+ */
+package akka.stream
+
+import java.util.concurrent.TimeUnit
+
+import akka.stream.impl.JsonObjectParser
+import akka.util.ByteString
+import org.openjdk.jmh.annotations._
+
+@State(Scope.Benchmark)
+@OutputTimeUnit(TimeUnit.SECONDS)
+@BenchmarkMode(Array(Mode.Throughput))
+class JsonFramingBenchmark {
+
+  /*
+    Benchmark                                 Mode  Cnt      Score      Error  Units
+    // old
+    JsonFramingBenchmark.collecting_1        thrpt   20     81.476 ±   14.793  ops/s
+    JsonFramingBenchmark.collecting_offer_5  thrpt   20     20.187 ±    2.291  ops/s
+
+    // new
+    JsonFramingBenchmark.counting_1          thrpt   20  10766.738 ± 1278.300  ops/s
+    JsonFramingBenchmark.counting_offer_5    thrpt   20  28798.255 ± 2670.163  ops/s
+   */
+
+  val json =
+    ByteString(
+      """|{"fname":"Frank","name":"Smith","age":42,"id":1337,"boardMember":false},
+        |{"fname":"Bob","name":"Smith","age":42,"id":1337,"boardMember":false},
+        |{"fname":"Bob","name":"Smith","age":42,"id":1337,"boardMember":false},
+        |{"fname":"Bob","name":"Smith","age":42,"id":1337,"boardMember":false},
+        |{"fname":"Bob","name":"Smith","age":42,"id":1337,"boardMember":false},
+        |{"fname":"Bob","name":"Smith","age":42,"id":1337,"boardMember":false},
+        |{"fname":"Hank","name":"Smith","age":42,"id":1337,"boardMember":false}""".stripMargin
+    )
+
+  val bracket = new JsonObjectParser
+
+  @Setup(Level.Invocation)
+  def init(): Unit = {
+    bracket.offer(json)
+  }
+
+  @Benchmark
+  def counting_1: ByteString =
+    bracket.poll().get
+
+  @Benchmark
+  @OperationsPerInvocation(5)
+  def counting_offer_5: ByteString = {
+    bracket.offer(json)
+    bracket.poll().get
+    bracket.poll().get
+    bracket.poll().get
+    bracket.poll().get
+    bracket.poll().get
+    bracket.poll().get
+  }
+
+}
diff --git a/akka-bench-jmh/src/main/scala/akka/util/ByteString_copyToBuffer_Benchmark.scala b/akka-bench-jmh/src/main/scala/akka/util/ByteString_copyToBuffer_Benchmark.scala
new file mode 100644
index 0000000000..5868897ec7
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/util/ByteString_copyToBuffer_Benchmark.scala
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2014-2016 Lightbend Inc. 
+ */
+package akka.util
+
+import java.nio.ByteBuffer
+import java.util.concurrent.TimeUnit
+
+import akka.util.ByteString.{ ByteString1, ByteString1C, ByteStrings }
+import org.openjdk.jmh.annotations._
+import org.openjdk.jmh.infra.Blackhole
+
+@State(Scope.Benchmark)
+@Measurement(timeUnit = TimeUnit.MILLISECONDS)
+class ByteString_copyToBuffer_Benchmark {
+
+  val _bs_mini = ByteString(Array.ofDim[Byte](128 * 4))
+  val _bs_small = ByteString(Array.ofDim[Byte](1024 * 1))
+  val _bs_large = ByteString(Array.ofDim[Byte](1024 * 4))
+
+  val bs_mini = ByteString(Array.ofDim[Byte](128 * 4 * 4))
+  val bs_small = ByteString(Array.ofDim[Byte](1024 * 1 * 4))
+  val bs_large = ByteString(Array.ofDim[Byte](1024 * 4 * 4))
+
+  val bss_mini = ByteStrings(Vector.fill(4)(bs_mini.asInstanceOf[ByteString1C].toByteString1), 4 * bs_mini.length)
+  val bss_small = ByteStrings(Vector.fill(4)(bs_small.asInstanceOf[ByteString1C].toByteString1), 4 * bs_small.length)
+  val bss_large = ByteStrings(Vector.fill(4)(bs_large.asInstanceOf[ByteString1C].toByteString1), 4 * bs_large.length)
+  val bss_pc_large = bss_large.compact
+
+  val buf = ByteBuffer.allocate(1024 * 4 * 4)
+
+  /*
+    BEFORE
+
+    [info] Benchmark                                       Mode  Cnt            Score          Error  Units
+    [info] ByteStringBenchmark.bs_large_copyToBuffer      thrpt   40  142 163 289.866 ± 21751578.294  ops/s
+    [info] ByteStringBenchmark.bss_large_copyToBuffer     thrpt   40    1 489 195.631 ±   209165.487  ops/s << that's the interesting case, we needlessly fold and allocate tons of Stream etc
+    [info] ByteStringBenchmark.bss_large_pc_copyToBuffer  thrpt   40  184 466 756.364 ±  9169108.378  ops/s // "can't beat that"
+    
+    
+    [info] ....[Thread state: RUNNABLE]........................................................................
+    [info]  35.9%  35.9% scala.collection.Iterator$class.toStream
+    [info]  20.2%  20.2% scala.collection.immutable.Stream.foldLeft
+    [info]  11.6%  11.6% scala.collection.immutable.Stream$StreamBuilder.
+    [info]  10.9%  10.9% akka.util.ByteIterator.
+    [info]   6.1%   6.1% scala.collection.mutable.ListBuffer.
+    [info]   5.2%   5.2% akka.util.ByteString.copyToBuffer
+    [info]   5.2%   5.2% scala.collection.AbstractTraversable.
+    [info]   2.2%   2.2% scala.collection.immutable.VectorIterator.initFrom
+    [info]   1.2%   1.2% akka.util.generated.ByteStringBenchmark_bss_large_copyToBuffer.bss_large_copyToBuffer_thrpt_jmhStub
+    [info]   0.3%   0.3% akka.util.ByteIterator$MultiByteArrayIterator.copyToBuffer
+    [info]   1.2%   1.2% 
+    
+    
+    AFTER specializing impls
+    
+    [info] ....[Thread state: RUNNABLE]........................................................................
+    [info]  99.5%  99.6% akka.util.generated.ByteStringBenchmark_bss_large_copyToBuffer_jmhTest.bss_large_copyToBuffer_thrpt_jmhStub
+    [info]   0.1%   0.1% java.util.concurrent.CountDownLatch.countDown
+    [info]   0.1%   0.1% sun.reflect.NativeMethodAccessorImpl.invoke0
+    [info]   0.1%   0.1% sun.misc.Unsafe.putObject
+    [info]   0.1%   0.1% org.openjdk.jmh.infra.IterationParamsL2.getBatchSize
+    [info]   0.1%   0.1% java.lang.Thread.currentThread
+    [info]   0.1%   0.1% sun.misc.Unsafe.compareAndSwapInt
+    [info]   0.1%   0.1% sun.reflect.AccessorGenerator.internalize
+    
+    [info] Benchmark                                       Mode  Cnt            Score         Error  Units
+    [info] ByteStringBenchmark.bs_large_copyToBuffer      thrpt   40  177 328 585.473 ± 7742067.648  ops/s
+    [info] ByteStringBenchmark.bss_large_copyToBuffer     thrpt   40  113 535 003.488 ± 3899763.124  ops/s // previous bad case now very good (was 2M/s)
+    [info] ByteStringBenchmark.bss_large_pc_copyToBuffer  thrpt   40  203 590 896.493 ± 7582752.024  ops/s // "can't beat that"
+    
+   */
+
+  @Benchmark
+  def bs_large_copyToBuffer(): Int = {
+    buf.flip()
+    bs_large.copyToBuffer(buf)
+  }
+
+  @Benchmark
+  def bss_large_copyToBuffer(): Int = {
+    buf.flip()
+    bss_large.copyToBuffer(buf)
+  }
+
+  /** Pre-compacted */
+  @Benchmark
+  def bss_large_pc_copyToBuffer(): Int = {
+    buf.flip()
+    bss_pc_large.copyToBuffer(buf)
+  }
+}
diff --git a/akka-bench-jmh/src/main/scala/akka/util/ByteString_decode_Benchmark.scala b/akka-bench-jmh/src/main/scala/akka/util/ByteString_decode_Benchmark.scala
new file mode 100644
index 0000000000..b606f251fe
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/util/ByteString_decode_Benchmark.scala
@@ -0,0 +1,64 @@
+/**
+ * Copyright (C) 2014-2016 Lightbend Inc. 
+ */
+package akka.util
+
+import java.nio.charset.Charset
+import java.util.concurrent.TimeUnit
+
+import akka.util.ByteString.{ ByteString1C, ByteStrings }
+import org.openjdk.jmh.annotations._
+
+@State(Scope.Benchmark)
+@Measurement(timeUnit = TimeUnit.MILLISECONDS)
+class ByteString_decode_Benchmark {
+
+  val _bs_large = ByteString(Array.ofDim[Byte](1024 * 4))
+
+  val bs_large = ByteString(Array.ofDim[Byte](1024 * 4 * 4))
+
+  val bss_large = ByteStrings(Vector.fill(4)(bs_large.asInstanceOf[ByteString1C].toByteString1), 4 * bs_large.length)
+  val bc_large = bss_large.compact // compacted
+
+  val utf8String = "utf-8"
+  val utf8 = Charset.forName(utf8String)
+
+  /*
+    Using Charset helps a bit, but nothing impressive:
+  
+    [info] ByteString_decode_Benchmark.bc_large_decodeString_stringCharset_utf8        thrpt   20  21 612.293 ±  825.099  ops/s
+      =>
+    [info] ByteString_decode_Benchmark.bc_large_decodeString_charsetCharset_utf8       thrpt   20  22 473.372 ±  851.597  ops/s
+    
+    
+    [info] ByteString_decode_Benchmark.bs_large_decodeString_stringCharset_utf8        thrpt   20  84 443.674 ± 3723.987  ops/s
+      =>
+    [info] ByteString_decode_Benchmark.bs_large_decodeString_charsetCharset_utf8       thrpt   20  93 865.033 ± 2052.476  ops/s
+    
+    
+    [info] ByteString_decode_Benchmark.bss_large_decodeString_stringCharset_utf8       thrpt   20  14 886.553 ±  326.752  ops/s
+      =>
+    [info] ByteString_decode_Benchmark.bss_large_decodeString_charsetCharset_utf8      thrpt   20  16 031.670 ±  474.565  ops/s
+   */
+
+  @Benchmark
+  def bc_large_decodeString_stringCharset_utf8: String =
+    bc_large.decodeString(utf8String)
+  @Benchmark
+  def bs_large_decodeString_stringCharset_utf8: String =
+    bs_large.decodeString(utf8String)
+  @Benchmark
+  def bss_large_decodeString_stringCharset_utf8: String =
+    bss_large.decodeString(utf8String)
+
+  @Benchmark
+  def bc_large_decodeString_charsetCharset_utf8: String =
+    bc_large.decodeString(utf8)
+  @Benchmark
+  def bs_large_decodeString_charsetCharset_utf8: String =
+    bs_large.decodeString(utf8)
+  @Benchmark
+  def bss_large_decodeString_charsetCharset_utf8: String =
+    bss_large.decodeString(utf8)
+
+}
diff --git a/akka-bench-jmh/src/main/scala/akka/util/ByteString_dropSliceTake_Benchmark.scala b/akka-bench-jmh/src/main/scala/akka/util/ByteString_dropSliceTake_Benchmark.scala
new file mode 100644
index 0000000000..90ff47a807
--- /dev/null
+++ b/akka-bench-jmh/src/main/scala/akka/util/ByteString_dropSliceTake_Benchmark.scala
@@ -0,0 +1,156 @@
+/**
+ * Copyright (C) 2014-2016 Lightbend Inc. 
+ */
+package akka.util
+
+import java.nio.ByteBuffer
+import java.util.concurrent.TimeUnit
+
+import akka.util.ByteString.{ ByteString1C, ByteStrings }
+import org.openjdk.jmh.annotations._
+
+@State(Scope.Benchmark)
+@Measurement(timeUnit = TimeUnit.MILLISECONDS)
+class ByteString_dropSliceTake_Benchmark {
+
+  val _bs_mini = ByteString(Array.ofDim[Byte](128 * 4))
+  val _bs_small = ByteString(Array.ofDim[Byte](1024 * 1))
+  val _bs_large = ByteString(Array.ofDim[Byte](1024 * 4))
+
+  val bs_mini = ByteString(Array.ofDim[Byte](128 * 4 * 4))
+  val bs_small = ByteString(Array.ofDim[Byte](1024 * 1 * 4))
+  val bs_large = ByteString(Array.ofDim[Byte](1024 * 4 * 4))
+
+  val bss_mini = ByteStrings(Vector.fill(4)(bs_mini.asInstanceOf[ByteString1C].toByteString1), 4 * bs_mini.length)
+  val bss_small = ByteStrings(Vector.fill(4)(bs_small.asInstanceOf[ByteString1C].toByteString1), 4 * bs_small.length)
+  val bss_large = ByteStrings(Vector.fill(4)(bs_large.asInstanceOf[ByteString1C].toByteString1), 4 * bs_large.length)
+  val bss_pc_large = bss_large.compact
+
+  /*
+   --------------------------------- BASELINE -------------------------------------------------------------------- 
+   [info] Benchmark                                                         Mode  Cnt            Score         Error  Units
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_100        thrpt   20  111 122 621.983 ± 6172679.160  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_256        thrpt   20  110 238 003.870 ± 4042572.908  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_2000       thrpt   20  106 435 449.123 ± 2972282.531  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_100       thrpt   20    1 155 292.430 ±   23096.219  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_256       thrpt   20    1 191 713.229 ±   15910.426  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_2000      thrpt   20    1 201 342.579 ±   21119.392  ops/s
+
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_drop_100             thrpt   20  108 252 561.824 ± 3841392.346  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_drop_256             thrpt   20  112 515 936.237 ± 5651549.124  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_drop_2000            thrpt   20  110 851 553.706 ± 3327510.108  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_18             thrpt   20      983 544.541 ±   46299.808  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_100            thrpt   20      875 345.433 ±   44760.533  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_256            thrpt   20      864 182.258 ±  111172.303  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_2000           thrpt   20      997 459.151 ±   33627.993  ops/s
+
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_slice_80_80          thrpt   20  112 299 538.691 ± 7259114.294  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_slice_129_129        thrpt   20  105 640 836.625 ± 9112709.942  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_slice_80_80         thrpt   20   10 868 202.262 ±  526537.133  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_slice_129_129       thrpt   20    9 429 199.802 ± 1321542.453  ops/s
+   
+   --------------------------------- AFTER -----------------------------------------------------------------------
+   
+   ------ TODAY –––––––
+   [info] Benchmark                                                         Mode  Cnt            Score         Error  Units
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_100        thrpt   20  126 091 961.654 ± 2813125.268  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_256        thrpt   20  118 393 394.350 ± 2934782.759  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_dropRight_2000       thrpt   20  119 183 386.004 ± 4445324.298  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_100       thrpt   20    8 813 065.392 ±  234570.880  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_256       thrpt   20    9 039 585.934 ±  297168.301  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_dropRight_2000      thrpt   20    9 629 458.168 ±  124846.904  ops/s
+   
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_drop_100             thrpt   20  111 666 137.955 ± 4846727.674  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_drop_256             thrpt   20  114 405 514.622 ± 4985750.805  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_drop_2000            thrpt   20  114 364 716.297 ± 2512280.603  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_18             thrpt   20   10 040 457.962 ±  527850.116  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_100            thrpt   20    9 184 934.769 ±  549140.840  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_256            thrpt   20   10 887 437.121 ±  195606.240  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_drop_2000           thrpt   20   10 725 300.292 ±  403470.413  ops/s
+   
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_slice_80_80          thrpt   20  233 017 314.148 ± 7070246.826  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bs_large_slice_129_129        thrpt   20  275 245 086.247 ± 4969752.048  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_slice_80_80         thrpt   20  264 963 420.976 ± 4259289.143  ops/s
+   [info] ByteString_dropSliceTake_Benchmark.bss_large_slice_129_129       thrpt   20  265 477 577.022 ± 4623974.283  ops/s
+   
+   */
+
+  // 18 == "http://example.com", a typical url length 
+
+  @Benchmark
+  def bs_large_drop_0: ByteString =
+    bs_large.drop(0)
+  @Benchmark
+  def bss_large_drop_0: ByteString =
+    bss_large.drop(0)
+
+  @Benchmark
+  def bs_large_drop_18: ByteString =
+    bs_large.drop(18)
+  @Benchmark
+  def bss_large_drop_18: ByteString =
+    bss_large.drop(18)
+
+  @Benchmark
+  def bs_large_drop_100: ByteString =
+    bs_large.drop(100)
+  @Benchmark
+  def bss_large_drop_100: ByteString =
+    bss_large.drop(100)
+
+  @Benchmark
+  def bs_large_drop_256: ByteString =
+    bs_large.drop(256)
+  @Benchmark
+  def bss_large_drop_256: ByteString =
+    bss_large.drop(256)
+
+  @Benchmark
+  def bs_large_drop_2000: ByteString =
+    bs_large.drop(2000)
+  @Benchmark
+  def bss_large_drop_2000: ByteString =
+    bss_large.drop(2000)
+
+  /* these force 2 array drops, and 1 element drop inside the 2nd to first/last; can be considered as "bad case" */
+
+  @Benchmark
+  def bs_large_slice_129_129: ByteString =
+    bs_large.slice(129, 129)
+  @Benchmark
+  def bss_large_slice_129_129: ByteString =
+    bss_large.slice(129, 129)
+
+  /* these only move the indexes, don't drop any arrays "happy case" */
+
+  @Benchmark
+  def bs_large_slice_80_80: ByteString =
+    bs_large.slice(80, 80)
+  @Benchmark
+  def bss_large_slice_80_80: ByteString =
+    bss_large.slice(80, 80)
+
+  // drop right ---
+
+  @Benchmark
+  def bs_large_dropRight_100: ByteString =
+    bs_large.dropRight(100)
+  @Benchmark
+  def bss_large_dropRight_100: ByteString =
+    bss_large.dropRight(100)
+
+  @Benchmark
+  def bs_large_dropRight_256: ByteString =
+    bs_large.dropRight(256)
+  @Benchmark
+  def bss_large_dropRight_256: ByteString =
+    bss_large.dropRight(256)
+
+  @Benchmark
+  def bs_large_dropRight_2000: ByteString =
+    bs_large.dropRight(2000)
+  @Benchmark
+  def bss_large_dropRight_2000: ByteString =
+    bss_large.dropRight(2000)
+
+}
diff --git a/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala b/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala
index b673fa9057..1c4f03a16a 100644
--- a/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala
+++ b/akka-camel/src/test/scala/akka/camel/ConsumerIntegrationTest.scala
@@ -123,13 +123,13 @@ class ConsumerIntegrationTest extends WordSpec with Matchers with NonSharedCamel
     }
 
     "Error passing consumer supports redelivery through route modification" in {
-      val ref = start(new FailingOnceConsumer("direct:failing-once-concumer") {
+      val ref = start(new FailingOnceConsumer("direct:failing-once-consumer") {
         override def onRouteDefinition = (rd: RouteDefinition) ⇒ {
-          rd.onException(classOf[TestException]).maximumRedeliveries(1).end
+          rd.onException(classOf[TestException]).redeliveryDelay(0L).maximumRedeliveries(1).end
         }
       }, name = "direct-failing-once-consumer")
       filterEvents(EventFilter[TestException](occurrences = 1)) {
-        camel.sendTo("direct:failing-once-concumer", msg = "hello") should ===("accepted: hello")
+        camel.sendTo("direct:failing-once-consumer", msg = "hello") should ===("accepted: hello")
       }
       stop(ref)
     }
diff --git a/akka-cluster-sharding/src/main/resources/reference.conf b/akka-cluster-sharding/src/main/resources/reference.conf
index 8206019d46..c89d0036a5 100644
--- a/akka-cluster-sharding/src/main/resources/reference.conf
+++ b/akka-cluster-sharding/src/main/resources/reference.conf
@@ -94,6 +94,20 @@ akka.cluster.sharding {
   # works only for state-store-mode = "ddata"
   updating-state-timeout = 5 s
 
+  # The shard uses this strategy to determines how to recover the underlying entity actors. The strategy is only used
+  # by the persistent shard when rebalancing or restarting. The value can either be "all" or "constant". The "all"
+  # strategy start all the underlying entity actors at the same time. The constant strategy will start the underlying
+  # entity actors at a fix rate. The default strategy "all".
+  entity-recovery-strategy = "all"
+
+  # Default settings for the constant rate entity recovery strategy
+  entity-recovery-constant-rate-strategy {
+    # Sets the frequency at which a batch of entity actors is started.
+    frequency = 100 ms
+    # Sets the number of entity actors to be restart at a particular interval
+    number-of-entities = 5
+  }
+
   # Settings for the coordinator singleton. Same layout as akka.cluster.singleton.
   # The "role" of the singleton configuration is not used. The singleton role will
   # be the same as "akka.cluster.sharding.role".
diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala
index 631d064939..f063268ccc 100644
--- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala
+++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterSharding.scala
@@ -258,7 +258,7 @@ class ClusterSharding(system: ExtendedActorSystem) extends Extension {
    * @param entityProps the `Props` of the entity actors that will be created by the `ShardRegion`
    * @param settings configuration settings, see [[ClusterShardingSettings]]
    * @param messageExtractor functions to extract the entity id, shard id, and the message to send to the
-   *   entity from the incoming message
+   *   entity from the incoming message, see [[ShardRegion.MessageExtractor]]
    * @param allocationStrategy possibility to use a custom shard allocation and
    *   rebalancing logic
    * @param handOffStopMessage the message that will be sent to entities when they are to be stopped
diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala
index b77fb80eb8..230cfc4a5e 100644
--- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala
+++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ClusterShardingSettings.scala
@@ -38,7 +38,10 @@ object ClusterShardingSettings {
       leastShardAllocationMaxSimultaneousRebalance =
         config.getInt("least-shard-allocation-strategy.max-simultaneous-rebalance"),
       waitingForStateTimeout = config.getDuration("waiting-for-state-timeout", MILLISECONDS).millis,
-      updatingStateTimeout = config.getDuration("updating-state-timeout", MILLISECONDS).millis)
+      updatingStateTimeout = config.getDuration("updating-state-timeout", MILLISECONDS).millis,
+      entityRecoveryStrategy = config.getString("entity-recovery-strategy"),
+      entityRecoveryConstantRateStrategyFrequency = config.getDuration("entity-recovery-constant-rate-strategy.frequency", MILLISECONDS).millis,
+      entityRecoveryConstantRateStrategyNumberOfEntities = config.getInt("entity-recovery-constant-rate-strategy.number-of-entities"))
 
     val coordinatorSingletonSettings = ClusterSingletonManagerSettings(config.getConfig("coordinator-singleton"))
 
@@ -71,19 +74,62 @@ object ClusterShardingSettings {
     if (role == "") None else Option(role)
 
   class TuningParameters(
-    val coordinatorFailureBackoff:                    FiniteDuration,
-    val retryInterval:                                FiniteDuration,
-    val bufferSize:                                   Int,
-    val handOffTimeout:                               FiniteDuration,
-    val shardStartTimeout:                            FiniteDuration,
-    val shardFailureBackoff:                          FiniteDuration,
-    val entityRestartBackoff:                         FiniteDuration,
-    val rebalanceInterval:                            FiniteDuration,
-    val snapshotAfter:                                Int,
-    val leastShardAllocationRebalanceThreshold:       Int,
-    val leastShardAllocationMaxSimultaneousRebalance: Int,
-    val waitingForStateTimeout:                       FiniteDuration,
-    val updatingStateTimeout:                         FiniteDuration)
+    val coordinatorFailureBackoff:                          FiniteDuration,
+    val retryInterval:                                      FiniteDuration,
+    val bufferSize:                                         Int,
+    val handOffTimeout:                                     FiniteDuration,
+    val shardStartTimeout:                                  FiniteDuration,
+    val shardFailureBackoff:                                FiniteDuration,
+    val entityRestartBackoff:                               FiniteDuration,
+    val rebalanceInterval:                                  FiniteDuration,
+    val snapshotAfter:                                      Int,
+    val leastShardAllocationRebalanceThreshold:             Int,
+    val leastShardAllocationMaxSimultaneousRebalance:       Int,
+    val waitingForStateTimeout:                             FiniteDuration,
+    val updatingStateTimeout:                               FiniteDuration,
+    val entityRecoveryStrategy:                             String,
+    val entityRecoveryConstantRateStrategyFrequency:        FiniteDuration,
+    val entityRecoveryConstantRateStrategyNumberOfEntities: Int) {
+
+    require(
+      entityRecoveryStrategy == "all" || entityRecoveryStrategy == "constant",
+      s"Unknown 'entity-recovery-strategy' [$entityRecoveryStrategy], valid values are 'all' or 'constant'")
+
+    // included for binary compatibility
+    def this(
+      coordinatorFailureBackoff:                    FiniteDuration,
+      retryInterval:                                FiniteDuration,
+      bufferSize:                                   Int,
+      handOffTimeout:                               FiniteDuration,
+      shardStartTimeout:                            FiniteDuration,
+      shardFailureBackoff:                          FiniteDuration,
+      entityRestartBackoff:                         FiniteDuration,
+      rebalanceInterval:                            FiniteDuration,
+      snapshotAfter:                                Int,
+      leastShardAllocationRebalanceThreshold:       Int,
+      leastShardAllocationMaxSimultaneousRebalance: Int,
+      waitingForStateTimeout:                       FiniteDuration,
+      updatingStateTimeout:                         FiniteDuration) = {
+      this(
+        coordinatorFailureBackoff,
+        retryInterval,
+        bufferSize,
+        handOffTimeout,
+        shardStartTimeout,
+        shardFailureBackoff,
+        entityRestartBackoff,
+        rebalanceInterval,
+        snapshotAfter,
+        leastShardAllocationRebalanceThreshold,
+        leastShardAllocationMaxSimultaneousRebalance,
+        waitingForStateTimeout,
+        updatingStateTimeout,
+        "all",
+        100 milliseconds,
+        5
+      )
+    }
+  }
 }
 
 /**
diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/Shard.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/Shard.scala
index e8381da165..a1d42d1034 100644
--- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/Shard.scala
+++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/Shard.scala
@@ -6,16 +6,19 @@ package akka.cluster.sharding
 import java.net.URLEncoder
 import akka.actor.ActorLogging
 import akka.actor.ActorRef
+import akka.actor.ActorSystem
 import akka.actor.Deploy
 import akka.actor.Props
 import akka.actor.Terminated
-import akka.cluster.sharding.Shard.{ ShardCommand }
+import akka.cluster.sharding.Shard.ShardCommand
 import akka.persistence.PersistentActor
 import akka.persistence.SnapshotOffer
 import akka.actor.Actor
 import akka.persistence.RecoveryCompleted
 import akka.persistence.SaveSnapshotFailure
 import akka.persistence.SaveSnapshotSuccess
+import scala.concurrent.Future
+import scala.concurrent.duration.FiniteDuration
 
 /**
  * INTERNAL API
@@ -35,6 +38,12 @@ private[akka] object Shard {
    */
   final case class RestartEntity(entity: EntityId) extends ShardCommand
 
+  /**
+   * When initialising a shard with remember entities enabled the following message is used
+   * to restart batches of entity actors at a time.
+   */
+  final case class RestartEntities(entity: Set[EntityId]) extends ShardCommand
+
   /**
    * A case class which represents a state change for the Shard
    */
@@ -116,7 +125,7 @@ private[akka] class Shard(
 
   import ShardRegion.{ handOffStopperProps, EntityId, Msg, Passivate, ShardInitialized }
   import ShardCoordinator.Internal.{ HandOff, ShardStopped }
-  import Shard.{ State, RestartEntity, EntityStopped, EntityStarted }
+  import Shard.{ State, RestartEntity, RestartEntities, EntityStopped, EntityStarted }
   import Shard.{ ShardQuery, GetCurrentShardState, CurrentShardState, GetShardStats, ShardStats }
   import akka.cluster.sharding.ShardCoordinator.Internal.CoordinatorMessage
   import akka.cluster.sharding.ShardRegion.ShardRegionCommand
@@ -151,7 +160,8 @@ private[akka] class Shard(
   }
 
   def receiveShardCommand(msg: ShardCommand): Unit = msg match {
-    case RestartEntity(id) ⇒ getEntity(id)
+    case RestartEntity(id)    ⇒ getEntity(id)
+    case RestartEntities(ids) ⇒ ids foreach getEntity
   }
 
   def receiveShardRegionCommand(msg: ShardRegionCommand): Unit = msg match {
@@ -313,8 +323,19 @@ private[akka] class PersistentShard(
   with PersistentActor with ActorLogging {
 
   import ShardRegion.{ EntityId, Msg }
-  import Shard.{ State, RestartEntity, EntityStopped, EntityStarted }
+  import Shard.{ State, RestartEntity, RestartEntities, EntityStopped, EntityStarted }
   import settings.tuningParameters._
+  import akka.pattern.pipe
+
+  val rememberedEntitiesRecoveryStrategy: EntityRecoveryStrategy =
+    entityRecoveryStrategy match {
+      case "all" ⇒ EntityRecoveryStrategy.allStrategy()
+      case "constant" ⇒ EntityRecoveryStrategy.constantStrategy(
+        context.system,
+        entityRecoveryConstantRateStrategyFrequency,
+        entityRecoveryConstantRateStrategyNumberOfEntities
+      )
+    }
 
   override def persistenceId = s"/sharding/${typeName}Shard/${shardId}"
 
@@ -344,7 +365,7 @@ private[akka] class PersistentShard(
     case EntityStopped(id)                 ⇒ state = state.copy(state.entities - id)
     case SnapshotOffer(_, snapshot: State) ⇒ state = snapshot
     case RecoveryCompleted ⇒
-      state.entities foreach getEntity
+      restartRememberedEntities()
       super.initialized()
       log.debug("Shard recovery completed {}", shardId)
   }
@@ -388,4 +409,45 @@ private[akka] class PersistentShard(
     }
   }
 
+  private def restartRememberedEntities(): Unit = {
+    rememberedEntitiesRecoveryStrategy.recoverEntities(state.entities).foreach { scheduledRecovery ⇒
+      import context.dispatcher
+      scheduledRecovery.filter(_.nonEmpty).map(RestartEntities).pipeTo(self)
+    }
+  }
+}
+
+object EntityRecoveryStrategy {
+  def allStrategy(): EntityRecoveryStrategy = new AllAtOnceEntityRecoveryStrategy()
+
+  def constantStrategy(actorSystem: ActorSystem, frequency: FiniteDuration, numberOfEntities: Int): EntityRecoveryStrategy =
+    new ConstantRateEntityRecoveryStrategy(actorSystem, frequency, numberOfEntities)
+}
+
+trait EntityRecoveryStrategy {
+  import ShardRegion.EntityId
+  import scala.concurrent.Future
+
+  def recoverEntities(entities: Set[EntityId]): Set[Future[Set[EntityId]]]
+}
+
+final class AllAtOnceEntityRecoveryStrategy extends EntityRecoveryStrategy {
+  import ShardRegion.EntityId
+  override def recoverEntities(entities: Set[EntityId]): Set[Future[Set[EntityId]]] =
+    if (entities.isEmpty) Set.empty else Set(Future.successful(entities))
+}
+
+final class ConstantRateEntityRecoveryStrategy(actorSystem: ActorSystem, frequency: FiniteDuration, numberOfEntities: Int) extends EntityRecoveryStrategy {
+  import ShardRegion.EntityId
+  import akka.pattern.after
+  import actorSystem.dispatcher
+
+  override def recoverEntities(entities: Set[EntityId]): Set[Future[Set[EntityId]]] =
+    entities.grouped(numberOfEntities).foldLeft((frequency, Set[Future[Set[EntityId]]]())) {
+      case ((interval, scheduledEntityIds), entityIds) ⇒
+        (interval + frequency, scheduledEntityIds + scheduleEntities(interval, entityIds))
+    }._2
+
+  private def scheduleEntities(interval: FiniteDuration, entityIds: Set[EntityId]) =
+    after(interval, actorSystem.scheduler)(Future.successful[Set[EntityId]](entityIds))
 }
diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardCoordinator.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardCoordinator.scala
index 370a635f1f..3ba933ee7d 100644
--- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardCoordinator.scala
+++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardCoordinator.scala
@@ -153,7 +153,7 @@ object ShardCoordinator {
           case (_, v) ⇒ v.filterNot(s ⇒ rebalanceInProgress(s))
         }.maxBy(_.size)
         if (mostShards.size - leastShards.size >= rebalanceThreshold)
-          Future.successful(Set(mostShards.head))
+          Future.successful(mostShards.take(maxSimultaneousRebalance - rebalanceInProgress.size).toSet)
         else
           emptyRebalanceResult
       } else emptyRebalanceResult
@@ -367,8 +367,8 @@ object ShardCoordinator {
     }
 
     def stoppingShard: Receive = {
-      case ShardStopped(shard) ⇒ done(ok = true)
-      case ReceiveTimeout      ⇒ done(ok = false)
+      case ShardStopped(`shard`) ⇒ done(ok = true)
+      case ReceiveTimeout        ⇒ done(ok = false)
     }
 
     def done(ok: Boolean): Unit = {
diff --git a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala
index c73eeb8f64..5593191c06 100644
--- a/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala
+++ b/akka-cluster-sharding/src/main/scala/akka/cluster/sharding/ShardRegion.scala
@@ -413,8 +413,8 @@ class ShardRegion(
     case msg: CoordinatorMessage                 ⇒ receiveCoordinatorMessage(msg)
     case cmd: ShardRegionCommand                 ⇒ receiveCommand(cmd)
     case query: ShardRegionQuery                 ⇒ receiveQuery(query)
-    case msg if extractEntityId.isDefinedAt(msg) ⇒ deliverMessage(msg, sender())
     case msg: RestartShard                       ⇒ deliverMessage(msg, sender())
+    case msg if extractEntityId.isDefinedAt(msg) ⇒ deliverMessage(msg, sender())
   }
 
   def receiveClusterState(state: CurrentClusterState): Unit = {
@@ -454,7 +454,7 @@ class ShardRegion(
       regionByShard.get(shard) match {
         case Some(r) if r == self && ref != self ⇒
           // should not happen, inconsistency between ShardRegion and ShardCoordinator
-          throw new IllegalStateException(s"Unexpected change of shard [${shard}] from self to [${ref}]")
+          throw new IllegalStateException(s"Unexpected change of shard [$shard] from self to [$ref]")
         case _ ⇒
       }
       regionByShard = regionByShard.updated(shard, ref)
@@ -546,7 +546,7 @@ class ShardRegion(
   }
 
   def receiveTerminated(ref: ActorRef): Unit = {
-    if (coordinator.exists(_ == ref))
+    if (coordinator.contains(ref))
       coordinator = None
     else if (regions.contains(ref)) {
       val shards = regions(ref)
@@ -711,7 +711,7 @@ class ShardRegion(
           case Some(ref) ⇒
             log.debug("Forwarding request for shard [{}] to [{}]", shardId, ref)
             ref.tell(msg, snd)
-          case None if (shardId == null || shardId == "") ⇒
+          case None if shardId == null || shardId == "" ⇒
             log.warning("Shard must not be empty, dropping message [{}]", msg.getClass.getName)
             context.system.deadLetters ! msg
           case None ⇒
diff --git a/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala b/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala
index cc9e19b88a..36a251628d 100644
--- a/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala
+++ b/akka-cluster-sharding/src/multi-jvm/scala/akka/cluster/sharding/ClusterShardingSpec.scala
@@ -114,7 +114,11 @@ object ClusterShardingSpec {
 
 }
 
-abstract class ClusterShardingSpecConfig(val mode: String) extends MultiNodeConfig {
+abstract class ClusterShardingSpecConfig(
+  val mode:                   String,
+  val entityRecoveryStrategy: String = "all")
+  extends MultiNodeConfig {
+
   val controller = role("controller")
   val first = role("first")
   val second = role("second")
@@ -144,6 +148,11 @@ abstract class ClusterShardingSpecConfig(val mode: String) extends MultiNodeConf
       entity-restart-backoff = 1s
       rebalance-interval = 2 s
       state-store-mode = "$mode"
+      entity-recovery-strategy = "$entityRecoveryStrategy"
+      entity-recovery-constant-rate-strategy {
+        frequency = 1 ms
+        number-of-entities = 1
+      }
       least-shard-allocation-strategy {
         rebalance-threshold = 2
         max-simultaneous-rebalance = 1
@@ -177,9 +186,19 @@ object ClusterShardingDocCode {
 
 object PersistentClusterShardingSpecConfig extends ClusterShardingSpecConfig("persistence")
 object DDataClusterShardingSpecConfig extends ClusterShardingSpecConfig("ddata")
+object PersistentClusterShardingWithEntityRecoverySpecConfig extends ClusterShardingSpecConfig(
+  "persistence",
+  "all"
+)
+object DDataClusterShardingWithEntityRecoverySpecConfig extends ClusterShardingSpecConfig(
+  "ddata",
+  "constant"
+)
 
 class PersistentClusterShardingSpec extends ClusterShardingSpec(PersistentClusterShardingSpecConfig)
 class DDataClusterShardingSpec extends ClusterShardingSpec(DDataClusterShardingSpecConfig)
+class PersistentClusterShardingWithEntityRecoverySpec extends ClusterShardingSpec(PersistentClusterShardingWithEntityRecoverySpecConfig)
+class DDataClusterShardingWithEntityRecoverySpec extends ClusterShardingSpec(DDataClusterShardingWithEntityRecoverySpecConfig)
 
 class PersistentClusterShardingMultiJvmNode1 extends PersistentClusterShardingSpec
 class PersistentClusterShardingMultiJvmNode2 extends PersistentClusterShardingSpec
@@ -197,6 +216,22 @@ class DDataClusterShardingMultiJvmNode5 extends DDataClusterShardingSpec
 class DDataClusterShardingMultiJvmNode6 extends DDataClusterShardingSpec
 class DDataClusterShardingMultiJvmNode7 extends DDataClusterShardingSpec
 
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode1 extends PersistentClusterShardingSpec
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode2 extends PersistentClusterShardingSpec
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode3 extends PersistentClusterShardingSpec
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode4 extends PersistentClusterShardingSpec
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode5 extends PersistentClusterShardingSpec
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode6 extends PersistentClusterShardingSpec
+class PersistentClusterShardingWithEntityRecoveryMultiJvmNode7 extends PersistentClusterShardingSpec
+
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode1 extends DDataClusterShardingSpec
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode2 extends DDataClusterShardingSpec
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode3 extends DDataClusterShardingSpec
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode4 extends DDataClusterShardingSpec
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode5 extends DDataClusterShardingSpec
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode6 extends DDataClusterShardingSpec
+class DDataClusterShardingWithEntityRecoveryMultiJvmNode7 extends DDataClusterShardingSpec
+
 abstract class ClusterShardingSpec(config: ClusterShardingSpecConfig) extends MultiNodeSpec(config) with STMultiNodeSpec with ImplicitSender {
   import ClusterShardingSpec._
   import config._
diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/AllAtOnceEntityRecoveryStrategySpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/AllAtOnceEntityRecoveryStrategySpec.scala
new file mode 100644
index 0000000000..b9b7b2b562
--- /dev/null
+++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/AllAtOnceEntityRecoveryStrategySpec.scala
@@ -0,0 +1,37 @@
+package akka.cluster.sharding
+
+import akka.cluster.sharding.ShardRegion.EntityId
+import akka.testkit.AkkaSpec
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+import scala.language.postfixOps
+
+class AllAtOnceEntityRecoveryStrategySpec extends AkkaSpec {
+  val strategy = EntityRecoveryStrategy.allStrategy()
+
+  import system.dispatcher
+
+  "AllAtOnceEntityRecoveryStrategy" must {
+    "recover entities" in {
+      val entities = Set[EntityId]("1", "2", "3", "4", "5")
+      val startTime = System.currentTimeMillis()
+      val resultWithTimes = strategy.recoverEntities(entities).map(
+        _.map(entityIds ⇒ (entityIds, System.currentTimeMillis() - startTime))
+      )
+
+      val result = Await.result(Future.sequence(resultWithTimes), 4 seconds).toList.sortWith(_._2 < _._2)
+      result.size should ===(1)
+
+      val scheduledEntities = result.map(_._1)
+      scheduledEntities.head should ===(entities)
+
+      val times = result.map(_._2)
+      times.head should ===(0L +- 20L)
+    }
+
+    "not recover when no entities to recover" in {
+      val result = strategy.recoverEntities(Set[EntityId]())
+      result.size should ===(0)
+    }
+  }
+}
\ No newline at end of file
diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ConstantRateEntityRecoveryStrategySpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ConstantRateEntityRecoveryStrategySpec.scala
new file mode 100644
index 0000000000..454218ca0f
--- /dev/null
+++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/ConstantRateEntityRecoveryStrategySpec.scala
@@ -0,0 +1,45 @@
+package akka.cluster.sharding
+
+import akka.cluster.sharding.ShardRegion.EntityId
+import akka.testkit.AkkaSpec
+
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+import scala.language.postfixOps
+
+class ConstantRateEntityRecoveryStrategySpec extends AkkaSpec {
+
+  import system.dispatcher
+
+  val strategy = EntityRecoveryStrategy.constantStrategy(system, 500 millis, 2)
+
+  "ConstantRateEntityRecoveryStrategy" must {
+    "recover entities" in {
+      val entities = Set[EntityId]("1", "2", "3", "4", "5")
+      val startTime = System.currentTimeMillis()
+      val resultWithTimes = strategy.recoverEntities(entities).map(
+        _.map(entityIds ⇒ (entityIds, System.currentTimeMillis() - startTime))
+      )
+
+      val result = Await.result(Future.sequence(resultWithTimes), 4 seconds).toList.sortWith(_._2 < _._2)
+      result.size should ===(3)
+
+      val scheduledEntities = result.map(_._1)
+      scheduledEntities.head.size should ===(2)
+      scheduledEntities(1).size should ===(2)
+      scheduledEntities(2).size should ===(1)
+      scheduledEntities.foldLeft(Set[EntityId]())(_ ++ _) should ===(entities)
+
+      val times = result.map(_._2)
+
+      times.head should ===(500L +- 30L)
+      times(1) should ===(1000L +- 30L)
+      times(2) should ===(1500L +- 30L)
+    }
+
+    "not recover when no entities to recover" in {
+      val result = strategy.recoverEntities(Set[EntityId]())
+      result.size should ===(0)
+    }
+  }
+}
diff --git a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/LeastShardAllocationStrategySpec.scala b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/LeastShardAllocationStrategySpec.scala
index 8717e9eb22..54fd1ef339 100644
--- a/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/LeastShardAllocationStrategySpec.scala
+++ b/akka-cluster-sharding/src/test/scala/akka/cluster/sharding/LeastShardAllocationStrategySpec.scala
@@ -31,14 +31,23 @@ class LeastShardAllocationStrategySpec extends AkkaSpec {
       Await.result(allocationStrategy.rebalance(allocations, Set.empty), 3.seconds) should ===(Set.empty[String])
 
       val allocations2 = allocations.updated(regionB, Vector("shard2", "shard3", "shard4"))
-      Await.result(allocationStrategy.rebalance(allocations2, Set.empty), 3.seconds) should ===(Set("shard2"))
+      Await.result(allocationStrategy.rebalance(allocations2, Set.empty), 3.seconds) should ===(Set("shard2", "shard3"))
       Await.result(allocationStrategy.rebalance(allocations2, Set("shard4")), 3.seconds) should ===(Set.empty[String])
 
       val allocations3 = allocations2.updated(regionA, Vector("shard1", "shard5", "shard6"))
       Await.result(allocationStrategy.rebalance(allocations3, Set("shard1")), 3.seconds) should ===(Set("shard2"))
     }
 
-    "must limit number of simultanious rebalance" in {
+    "rebalance multiple shards if max simultaneous rebalances is not exceeded" in {
+      val allocations = Map(
+        regionA → Vector("shard1"),
+        regionB → Vector("shard2", "shard3", "shard4", "shard5", "shard6"),
+        regionC → Vector.empty)
+
+      Await.result(allocationStrategy.rebalance(allocations, Set.empty), 3.seconds) should ===(Set("shard2", "shard3"))
+      Await.result(allocationStrategy.rebalance(allocations, Set("shard2", "shard3")), 3.seconds) should ===(Set.empty[String])
+    }
+    "limit number of simultaneous rebalance" in {
       val allocations = Map(
         regionA → Vector("shard1"),
         regionB → Vector("shard2", "shard3", "shard4", "shard5", "shard6"), regionC → Vector.empty)
diff --git a/akka-cluster-tools/src/main/scala/akka/cluster/singleton/ClusterSingletonManager.scala b/akka-cluster-tools/src/main/scala/akka/cluster/singleton/ClusterSingletonManager.scala
index 0383f294c3..848a91bc92 100644
--- a/akka-cluster-tools/src/main/scala/akka/cluster/singleton/ClusterSingletonManager.scala
+++ b/akka-cluster-tools/src/main/scala/akka/cluster/singleton/ClusterSingletonManager.scala
@@ -23,6 +23,7 @@ import akka.cluster.Member
 import akka.cluster.MemberStatus
 import akka.AkkaException
 import akka.actor.NoSerializationVerificationNeeded
+import akka.cluster.UniqueAddress
 
 object ClusterSingletonManagerSettings {
 
@@ -184,11 +185,11 @@ object ClusterSingletonManager {
     case object End extends State
 
     case object Uninitialized extends Data
-    final case class YoungerData(oldestOption: Option[Address]) extends Data
-    final case class BecomingOldestData(previousOldestOption: Option[Address]) extends Data
+    final case class YoungerData(oldestOption: Option[UniqueAddress]) extends Data
+    final case class BecomingOldestData(previousOldestOption: Option[UniqueAddress]) extends Data
     final case class OldestData(singleton: ActorRef, singletonTerminated: Boolean = false) extends Data
     final case class WasOldestData(singleton: ActorRef, singletonTerminated: Boolean,
-                                   newOldestOption: Option[Address]) extends Data
+                                   newOldestOption: Option[UniqueAddress]) extends Data
     final case class HandingOverData(singleton: ActorRef, handOverTo: Option[ActorRef]) extends Data
     case object EndData extends Data
     final case class DelayedMemberRemoved(member: Member)
@@ -205,9 +206,9 @@ object ClusterSingletonManager {
       /**
        * The first event, corresponding to CurrentClusterState.
        */
-      final case class InitialOldestState(oldest: Option[Address], safeToBeOldest: Boolean)
+      final case class InitialOldestState(oldest: Option[UniqueAddress], safeToBeOldest: Boolean)
 
-      final case class OldestChanged(oldest: Option[Address])
+      final case class OldestChanged(oldest: Option[UniqueAddress])
     }
 
     /**
@@ -245,14 +246,14 @@ object ClusterSingletonManager {
         block()
         val after = membersByAge.headOption
         if (before != after)
-          changes :+= OldestChanged(after.map(_.address))
+          changes :+= OldestChanged(after.map(_.uniqueAddress))
       }
 
       def handleInitial(state: CurrentClusterState): Unit = {
         membersByAge = immutable.SortedSet.empty(ageOrdering) union state.members.filter(m ⇒
           (m.status == MemberStatus.Up || m.status == MemberStatus.Leaving) && matchingRole(m))
         val safeToBeOldest = !state.members.exists { m ⇒ (m.status == MemberStatus.Down || m.status == MemberStatus.Exiting) }
-        val initial = InitialOldestState(membersByAge.headOption.map(_.address), safeToBeOldest)
+        val initial = InitialOldestState(membersByAge.headOption.map(_.uniqueAddress), safeToBeOldest)
         changes :+= initial
       }
 
@@ -376,7 +377,7 @@ class ClusterSingletonManager(
   import FSM.`→`
 
   val cluster = Cluster(context.system)
-  val selfAddressOption = Some(cluster.selfAddress)
+  val selfUniqueAddressOption = Some(cluster.selfUniqueAddress)
   import cluster.settings.LogInfo
 
   require(
@@ -406,13 +407,13 @@ class ClusterSingletonManager(
   var selfExited = false
 
   // keep track of previously removed members
-  var removed = Map.empty[Address, Deadline]
+  var removed = Map.empty[UniqueAddress, Deadline]
 
-  def addRemoved(address: Address): Unit =
-    removed += address → (Deadline.now + 15.minutes)
+  def addRemoved(node: UniqueAddress): Unit =
+    removed += node → (Deadline.now + 15.minutes)
 
   def cleanupOverdueNotMemberAnyMore(): Unit = {
-    removed = removed filter { case (address, deadline) ⇒ deadline.hasTimeLeft }
+    removed = removed filter { case (_, deadline) ⇒ deadline.hasTimeLeft }
   }
 
   def logInfo(message: String): Unit =
@@ -463,10 +464,10 @@ class ClusterSingletonManager(
 
     case Event(InitialOldestState(oldestOption, safeToBeOldest), _) ⇒
       oldestChangedReceived = true
-      if (oldestOption == selfAddressOption && safeToBeOldest)
+      if (oldestOption == selfUniqueAddressOption && safeToBeOldest)
         // oldest immediately
         gotoOldest()
-      else if (oldestOption == selfAddressOption)
+      else if (oldestOption == selfUniqueAddressOption)
         goto(BecomingOldest) using BecomingOldestData(None)
       else
         goto(Younger) using YoungerData(oldestOption)
@@ -475,22 +476,22 @@ class ClusterSingletonManager(
   when(Younger) {
     case Event(OldestChanged(oldestOption), YoungerData(previousOldestOption)) ⇒
       oldestChangedReceived = true
-      if (oldestOption == selfAddressOption) {
-        logInfo("Younger observed OldestChanged: [{} -> myself]", previousOldestOption)
+      if (oldestOption == selfUniqueAddressOption) {
+        logInfo("Younger observed OldestChanged: [{} -> myself]", previousOldestOption.map(_.address))
         previousOldestOption match {
           case None                                 ⇒ gotoOldest()
           case Some(prev) if removed.contains(prev) ⇒ gotoOldest()
           case Some(prev) ⇒
-            peer(prev) ! HandOverToMe
+            peer(prev.address) ! HandOverToMe
             goto(BecomingOldest) using BecomingOldestData(previousOldestOption)
         }
       } else {
-        logInfo("Younger observed OldestChanged: [{} -> {}]", previousOldestOption, oldestOption)
+        logInfo("Younger observed OldestChanged: [{} -> {}]", previousOldestOption.map(_.address), oldestOption.map(_.address))
         getNextOldestChanged()
         stay using YoungerData(oldestOption)
       }
 
-    case Event(MemberRemoved(m, _), _) if m.address == cluster.selfAddress ⇒
+    case Event(MemberRemoved(m, _), _) if m.uniqueAddress == cluster.selfUniqueAddress ⇒
       logInfo("Self removed, stopping ClusterSingletonManager")
       stop()
 
@@ -498,11 +499,17 @@ class ClusterSingletonManager(
       scheduleDelayedMemberRemoved(m)
       stay
 
-    case Event(DelayedMemberRemoved(m), YoungerData(Some(previousOldest))) if m.address == previousOldest ⇒
+    case Event(DelayedMemberRemoved(m), YoungerData(Some(previousOldest))) if m.uniqueAddress == previousOldest ⇒
       logInfo("Previous oldest removed [{}]", m.address)
-      addRemoved(m.address)
+      addRemoved(m.uniqueAddress)
       // transition when OldestChanged
       stay using YoungerData(None)
+
+    case Event(HandOverToMe, _) ⇒
+      // this node was probably quickly restarted with same hostname:port,
+      // confirm that the old singleton instance has been stopped
+      sender() ! HandOverDone
+      stay
   }
 
   when(BecomingOldest) {
@@ -514,16 +521,16 @@ class ClusterSingletonManager(
       stay
 
     case Event(HandOverDone, BecomingOldestData(Some(previousOldest))) ⇒
-      if (sender().path.address == previousOldest)
+      if (sender().path.address == previousOldest.address)
         gotoOldest()
       else {
         logInfo(
           "Ignoring HandOverDone in BecomingOldest from [{}]. Expected previous oldest [{}]",
-          sender().path.address, previousOldest)
+          sender().path.address, previousOldest.address)
         stay
       }
 
-    case Event(MemberRemoved(m, _), _) if m.address == cluster.selfAddress ⇒
+    case Event(MemberRemoved(m, _), _) if m.uniqueAddress == cluster.selfUniqueAddress ⇒
       logInfo("Self removed, stopping ClusterSingletonManager")
       stop()
 
@@ -531,26 +538,39 @@ class ClusterSingletonManager(
       scheduleDelayedMemberRemoved(m)
       stay
 
-    case Event(DelayedMemberRemoved(m), BecomingOldestData(Some(previousOldest))) if m.address == previousOldest ⇒
-      logInfo("Previous oldest [{}] removed", previousOldest)
-      addRemoved(m.address)
+    case Event(DelayedMemberRemoved(m), BecomingOldestData(Some(previousOldest))) if m.uniqueAddress == previousOldest ⇒
+      logInfo("Previous oldest [{}] removed", previousOldest.address)
+      addRemoved(m.uniqueAddress)
       gotoOldest()
 
-    case Event(TakeOverFromMe, BecomingOldestData(None)) ⇒
-      sender() ! HandOverToMe
-      stay using BecomingOldestData(Some(sender().path.address))
-
-    case Event(TakeOverFromMe, BecomingOldestData(Some(previousOldest))) ⇒
-      if (previousOldest == sender().path.address) sender() ! HandOverToMe
-      else logInfo(
-        "Ignoring TakeOver request in BecomingOldest from [{}]. Expected previous oldest [{}]",
-        sender().path.address, previousOldest)
-      stay
+    case Event(TakeOverFromMe, BecomingOldestData(previousOldestOption)) ⇒
+      val senderAddress = sender().path.address
+      // it would have been better to include the UniqueAddress in the TakeOverFromMe message,
+      // but can't change due to backwards compatibility
+      cluster.state.members.collectFirst { case m if m.address == senderAddress ⇒ m.uniqueAddress } match {
+        case None ⇒
+          // from unknown node, ignore
+          logInfo(
+            "Ignoring TakeOver request from unknown node in BecomingOldest from [{}].", senderAddress)
+          stay
+        case Some(senderUniqueAddress) ⇒
+          previousOldestOption match {
+            case Some(previousOldest) ⇒
+              if (previousOldest == senderUniqueAddress) sender() ! HandOverToMe
+              else logInfo(
+                "Ignoring TakeOver request in BecomingOldest from [{}]. Expected previous oldest [{}]",
+                sender().path.address, previousOldest.address)
+              stay
+            case None ⇒
+              sender() ! HandOverToMe
+              stay using BecomingOldestData(Some(senderUniqueAddress))
+          }
+      }
 
     case Event(HandOverRetry(count), BecomingOldestData(previousOldestOption)) ⇒
       if (count <= maxHandOverRetries) {
-        logInfo("Retry [{}], sending HandOverToMe to [{}]", count, previousOldestOption)
-        previousOldestOption foreach { peer(_) ! HandOverToMe }
+        logInfo("Retry [{}], sending HandOverToMe to [{}]", count, previousOldestOption.map(_.address))
+        previousOldestOption.foreach(node ⇒ peer(node.address) ! HandOverToMe)
         setTimer(HandOverRetryTimer, HandOverRetry(count + 1), handOverRetryInterval, repeat = false)
         stay()
       } else if (previousOldestOption forall removed.contains) {
@@ -582,16 +602,19 @@ class ClusterSingletonManager(
   when(Oldest) {
     case Event(OldestChanged(oldestOption), OldestData(singleton, singletonTerminated)) ⇒
       oldestChangedReceived = true
-      logInfo("Oldest observed OldestChanged: [{} -> {}]", cluster.selfAddress, oldestOption)
+      logInfo("Oldest observed OldestChanged: [{} -> {}]", cluster.selfAddress, oldestOption.map(_.address))
       oldestOption match {
-        case Some(a) if a == cluster.selfAddress ⇒
+        case Some(a) if a == cluster.selfUniqueAddress ⇒
           // already oldest
           stay
         case Some(a) if !selfExited && removed.contains(a) ⇒
+          // The member removal was not completed and the old removed node is considered
+          // oldest again. Safest is to terminate the singleton instance and goto Younger.
+          // This node will become oldest again when the other is removed again.
           gotoHandingOver(singleton, singletonTerminated, None)
         case Some(a) ⇒
           // send TakeOver request in case the new oldest doesn't know previous oldest
-          peer(a) ! TakeOverFromMe
+          peer(a.address) ! TakeOverFromMe
           setTimer(TakeOverRetryTimer, TakeOverRetry(1), handOverRetryInterval, repeat = false)
           goto(WasOldest) using WasOldestData(singleton, singletonTerminated, newOldestOption = Some(a))
         case None ⇒
@@ -610,8 +633,8 @@ class ClusterSingletonManager(
   when(WasOldest) {
     case Event(TakeOverRetry(count), WasOldestData(_, _, newOldestOption)) ⇒
       if (count <= maxTakeOverRetries) {
-        logInfo("Retry [{}], sending TakeOverFromMe to [{}]", count, newOldestOption)
-        newOldestOption foreach { peer(_) ! TakeOverFromMe }
+        logInfo("Retry [{}], sending TakeOverFromMe to [{}]", count, newOldestOption.map(_.address))
+        newOldestOption.foreach(node ⇒ peer(node.address) ! TakeOverFromMe)
         setTimer(TakeOverRetryTimer, TakeOverRetry(count + 1), handOverRetryInterval, repeat = false)
         stay
       } else if (cluster.isTerminated)
@@ -622,12 +645,12 @@ class ClusterSingletonManager(
     case Event(HandOverToMe, WasOldestData(singleton, singletonTerminated, _)) ⇒
       gotoHandingOver(singleton, singletonTerminated, Some(sender()))
 
-    case Event(MemberRemoved(m, _), _) if m.address == cluster.selfAddress && !selfExited ⇒
+    case Event(MemberRemoved(m, _), _) if m.uniqueAddress == cluster.selfUniqueAddress && !selfExited ⇒
       logInfo("Self removed, stopping ClusterSingletonManager")
       stop()
 
-    case Event(MemberRemoved(m, _), WasOldestData(singleton, singletonTerminated, Some(newOldest))) if !selfExited && m.address == newOldest ⇒
-      addRemoved(m.address)
+    case Event(MemberRemoved(m, _), WasOldestData(singleton, singletonTerminated, Some(newOldest))) if !selfExited && m.uniqueAddress == newOldest ⇒
+      addRemoved(m.uniqueAddress)
       gotoHandingOver(singleton, singletonTerminated, None)
 
     case Event(Terminated(ref), d @ WasOldestData(singleton, _, _)) if ref == singleton ⇒
@@ -660,17 +683,17 @@ class ClusterSingletonManager(
     val newOldest = handOverTo.map(_.path.address)
     logInfo("Singleton terminated, hand-over done [{} -> {}]", cluster.selfAddress, newOldest)
     handOverTo foreach { _ ! HandOverDone }
-    if (removed.contains(cluster.selfAddress)) {
+    if (removed.contains(cluster.selfUniqueAddress)) {
       logInfo("Self removed, stopping ClusterSingletonManager")
       stop()
-    } else if (selfExited)
-      goto(End) using EndData
+    } else if (handOverTo.isEmpty)
+      goto(Younger) using YoungerData(None)
     else
-      goto(Younger) using YoungerData(newOldest)
+      goto(End) using EndData
   }
 
   when(End) {
-    case Event(MemberRemoved(m, _), _) if m.address == cluster.selfAddress ⇒
+    case Event(MemberRemoved(m, _), _) if m.uniqueAddress == cluster.selfUniqueAddress ⇒
       logInfo("Self removed, stopping ClusterSingletonManager")
       stop()
   }
@@ -678,21 +701,21 @@ class ClusterSingletonManager(
   whenUnhandled {
     case Event(_: CurrentClusterState, _) ⇒ stay
     case Event(MemberExited(m), _) ⇒
-      if (m.address == cluster.selfAddress) {
+      if (m.uniqueAddress == cluster.selfUniqueAddress) {
         selfExited = true
         logInfo("Exited [{}]", m.address)
       }
       stay
-    case Event(MemberRemoved(m, _), _) if m.address == cluster.selfAddress && !selfExited ⇒
+    case Event(MemberRemoved(m, _), _) if m.uniqueAddress == cluster.selfUniqueAddress && !selfExited ⇒
       logInfo("Self removed, stopping ClusterSingletonManager")
       stop()
     case Event(MemberRemoved(m, _), _) ⇒
       if (!selfExited) logInfo("Member removed [{}]", m.address)
-      addRemoved(m.address)
+      addRemoved(m.uniqueAddress)
       stay
     case Event(DelayedMemberRemoved(m), _) ⇒
       if (!selfExited) logInfo("Member removed [{}]", m.address)
-      addRemoved(m.address)
+      addRemoved(m.uniqueAddress)
       stay
     case Event(TakeOverFromMe, _) ⇒
       logInfo("Ignoring TakeOver request in [{}] from [{}].", stateName, sender().path.address)
@@ -720,7 +743,7 @@ class ClusterSingletonManager(
   }
 
   onTransition {
-    case _ → (Younger | End) if removed.contains(cluster.selfAddress) ⇒
+    case _ → (Younger | End) if removed.contains(cluster.selfUniqueAddress) ⇒
       logInfo("Self removed, stopping ClusterSingletonManager")
       // note that FSM.stop() can't be used in onTransition
       context.stop(self)
diff --git a/akka-cluster-tools/src/test/scala/akka/cluster/singleton/ClusterSingletonRestartSpec.scala b/akka-cluster-tools/src/test/scala/akka/cluster/singleton/ClusterSingletonRestartSpec.scala
new file mode 100644
index 0000000000..e8c2f6d8b1
--- /dev/null
+++ b/akka-cluster-tools/src/test/scala/akka/cluster/singleton/ClusterSingletonRestartSpec.scala
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2009-2016 Lightbend Inc. 
+ */
+package akka.cluster.singleton
+
+import scala.concurrent.duration._
+
+import akka.actor.ActorSystem
+import akka.actor.PoisonPill
+import akka.cluster.Cluster
+import akka.cluster.MemberStatus
+import akka.testkit.AkkaSpec
+import akka.testkit.TestActors
+import akka.testkit.TestProbe
+import com.typesafe.config.ConfigFactory
+
+class ClusterSingletonRestartSpec extends AkkaSpec("""
+  akka.loglevel = INFO
+  akka.actor.provider = akka.cluster.ClusterActorRefProvider
+  akka.remote {
+    netty.tcp {
+      hostname = "127.0.0.1"
+      port = 0
+    }
+  }
+  """) {
+
+  val sys1 = ActorSystem(system.name, system.settings.config)
+  val sys2 = ActorSystem(system.name, system.settings.config)
+  var sys3: ActorSystem = null
+
+  def join(from: ActorSystem, to: ActorSystem): Unit = {
+    from.actorOf(
+      ClusterSingletonManager.props(
+        singletonProps = TestActors.echoActorProps,
+        terminationMessage = PoisonPill,
+        settings = ClusterSingletonManagerSettings(from)),
+      name = "echo")
+
+    within(10.seconds) {
+      awaitAssert {
+        Cluster(from) join Cluster(to).selfAddress
+        Cluster(from).state.members.map(_.uniqueAddress) should contain(Cluster(from).selfUniqueAddress)
+        Cluster(from).state.members.map(_.status) should ===(Set(MemberStatus.Up))
+      }
+    }
+  }
+
+  "Restarting cluster node with same hostname and port" must {
+    "hand-over to next oldest" in {
+      join(sys1, sys1)
+      join(sys2, sys1)
+
+      val proxy2 = sys2.actorOf(ClusterSingletonProxy.props("user/echo", ClusterSingletonProxySettings(sys2)), "proxy2")
+
+      within(5.seconds) {
+        awaitAssert {
+          val probe = TestProbe()(sys2)
+          proxy2.tell("hello", probe.ref)
+          probe.expectMsg(1.second, "hello")
+        }
+      }
+
+      shutdown(sys1)
+      // it will be downed by the join attempts of the new incarnation
+
+      sys3 = ActorSystem(
+        system.name,
+        ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${Cluster(sys1).selfAddress.port.get}").withFallback(
+          system.settings.config))
+      join(sys3, sys2)
+
+      within(5.seconds) {
+        awaitAssert {
+          val probe = TestProbe()(sys2)
+          proxy2.tell("hello2", probe.ref)
+          probe.expectMsg(1.second, "hello2")
+        }
+      }
+
+      Cluster(sys2).leave(Cluster(sys2).selfAddress)
+
+      within(10.seconds) {
+        awaitAssert {
+          Cluster(sys3).state.members.map(_.uniqueAddress) should ===(Set(Cluster(sys3).selfUniqueAddress))
+        }
+      }
+
+      val proxy3 = sys3.actorOf(ClusterSingletonProxy.props("user/echo", ClusterSingletonProxySettings(sys3)), "proxy3")
+
+      within(5.seconds) {
+        awaitAssert {
+          val probe = TestProbe()(sys3)
+          proxy3.tell("hello3", probe.ref)
+          probe.expectMsg(1.second, "hello3")
+        }
+      }
+
+    }
+  }
+
+  override def afterTermination(): Unit = {
+    shutdown(sys1)
+    shutdown(sys2)
+    if (sys3 != null)
+      shutdown(sys3)
+  }
+}
+
diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala
index d8c613114a..a65ca8cb24 100644
--- a/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala
+++ b/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala
@@ -85,25 +85,25 @@ private[cluster] object InternalClusterAction {
    * If a node is uninitialized it will reply to `InitJoin` with
    * `InitJoinNack`.
    */
-  case object JoinSeedNode
+  case object JoinSeedNode extends DeadLetterSuppression
 
   /**
    * see JoinSeedNode
    */
   @SerialVersionUID(1L)
-  case object InitJoin extends ClusterMessage
+  case object InitJoin extends ClusterMessage with DeadLetterSuppression
 
   /**
    * see JoinSeedNode
    */
   @SerialVersionUID(1L)
-  final case class InitJoinAck(address: Address) extends ClusterMessage
+  final case class InitJoinAck(address: Address) extends ClusterMessage with DeadLetterSuppression
 
   /**
    * see JoinSeedNode
    */
   @SerialVersionUID(1L)
-  final case class InitJoinNack(address: Address) extends ClusterMessage
+  final case class InitJoinNack(address: Address) extends ClusterMessage with DeadLetterSuppression
 
   /**
    * Marker interface for periodic tick messages
@@ -508,8 +508,15 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
           // new node will retry join
           logInfo("New incarnation of existing member [{}] is trying to join. " +
             "Existing will be removed from the cluster and then new member will be allowed to join.", m)
-          if (m.status != Down)
+          if (m.status != Down) {
+            // we can confirm it as terminated/unreachable immediately
+            val newReachability = latestGossip.overview.reachability.terminated(selfUniqueAddress, m.uniqueAddress)
+            val newOverview = latestGossip.overview.copy(reachability = newReachability)
+            val newGossip = latestGossip.copy(overview = newOverview)
+            updateLatestGossip(newGossip)
+
             downing(m.address)
+          }
         case None ⇒
           // remove the node from the failure detector
           failureDetector.remove(node.address)
@@ -609,7 +616,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
         publish(latestGossip)
       case Some(_) ⇒ // already down
       case None ⇒
-        logInfo("Ignoring down of unknown node [{}] as [{}]", address)
+        logInfo("Ignoring down of unknown node [{}]", address)
     }
 
   }
@@ -1259,10 +1266,10 @@ private[cluster] class OnMemberStatusChangedListener(callback: Runnable, status:
   import ClusterEvent._
   private val cluster = Cluster(context.system)
   private val to = status match {
-    case Up ⇒
-      classOf[MemberUp]
-    case Removed ⇒
-      classOf[MemberRemoved]
+    case Up      ⇒ classOf[MemberUp]
+    case Removed ⇒ classOf[MemberRemoved]
+    case other ⇒ throw new IllegalArgumentException(
+      s"Expected Up or Removed in OnMemberStatusChangedListener, got [$other]")
   }
 
   override def preStart(): Unit =
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeChurnSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeChurnSpec.scala
index ef56dc19cf..564eb27d29 100644
--- a/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeChurnSpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeChurnSpec.scala
@@ -74,12 +74,15 @@ abstract class NodeChurnSpec
     }
   }
 
-  def awaitRemoved(additionaSystems: Vector[ActorSystem]): Unit = {
+  def awaitRemoved(additionaSystems: Vector[ActorSystem], round: Int): Unit = {
     awaitMembersUp(roles.size, timeout = 40.seconds)
-    within(20.seconds) {
+    enterBarrier("removed-" + round)
+    within(3.seconds) {
       awaitAssert {
         additionaSystems.foreach { s ⇒
-          Cluster(s).isTerminated should be(true)
+          withClue(s"Cluster(s).self:") {
+            Cluster(s).isTerminated should be(true)
+          }
         }
       }
     }
@@ -113,7 +116,7 @@ abstract class NodeChurnSpec
           else
             Cluster(node).leave(Cluster(node).selfAddress)
         }
-        awaitRemoved(systems)
+        awaitRemoved(systems, n)
         enterBarrier("members-removed-" + n)
         systems.foreach(_.terminate().await)
         log.info("end of round-" + n)
diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/SurviveNetworkInstabilitySpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/SurviveNetworkInstabilitySpec.scala
index f493be218c..eeeedb7301 100644
--- a/akka-cluster/src/multi-jvm/scala/akka/cluster/SurviveNetworkInstabilitySpec.scala
+++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/SurviveNetworkInstabilitySpec.scala
@@ -330,9 +330,19 @@ abstract class SurviveNetworkInstabilitySpec
       runOn(side1AfterJoin: _*) {
         // side2 removed
         val expected = (side1AfterJoin map address).toSet
-        awaitAssert(clusterView.members.map(_.address) should ===(expected))
-        awaitAssert(clusterView.members.collectFirst { case m if m.address == address(eighth) ⇒ m.status } should ===(
-          Some(MemberStatus.Up)))
+        awaitAssert {
+          // repeat the downing in case it was not successful, which may
+          // happen if the removal was reverted due to gossip merge, see issue #18767
+          runOn(fourth) {
+            for (role2 ← side2) {
+              cluster.down(role2)
+            }
+          }
+
+          clusterView.members.map(_.address) should ===(expected)
+          clusterView.members.collectFirst { case m if m.address == address(eighth) ⇒ m.status } should ===(
+            Some(MemberStatus.Up))
+        }
       }
 
       enterBarrier("side2-removed")
diff --git a/akka-contrib/src/test/scala/akka/contrib/pattern/ReceivePipelineSpec.scala b/akka-contrib/src/test/scala/akka/contrib/pattern/ReceivePipelineSpec.scala
index 77b2a2ff4c..5a80ee6cb5 100644
--- a/akka-contrib/src/test/scala/akka/contrib/pattern/ReceivePipelineSpec.scala
+++ b/akka-contrib/src/test/scala/akka/contrib/pattern/ReceivePipelineSpec.scala
@@ -326,7 +326,7 @@ class PersistentReceivePipelineSpec(config: Config) extends AkkaSpec(config) wit
       totaller ! 6
       totaller ! "get"
       expectMsg(6)
-      probe.expectMsg(99)
+      probe.expectMsg(99L)
     }
   }
 }
diff --git a/akka-distributed-data/src/main/scala/akka/cluster/ddata/Replicator.scala b/akka-distributed-data/src/main/scala/akka/cluster/ddata/Replicator.scala
index 8ccb288c0e..74a4d43553 100644
--- a/akka-distributed-data/src/main/scala/akka/cluster/ddata/Replicator.scala
+++ b/akka-distributed-data/src/main/scala/akka/cluster/ddata/Replicator.scala
@@ -276,14 +276,14 @@ object Replicator {
   final case class Subscribe[A <: ReplicatedData](key: Key[A], subscriber: ActorRef) extends ReplicatorMessage
   /**
    * Unregister a subscriber.
-    *
-    * @see [[Replicator.Subscribe]]
+   *
+   * @see [[Replicator.Subscribe]]
    */
   final case class Unsubscribe[A <: ReplicatedData](key: Key[A], subscriber: ActorRef) extends ReplicatorMessage
   /**
    * The data value is retrieved with [[#get]] using the typed key.
-    *
-    * @see [[Replicator.Subscribe]]
+   *
+   * @see [[Replicator.Subscribe]]
    */
   final case class Changed[A <: ReplicatedData](key: Key[A])(data: A) extends ReplicatorMessage {
     /**
diff --git a/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorPruningSpec.scala b/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorPruningSpec.scala
index 10c57cc006..dcc25e9a22 100644
--- a/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorPruningSpec.scala
+++ b/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorPruningSpec.scala
@@ -42,7 +42,7 @@ class ReplicatorPruningSpec extends MultiNodeSpec(ReplicatorPruningSpec) with ST
   val replicator = system.actorOf(Replicator.props(
     ReplicatorSettings(system).withGossipInterval(1.second)
       .withPruning(pruningInterval = 1.second, maxPruningDissemination)), "replicator")
-  val timeout = 2.seconds.dilated
+  val timeout = 3.seconds.dilated
 
   val KeyA = GCounterKey("A")
   val KeyB = ORSetKey[String]("B")
diff --git a/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorSpec.scala b/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorSpec.scala
index 78efdb3a46..d4407634d6 100644
--- a/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorSpec.scala
+++ b/akka-distributed-data/src/multi-jvm/scala/akka/cluster/ddata/ReplicatorSpec.scala
@@ -41,7 +41,7 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
   implicit val cluster = Cluster(system)
   val replicator = system.actorOf(Replicator.props(
     ReplicatorSettings(system).withGossipInterval(1.second).withMaxDeltaElements(10)), "replicator")
-  val timeout = 2.seconds.dilated
+  val timeout = 3.seconds.dilated
   val writeTwo = WriteTo(2, timeout)
   val writeMajority = WriteMajority(timeout)
   val writeAll = WriteAll(timeout)
@@ -112,7 +112,7 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
         val c4 = c3 + 1
         // too strong consistency level
         replicator ! Update(KeyA, GCounter(), writeTwo)(_ + 1)
-        expectMsg(UpdateTimeout(KeyA, None))
+        expectMsg(timeout + 1.second, UpdateTimeout(KeyA, None))
         replicator ! Get(KeyA, ReadLocal)
         expectMsg(GetSuccess(KeyA, None)(c4)).dataValue should be(c4)
         changedProbe.expectMsg(Changed(KeyA)(c4)).dataValue should be(c4)
@@ -347,9 +347,9 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
       val c40 = expectMsgPF() { case g @ GetSuccess(KeyD, _) ⇒ g.get(KeyD) }
       c40.value should be(40)
       replicator ! Update(KeyD, GCounter() + 1, writeTwo)(_ + 1)
-      expectMsg(UpdateTimeout(KeyD, None))
+      expectMsg(timeout + 1.second, UpdateTimeout(KeyD, None))
       replicator ! Update(KeyD, GCounter(), writeTwo)(_ + 1)
-      expectMsg(UpdateTimeout(KeyD, None))
+      expectMsg(timeout + 1.second, UpdateTimeout(KeyD, None))
     }
     runOn(first) {
       for (n ← 1 to 30) {
@@ -466,7 +466,7 @@ class ReplicatorSpec extends MultiNodeSpec(ReplicatorSpec) with STMultiNodeSpec
 
     runOn(first, second) {
       replicator ! Get(KeyE2, readAll, Some(998))
-      expectMsg(GetFailure(KeyE2, Some(998)))
+      expectMsg(timeout + 1.second, GetFailure(KeyE2, Some(998)))
       replicator ! Get(KeyE2, ReadLocal)
       expectMsg(NotFound(KeyE2, None))
     }
diff --git a/akka-docs/_sphinx/themes/akka/layout.html b/akka-docs/_sphinx/themes/akka/layout.html
index 0fdb57b588..22e0dd9c44 100644
--- a/akka-docs/_sphinx/themes/akka/layout.html
+++ b/akka-docs/_sphinx/themes/akka/layout.html
@@ -86,7 +86,7 @@