Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public void run() {
server.start();
} catch (Exception ex) {
RecordLog.warn("[NettyHttpCommandCenter] Failed to start Netty transport server", ex);
ex.printStackTrace();
}
}
});
Expand All @@ -59,7 +58,10 @@ public void run() {
@Override
public void stop() throws Exception {
server.close();
pool.shutdownNow();
// Do not interrupt the executor as part of normal shutdown. If a channel has
// been bound, closing it completes closeFuture normally. Otherwise HttpServer
// honors the stop request before starting or after a later successful bind.
pool.shutdown();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;

/**
* @author Eric Zhao
Expand All @@ -41,13 +42,27 @@ public final class HttpServer {

private static final int DEFAULT_PORT = 8719;

private Channel channel;
private volatile Channel channel;

/**
* Indicates that a stop request has been issued. Together with the volatile
* channel reference, this prevents a close request from being lost when it
* races with a successful bind.
*/
private volatile boolean stopped;

final static Map<String, CommandHandler> handlerMap = new ConcurrentHashMap<String, CommandHandler>();

public void start() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
if (stopped) {
return;
}
// Use daemon event-loop threads because the command center may not be tied
// to a managed application lifecycle, and stop() is not always invoked.
EventLoopGroup bossGroup = new NioEventLoopGroup(1,
new DefaultThreadFactory("sentinel-netty-http-boss", true));
EventLoopGroup workerGroup = new NioEventLoopGroup(0,
new DefaultThreadFactory("sentinel-netty-http-worker", true));
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
Expand All @@ -62,27 +77,34 @@ public void start() throws Exception {
port = Integer.parseInt(TransportConfig.getPort());
}
} catch (Exception e) {
// Will cause the application exit.
// Reject an invalid configured port before attempting to bind.
throw new IllegalArgumentException("Illegal port: " + TransportConfig.getPort());
}

int retryCount = 0;
ChannelFuture channelFuture = null;
// loop for an successful binding
// Retry binding on incremented ports until a port is available.
while (true) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bind retry loop does not recheck stopped between failed binds. If close() is called while retrying, the loop continues scanning ports until a bind succeeds (or indefinitely), delaying shutdown. Check stopped in the catch/retry path and break out promptly.

int newPort = getNewPort(port, retryCount);
try {
channelFuture = b.bind(newPort).sync();
TransportConfig.setRuntimePort(newPort);
CommandCenterLog.info("[NettyHttpCommandCenter] Begin listening at port " + newPort);
Channel boundChannel = channelFuture.channel();
channel = boundChannel;
if (stopped) {
// close() may have run before the bind completed. Honor that
// stop request now so the newly-bound channel cannot leak.
boundChannel.close();
} else {
TransportConfig.setRuntimePort(newPort);
CommandCenterLog.info("[NettyHttpCommandCenter] Begin listening at port " + newPort);
}
break;
} catch (Exception e) {
TimeUnit.MILLISECONDS.sleep(30);
RecordLog.warn("[HttpServer] Netty server bind error, port={}, retry={}", newPort, retryCount);
retryCount ++;
}
}
channel = channelFuture.channel();
channel.closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
Expand All @@ -102,7 +124,11 @@ private int getNewPort(int basePort, int retryCount) {
}

public void close() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting stopped = true without resetting it makes HttpServer one-shot: after CommandCenter.stop(), a later start() will silently do nothing. This changes the previous lifecycle semantics; please document the intended one-shot behavior or reset stopped when a restart is appropriate.

channel.close();
stopped = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stop/bind race guard uses two independent volatile fields (stopped and channel), which does not guarantee cross-visibility: start() may still observe stopped == false after assigning the channel and leave the server listening. Synchronize the stop flag and channel assignment under a common monitor, or hold both in a single atomic state object, so a close() request can never be lost.

Channel currentChannel = channel;
if (currentChannel != null) {
currentChannel.close();
}
}

public void registerCommand(String commandName, CommandHandler handler) {
Expand Down
Loading