spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.24k stars 40.7k forks source link

Maven Plugin: integration tests (start) expects registered port 9001 to be free for JMX, fails cryptically otherwise ("Failed to retrieve RMIServer stub: javax.naming.CommunicationException") #23545

Closed Chealer closed 4 years ago

Chealer commented 4 years ago

Following a change of PC-s, my team was unable to start its Spring Boot application due to an error. Thanks to my colleague, we figured out that this is due to a common and well-known conflict for port 9001. Thanks to ticket #22401 , section 8.3.2 of the Maven plugin reference now documents a simple workaround.

Obviously, we should never expect a port we haven't registered to be free, but such an expectation is particularly problematic with port 9001. Port 9001 is officially registered for "ETL Service Manager", but this appears to be an old product no longer in usage. In practice though, this number is very tempting, and has several usages according to Wikipedia:

And apparently, the Intel Graphics Command Center Service now also binds to port 9001 (as discussed in this Microsoft forums thread and in this similar issue report). This is why the port is already in use on our PC-s. Note that the Intel Graphics Command Center Service is on our organization's laptops even though I see no sign of the Intel Graphics Command Center.

And to make it even worst, the part which attempts to bind port 9001 visibly has no error-checking at all. Therefore, it's only when the client tries to connect to it that the failure appears, and at this point, it's not pretty. Rather than a usual "Could not bind to port x" message, we get a message which even a senior developer like myself may not understand:

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.2.2.RELEASE:start (default-cli) on project [...]: Could not contact Spring Boot application: Failed to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is java.rmi.ConnectIOException: non-JRMP server at remote endpoint]


This was apparently introduced in commit 54e12a07e6d7d8e38982a7c398ef76a0283e7e8d and affects the Maven plugin of Spring Boot 2.2.2 (but the changelog gives no indication that this would be fixed as of 2.3.4).

Ideally, Spring Boot would not try to open any registered port, but would instead either register one or use a dynamic port. But if that's not easy, it would help a lot meanwhile to detect failure earlier, so that the error message is reasonably clear.

snicoll commented 4 years ago

Unfortunately, we can't allocate a random port as two goals need to know about it (not only start but also stop obviously) and we don't want to start hacking the Maven lifecycle to pass that information around. As you've noticed yourself, there is a clear example of what you should do if the port is taken in the documentation (not really a workaround though).

Having said all of that, we could post-process the exception and provide better hints when such failure occurs.

Chealer commented 4 years ago

Thanks @snicoll ,

Unfortunately, we can't allocate a random port as two goals need to know about it (not only start but also stop obviously) and we don't want to start hacking the Maven lifecycle to pass that information around.

Right... I don't know Maven, but I do expect a proper fix to require a significant effort, which is why I offered a mitigant meanwhile.

As you've noticed yourself, there is a clear example of what you should do if the port is taken in the documentation (not really a workaround though).

Yes... can you clarify if there is an issue with the workaround?

Having said all of that, we could post-process the exception and provide better hints when such failure occurs.

That's exactly the kind of trap I wanted developers to avoid... I described a reliable (as well as efficient) way to mitigate in the last paragraph. To clarify, what detecting earlier means is to perform error checking on the initial unsafe operation (binding on the hijacked port) rather than when things really go wrong, when trying to connect to that port later.

snicoll commented 4 years ago

Yes... can you clarify if there is an issue with the workaround?

As I've mentioned already, it isn't a workaround. Why would there be an issue with something that's documented and supported? If that port is used, take another port.

We'll use this issue to improve the error message and hint users that the port is already taken.

Chealer commented 4 years ago

Hi @snicoll ,

Yes... can you clarify if there is an issue with the workaround?

As I've mentioned already, it isn't a workaround. Why would there be an issue with something that's documented and supported? If that port is used, take another port.

