|
37 | 37 | import io.netty.channel.ChannelHandler; |
38 | 38 | import io.netty.channel.ChannelHandlerContext; |
39 | 39 | import io.netty.channel.ChannelOption; |
| 40 | +import io.netty.channel.ChannelOutboundBuffer; |
40 | 41 | import io.netty.handler.codec.haproxy.HAProxyMessage; |
41 | 42 | import io.netty.handler.ssl.SslHandler; |
42 | 43 | import io.netty.util.concurrent.FastThreadLocal; |
|
182 | 183 | import org.apache.pulsar.transaction.coordinator.TransactionCoordinatorID; |
183 | 184 | import org.apache.pulsar.transaction.coordinator.exceptions.CoordinatorException; |
184 | 185 | import org.apache.pulsar.transaction.coordinator.impl.MLTransactionMetadataStore; |
| 186 | +import org.apache.pulsar.utils.TimedSingleThreadRateLimiter; |
185 | 187 | import org.slf4j.Logger; |
186 | 188 | import org.slf4j.LoggerFactory; |
187 | 189 |
|
|
192 | 194 | * parameter instance lifecycle. |
193 | 195 | */ |
194 | 196 | public class ServerCnx extends PulsarHandler implements TransportCnx { |
| 197 | + private static final Logger PAUSE_RECEIVING_LOG = LoggerFactory.getLogger(ServerCnx.class.getName() |
| 198 | + + ".pauseReceiving"); |
195 | 199 | private final BrokerService service; |
196 | 200 | private final SchemaRegistryService schemaService; |
197 | 201 | private final String listenerName; |
@@ -251,6 +255,10 @@ public class ServerCnx extends PulsarHandler implements TransportCnx { |
251 | 255 |
|
252 | 256 | private final long connectionLivenessCheckTimeoutMillis; |
253 | 257 | private final TopicsPattern.RegexImplementation topicsPatternImplementation; |
| 258 | + private final boolean pauseReceivingRequestsIfUnwritable; |
| 259 | + private final TimedSingleThreadRateLimiter requestRateLimiter; |
| 260 | + private final int pauseReceivingCooldownMilliSeconds; |
| 261 | + private boolean pausedDueToRateLimitation = false; |
254 | 262 |
|
255 | 263 | // Tracks and limits number of bytes pending to be published from a single specific IO thread. |
256 | 264 | static final class PendingBytesPerThreadTracker { |
@@ -314,6 +322,14 @@ public ServerCnx(PulsarService pulsar, String listenerName) { |
314 | 322 | // the null check is a workaround for #13620 |
315 | 323 | super(pulsar.getBrokerService() != null ? pulsar.getBrokerService().getKeepAliveIntervalSeconds() : 0, |
316 | 324 | TimeUnit.SECONDS); |
| 325 | + this.pauseReceivingRequestsIfUnwritable = |
| 326 | + pulsar.getConfig().isPulsarChannelPauseReceivingRequestsIfUnwritable(); |
| 327 | + this.requestRateLimiter = new TimedSingleThreadRateLimiter( |
| 328 | + pulsar.getConfig().getPulsarChannelPauseReceivingCooldownRateLimitPermits(), |
| 329 | + pulsar.getConfig().getPulsarChannelPauseReceivingCooldownRateLimitPeriodMs(), |
| 330 | + TimeUnit.MILLISECONDS); |
| 331 | + this.pauseReceivingCooldownMilliSeconds = |
| 332 | + pulsar.getConfig().getPulsarChannelPauseReceivingCooldownMs(); |
317 | 333 | this.service = pulsar.getBrokerService(); |
318 | 334 | this.schemaService = pulsar.getSchemaRegistryService(); |
319 | 335 | this.listenerName = listenerName; |
@@ -442,11 +458,62 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
442 | 458 | } |
443 | 459 | } |
444 | 460 |
|
| 461 | + private void checkPauseReceivingRequestsAfterResumeRateLimit(BaseCommand cmd) { |
| 462 | + if (!pauseReceivingRequestsIfUnwritable |
| 463 | + || pauseReceivingCooldownMilliSeconds <= 0 || cmd.getType() == BaseCommand.Type.PONG |
| 464 | + || cmd.getType() == BaseCommand.Type.PING) { |
| 465 | + return; |
| 466 | + } |
| 467 | + if (PAUSE_RECEIVING_LOG.isDebugEnabled()) { |
| 468 | + final ChannelOutboundBuffer outboundBuffer = ctx.channel().unsafe().outboundBuffer(); |
| 469 | + if (outboundBuffer != null) { |
| 470 | + PAUSE_RECEIVING_LOG.debug("Start to handle request [{}], totalPendingWriteBytes: {}, channel" |
| 471 | + + " isWritable: {}", cmd.getType(), outboundBuffer.totalPendingWriteBytes(), |
| 472 | + ctx.channel().isWritable()); |
| 473 | + } else { |
| 474 | + PAUSE_RECEIVING_LOG.debug("Start to handle request [{}], channel isWritable: {}", |
| 475 | + cmd.getType(), ctx.channel().isWritable()); |
| 476 | + } |
| 477 | + } |
| 478 | + // "requestRateLimiter" will return the permits that you acquired if it is not opening(has been called |
| 479 | + // "timingOpen(duration)"). |
| 480 | + if (requestRateLimiter.acquire(1) == 0 && !pausedDueToRateLimitation) { |
| 481 | + log.warn("[{}] Reached rate limitation", this); |
| 482 | + // Stop receiving requests. |
| 483 | + pausedDueToRateLimitation = true; |
| 484 | + ctx.channel().config().setAutoRead(false); |
| 485 | + // Resume after 1 second. |
| 486 | + ctx.channel().eventLoop().schedule(() -> { |
| 487 | + if (pausedDueToRateLimitation) { |
| 488 | + log.info("[{}] Resuming connection after rate limitation", this); |
| 489 | + ctx.channel().config().setAutoRead(true); |
| 490 | + pausedDueToRateLimitation = false; |
| 491 | + } |
| 492 | + }, 1, TimeUnit.SECONDS); |
| 493 | + } |
| 494 | + } |
| 495 | + |
445 | 496 | @Override |
446 | 497 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { |
447 | | - if (log.isDebugEnabled()) { |
448 | | - log.debug("Channel writability has changed to: {}", ctx.channel().isWritable()); |
| 498 | + if (pauseReceivingRequestsIfUnwritable && ctx.channel().isWritable()) { |
| 499 | + log.info("[{}] is writable, turn on channel auto-read", this); |
| 500 | + ctx.channel().config().setAutoRead(true); |
| 501 | + requestRateLimiter.timingOpen(pauseReceivingCooldownMilliSeconds, TimeUnit.MILLISECONDS); |
| 502 | + } else if (pauseReceivingRequestsIfUnwritable && !ctx.channel().isWritable()) { |
| 503 | + final ChannelOutboundBuffer outboundBuffer = ctx.channel().unsafe().outboundBuffer(); |
| 504 | + if (outboundBuffer != null) { |
| 505 | + if (PAUSE_RECEIVING_LOG.isDebugEnabled()) { |
| 506 | + PAUSE_RECEIVING_LOG.debug("[{}] is not writable, turn off channel auto-read," |
| 507 | + + " totalPendingWriteBytes: {}", this, outboundBuffer.totalPendingWriteBytes()); |
| 508 | + } |
| 509 | + } else { |
| 510 | + if (PAUSE_RECEIVING_LOG.isDebugEnabled()) { |
| 511 | + PAUSE_RECEIVING_LOG.debug("[{}] is not writable, turn off channel auto-read", this); |
| 512 | + } |
| 513 | + } |
| 514 | + ctx.channel().config().setAutoRead(false); |
449 | 515 | } |
| 516 | + ctx.fireChannelWritabilityChanged(); |
450 | 517 | } |
451 | 518 |
|
452 | 519 | @Override |
@@ -3652,8 +3719,9 @@ public CompletableFuture<Optional<Boolean>> checkConnectionLiveness() { |
3652 | 3719 | } |
3653 | 3720 |
|
3654 | 3721 | @Override |
3655 | | - protected void messageReceived() { |
3656 | | - super.messageReceived(); |
| 3722 | + protected void messageReceived(BaseCommand cmd) { |
| 3723 | + checkPauseReceivingRequestsAfterResumeRateLimit(cmd); |
| 3724 | + super.messageReceived(cmd); |
3657 | 3725 | if (connectionCheckInProgress != null && !connectionCheckInProgress.isDone()) { |
3658 | 3726 | connectionCheckInProgress.complete(Optional.of(true)); |
3659 | 3727 | connectionCheckInProgress = null; |
|
0 commit comments