Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

calling BlockingNettyContext.shutdown() does not clear handlers #90

Closed
bclozel opened this issue May 10, 2017 · 9 comments
Closed

calling BlockingNettyContext.shutdown() does not clear handlers #90

bclozel opened this issue May 10, 2017 · 9 comments
Labels
type/bug A general bug
Milestone

Comments

@bclozel
Copy link
Member

bclozel commented May 10, 2017

This issue is linked with spring-projects/spring-boot#9146
When trying to fix devtools support in Spring Boot (i.e. stopping the server, recreating a new Spring context and starting a new server - all that without stopping the JVM), I've found that disposing a server and starting a new one has not the expected behavior.

Here's a repro case that shows the behavior:

package com.example.demo;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import reactor.ipc.netty.http.client.HttpClient;
import reactor.ipc.netty.http.client.HttpClientResponse;
import reactor.ipc.netty.http.server.HttpServer;
import reactor.ipc.netty.tcp.BlockingNettyContext;

import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class ReactorNettyRestart {

	@Test
	public void testRestart() {
		// start a first server with a handler that answers HTTP 200 OK
		BlockingNettyContext context = HttpServer.create(8080)
				.start((req, resp) -> resp.status(200).send());

		HttpClientResponse response = HttpClient.create(8080).get("/").block();

		// checking the response status, OK
		Assertions.assertThat(response.status().code()).isEqualTo(200);
		// dispose the Netty context and wait for the channel close
		context.shutdown();
		context.getContext().onClose().block();

		// create a totally new server instance, with a different handler that answers HTTP 201
		context = HttpServer.create(8080)
				.start((req, resp) -> resp.status(201).send());

		response = HttpClient.create(8080).get("/").block();

		// fails, response status is 200 and debugging shows the the previous handler is called
		Assertions.assertThat(response.status().code()).isEqualTo(201);
		context.shutdown();
		context.getContext().onClose().block();
	}

	@Test
	public void testRestartBlockingClient() {

		RestTemplate template = new RestTemplate();
		// start a first server with a handler that answers HTTP 200 OK
		BlockingNettyContext context = HttpServer.create(8080)
				.start((req, resp) -> resp.status(200).send());

		ResponseEntity<String> response = template.getForEntity("http://localhost:8080/", String.class);

		// checking the response status, OK
		Assertions.assertThat(response.getStatusCode().value()).isEqualTo(200);
		// dispose the Netty context and wait for the channel close
		context.shutdown();
		context.getContext().onClose().block();

		// create a totally new server instance, with a different handler that answers HTTP 201
		context = HttpServer.create(8080)
				.start((req, resp) -> resp.status(201).send());

		response = template.getForEntity("http://localhost:8080/", String.class);

		// fails, response status is 200 and debugging shows the the previous handler is called
		Assertions.assertThat(response.getStatusCode().value()).isEqualTo(201);
		context.shutdown();
		context.getContext().onClose().block();
	}

}

Am I misusing the HttpServer API?
Is there some state that I forgot to clear?

@smaldini
Copy link
Contributor

smaldini commented Jun 2, 2017

The issue has more to do with the very currently limited lifecycle to be reworked.
Meanwhile, to fully reset the client pool, use HttpResources.reset().

@simonbasle simonbasle modified the milestones: 0.7.0.RELEASE, 0.7.0.M1 Jun 19, 2017
simonbasle added a commit that referenced this issue Jul 12, 2017
This commit further facilitates complete shutdown and restart of a
server via the start/shutdown API offered through BlockingNettyContext.

Calling shutdown() will now also shut down global resources, that is
TcpResources and HttpResources.
@simonbasle
Copy link
Member

I have added global resource cleanup as part of the BlockingNettyContext shutdown() method (that comes after start or automatically on JVM shutdown when using startAndAwait). See #135
Wdyt @bclozel @smaldini could that be a good resolution?

@violetagg violetagg modified the milestones: 0.7.0.M1, 0.7.0.M2 Jul 17, 2017
@bclozel
Copy link
Member Author

bclozel commented Jul 18, 2017

I've updated the code sample in the original issue, and I can still reproduce the issue with the current 0.7.0 SNAPSHOT.
The new code sample shows that this can be reproduced as well with a RestTemplate client, backed by a basic JDK client (so nothing reactor related).

Note that calling HttpResources.reset(); right after waiting for the context to be closed fixes the problem in both cases.

@bclozel bclozel changed the title calling HttpServer.dispose() does not clear handlers calling HttpServer.shutdown() does not clear handlers Jul 18, 2017
@bclozel bclozel changed the title calling HttpServer.shutdown() does not clear handlers calling BlockingNettyContext.shutdown() does not clear handlers Jul 18, 2017
@smaldini smaldini modified the milestones: 0.7.0.M2, 0.8.0.RELEASE Aug 31, 2017
@violetagg violetagg modified the milestones: 0.8.0.RELEASE, 0.8.x Backlog Jun 8, 2018
@sdeleuze
Copy link
Contributor

I faced the very same issue in Spring Fu (spring-projects-experimental/spring-fu@6be8b90) and I think that's a rather annoying one (it keeps the port open after closing the server) that deserves to be fixed before Spring Framework 5.1 / Spring Boot 2.1 release IMO.

@smaldini
Copy link
Contributor

We should have the following fix in a next milestone/RC:
when disposableServer.dispose() is called globalResource will be scanned for associated client connections and cleaned accordingly.

@smaldini smaldini removed this from the 0.8.x Backlog milestone Aug 14, 2018
@smaldini smaldini added this to the 0.8.0.M2 milestone Aug 14, 2018
smaldini pushed a commit that referenced this issue Aug 14, 2018
Add address-matching ConnectionProvider#disposeWhen(SocketAddress).
This will be used by global resource holders to selectively close
stateful connection providers such as PooledConnectionProvider.

Rename shutdown to disposeLoopsAndConnections to align with dispose
semantics.

Minor tweak to release state lifecycle in PooledConnectionProvider
@sdeleuze
Copy link
Contributor

I reopen this issue, I still see it in Spring Fu. To reproduce clone https://github.com/spring-projects/spring-fu, remove HttpResources.reset() instances from https://github.com/spring-projects/spring-fu/blob/master/kofu/src/test/kotlin/org/springframework/boot/kofu/web/AbstractWebServerDslTests.kt, you will see test failures with client requesting the wrong server.

@violetagg
Copy link
Member

@sdeleuze Can you test PR #449

@sdeleuze
Copy link
Contributor

sdeleuze commented Oct 9, 2018

The issue still occurs with latest snapshot, if you want to debug that I have created a branch that reproduces the issue: https://github.com/spring-projects/spring-fu/tree/reactor-netty-http-resources. You can run for example JacksonDslTests it fails without HttpResources.reset() at the end of each test.

@sdeleuze sdeleuze reopened this Oct 9, 2018
@sdeleuze
Copy link
Contributor

sdeleuze commented Oct 9, 2018

As noticed by @violetagg, Californium-BUILD-SNAPSHOT was not using Reactor Netty snapshots. Now that it is the case, I have been able to verify that the issue is fixed. Thanks.

@sdeleuze sdeleuze closed this as completed Oct 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/bug A general bug
Projects
None yet
Development

No branches or pull requests

5 participants