Ah, I understand what you meant now. Obviously there is an issue if that wastes people's time, but surely what you meant to ask is why it's a defect. The bug is not that the current design can't work, it's just that it violates standards (making it unreliable) to use that design (with port 9001) without registering the port Spring tries to use (see Wikipedia for an explanation of port ranges). I am not sure what you mean by "supported", but regarding documentation, that does not justify hijacking a registered port (I mean, if there was a warning before users downloaded Spring Boot, that would arguably excuse it, but documenting somewhere in an "Examples" section as it currently stands is very far from that).

We'll use this issue to improve the error message and hint users that the port is already taken.

Yes, as indicated in my last comment, that would mitigate, and if we're going to take months to ship an actual solution, this would in fact greatly diminish the impact.

wilkinsona commented 4 years ago

@chealer Unfortunately, registering a port won't make any difference as you've demonstrated with your examples above. 9001 is registered, yet, excluding Spring Boot, there are at least 5 different pieces of software that use it. Any port that we may switch to or that we may register may already have conflicting uses or would become conflicted in the future. Given that we can't prevent a port clash, the only solution to this issue that I can see is to improve the error message when a clash occurs. As described above, this is already what we intend to do to resolve this issue.

Chealer commented 4 years ago

Hi @wilkinsona ,

@Chealer Unfortunately, registering a port won't make any difference as you've demonstrated with your examples above. 9001 is registered, yet, excluding Spring Boot, there are at least 5 different pieces of software that use it. Any port that we may switch to or that we may register may already have conflicting uses or would become conflicted in the future.

I understand your reasoning, which is logical. In practice though, registering would (with a minimum of luck) make quite a difference. You are completely correct that registering a port does not guarantee there will be no conflict. Nevertheless:

  1. Registering a port (and changing the port used) would stop the current conflict(s) and make the plugin standards-compliant.
  2. The risk of future conflicts is not equal for all ports. Basically, conflicts on registered ports happen due to coders who do not really understand networking or who just never learned it. Those will tend to pick a "random" port, if it turns out to work on their system. They will tend to choose a "random" number which is easy to remember or communicate to the other side. Wikipedia's list shows that the most misused ports have simple numbers. For example, between 8000 and 10000, we see 8000, 8008, 8080, 8090, 8123, 8200, 8333, 8880, 8888, 9000, 9001, 9080, 9090, 9800 and 10000. You can trust me that the average registered port has much less conflict potential than port 9001.

Given that we can't prevent a port clash, the only solution to this issue that I can see is to improve the error message when a clash occurs. As described above, this is already what we intend to do to resolve this issue.

Just to be clear, that would help a lot, but that is technically not a solution to the issue. In terms of actual solutions, the 2 main options are:

And please note that although I "defended" registration above, I am not recommending to register a port for this. I would much prefer to see this solved by usage of a dynamic port (which would have the advantage of avoiding any risk of the conflicts you pointed out).

P.S. I clarified the issue bringing the title closer to its original form. Thanks for the edit and feel free to improve it further, but please try to keep enough elements so that others searching have a fair chance of finding it, and phrased as an issue report rather than as a request.

wilkinsona commented 4 years ago

Basically, conflicts on registered ports happen due to coders who do not really understand networking or who just never learned it.

It is entirely possible for someone to disagree with you, not because they don't understand something or have never bothered learning something, but because they have a different opinion. Questioning the effort that people exert or their ability to understand something is not a productive way to collaborate.

I would much prefer to see this solved by usage of a dynamic port (which would have the advantage of avoiding any risk of the conflicts you pointed out).

Stephane has already explained above that this isn't an option:

Unfortunately, we can't allocate a random port as two goals need to know about it (not only start but also stop obviously) and we don't want to start hacking the Maven lifecycle to pass that information around.

I updated the title of the issue to be an accurate description of the action that we intend to take. This is important for our tracking purposes and also for the changelog. I see that you'd prefer that the issue title is left as-is. To allow that to happen while allowing us to track the change that we intend to make, I have opened https://github.com/spring-projects/spring-boot/issues/24044. We don't intend to make any further changes for this issue so I will close it. Thanks for the report.

