New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ensure storage port never conflicts with client port #765
Conversation
// bad luck? try again | ||
if (port == usedPort) freePort(usedPort) | ||
else port | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, freePort
is public API, so instead, I'm adding an overloaded method, but private.
Should we deprecate freePort
? I don't think we should have this as public API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worse case freePort is not random but the OS insist on using the same over and over here, resulting in infinite loop.
I think it's better we we pick two ports at the same time, before closing them.
There is a SocketUtil
in Akka for this, but that is in testkit and we don't want that dependency from the launcher.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍, I will reserve both together while keeping the sockets open and bound during the selection.
This will also fix: lagom/lagom#2688 Same problem, but affecting another Cassandra test. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
// bad luck? try again | ||
if (port == usedPort) freePort(usedPort) | ||
else port | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -82,6 +82,14 @@ object CassandraLauncher { | |||
port | |||
} | |||
|
|||
@tailrec | |||
private def freePort(usedPort: Int): Int = { | |||
val port = freePort() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
possibly limit the tries? Tho i can't see why it would keep picking the same port
5e7186e
to
e901653
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a different approach. Maybe I'm overthinking it, but here is how it's implemented now.
- user calls
start
with 0 and thenrandomPort
, a random port will be selected and used everywhere.randomPort
will tell the truth. That's compatible with previous behavior actually. - user calls
start
with non-zeo and thenrandomPort
, the user-specified port will be used everywhere.randomPort
will return user-specified port, so not random. This differ from previous behavior. - user calls
randomPort
and thenstart
with 0, a random port will be selected and used everywhere.randomPort
will tell the truth. That's compatible with previous behavior actually. - user calls
randomPort
and thenstart
with non-zero, the user-specified port will be used everywhere.randomPort
will tell a lie becasue it will be fixed to a random value that is discarded. That's compatible with previous behavior actually.
val realHost = host.getOrElse("127.0.0.1") | ||
val realPort = if (port == 0) randomPort else port | ||
val storagePort = freePort() | ||
val realHost = host.getOrElse(DEFAULT_HOST) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there was a flaw here. If the user passes a host, we should use it when binding the ports. See below.
val storageSocket = ServerSocketChannel.open().socket() | ||
|
||
try { | ||
clientSocket.bind(new InetSocketAddress(host, requestedPort)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I explicitly bind the requested port, even if it's not 0. We must ensure that this port is free and that storage port won't conflict with it.
case (Failure(ex1), Failure(ex2)) => throw new RuntimeException(s"Failed to close sockets: client '${ex1.getMessage}', storage '${ex2.getMessage}'" ) | ||
case (Failure(ex1), _) => throw new RuntimeException(s"Failed to close client-port socket: '${ex1.getMessage}'" ) | ||
case (_, Failure(ex2)) => throw new RuntimeException(s"Failed to close storage-port socket: '${ex2.getMessage}'" ) | ||
case (_, _) => // we are fine, all closed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not so sure if really important to do this.
Maybe
t1.flatMap(_ => t2).get
would be enough (?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I guess you want to backport to release-0.x branch so it's included in next 0.10x
Yes, we want to backport it. |
@chbatey, I have changed this PR quite substantially, you may want to give a second review. |
Something pretty wrong with my impl: [info] The CassandraLauncher
Starting Cassandra on port client port: 0 storage port 0 host 127.0.0.1 java version 1.8.0_242-b08
4/06 15:03:42 ERROR[main] o.a.c.s.StartupChecks
[] - cassandra.jmx.local.port missing from cassandra-env.sh, unable to start local JMX service.
[info] - must support forking *** FAILED *** (45 seconds, 128 milliseconds)
[info] java.lang.RuntimeException: Cassandra did not start within 45 seconds
[info] at akka.persistence.cassandra.testkit.CassandraLauncher$.tryConnect$1(CassandraLauncher.scala:409)
[info] at akka.persistence.cassandra.testkit.CassandraLauncher$.waitForCassandraToListen(CassandraLauncher.scala:413)
[info] at akka.persistence.cassandra.testkit.CassandraLauncher$.startForked(CassandraLauncher.scala:337)
[info] at akka.persistence.cassandra.testkit.CassandraLauncher$.start(CassandraLauncher.scala:286)
[info] at akka.persistence.cassandra.testkit.CassandraLauncher$.start(CassandraLauncher.scala:213)
[info] at akka.persistence.cassandra.testkit.CassandraLauncherSpec.$anonfun$new$2(CassandraLauncherSpec.scala:52) Looking... |
TIL: it turns out that A few comments though:
I'm happy to change it if others have different opinions. |
@@ -68,20 +70,69 @@ object CassandraLauncher { | |||
|
|||
private var cassandraDaemon: Option[Closeable] = None | |||
|
|||
private val DEFAULT_HOST = "127.0.0.1" | |||
|
|||
private val initialPortsValue = (0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right
ensure storage port never conflicts with client port
Manual backport of #765 on release-0.x
This will fix a flaky test in Lagom lagom/lagom#2782.
As seen in Lagom build failure...
There is a small chance that the storage port conflicts with the client port as both are picked randomly, but not atomically.
The quick fix is to make sure that the storage port never equals the client port and re-try if necessary.