Chealer commented 4 years ago

Hi @wilkinsona

Basically, conflicts on registered ports happen due to coders who do not really understand networking or who just never learned it.

It is entirely possible for someone to disagree with you, not because they don't understand something or have never bothered learning something, but because they have a different opinion. Questioning the effort that people exert or their ability to understand something is not a productive way to collaborate.

I don't know what disagreement or opinion you mean, but in any case, I do not question anyone's ability to understand anything.

I would much prefer to see this solved by usage of a dynamic port (which would have the advantage of avoiding any risk of the conflicts you pointed out).

Stephane has already explained above that this isn't an option:

Unfortunately, we can't allocate a random port as two goals need to know about it (not only start but also stop obviously) and we don't want to start hacking the Maven lifecycle to pass that information around.

Using a dynamic port does not mean both sides picking their random port. What it could mean is for Maven to find an available port and tell both sides to use that port. That is not technically reliable as the port could be allocated to something else between the moment it is selected and bound, but the risk of conflict would already be reduced so much in practice that I for one would be satisfied with considering that as a solution.

I updated the title of the issue to be an accurate description of the action that we intend to take. This is important for our tracking purposes and also for the changelog.

I see. Well, the processes should be adjusted then since GitHub's ticket system is an issue tracking system, where items are in the form of issue reports rather than requests for enhancements.

To automate changelog generation, I suggest to take commits as input rather than issues.

I see that you'd prefer that the issue title is left as-is. To allow that to happen while allowing us to track the change that we intend to make, I have opened #24044. We don't intend to make any further changes for this issue so I will close it. Thanks for the report.

Thanks, but issues are closed when they are solved. It is OK not to intend to solve all issues, you can just leave the tickets as they are.

Chealer commented 3 years ago

Would you mind reopening this ticket @wilkinsona ?

Chealer commented 3 years ago

Please reopen this ticket

ddovbis commented 3 years ago

+1 to please reopen this ticket.

sudheerah commented 3 years ago

I had the similar kind of issue with Windows/ Java 1.8/ spring.boot.version-1.5.9.RELEASE where app didn't start up and stuck with below logs

DEBUG] Application argument(s): --server.port=54110 --spring.profiles.active=it
[DEBUG] Connecting to local MBeanServer at port 54111
[DEBUG] Waiting for spring application to start...
[DEBUG] MBean server at port 54111 is not up yet...
[DEBUG] Spring application is not ready yet, waiting 2000ms (attempt 1)
[DEBUG] MBean server at port 54111 is not up yet...
[DEBUG] Spring application is not ready yet, waiting 2000ms (attempt 2)

This solution worked for me https://stackoverflow.com/a/65952986/2303693. i.e. Adding below within the spring-boot-maven-plugin

 <configuration>
     <fork>false</fork>
  </configuration>

However same code worked in iOS machine without above fix.

torbjornsk commented 1 year ago

For those who end up here due to issues with port collisions and so on, these can be solved by utilizing the build-helper-maven-plugin to grab a random free port when the build starts. The port can be stored in a maven property and injected into the config of the spring-boot-maven-plugin. Something like this:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>reserve-ports-for-openapi-gen</id>
            <goals>
                <goal>reserve-network-port</goal>
            </goals>
            <phase>process-resources</phase>
            <configuration>
                <portNames>
                    <portName>app.http.port</portName>
                    <portName>app.jmx.port</portName>
                </portNames>
            </configuration>
        </execution>
    </executions>
</plugin>

Then in the configuration part of both the start and stop exection:

<jmxPort>${app.jmx.port}</jmxPort>

You can also avoid port crashes for the application port itself by injecting it with a D-parameter in the jvmArguments of the configuration of the spring-boot-maven-plugin itself:

<jvmArguments>
    -Dspring.application.admin.enabled=true
    -Dserver.port=${app.http.port}
</jvmArguments>