Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[Bug] DefaultMQPushConsumer.shutdown() never stops the pop consume service, so its non-daemon threads keep the JVM alive

Open Beginner friendly
#11,427 2 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@iloveeyjafjalla is already working on this.

Since Oct 14, 2026.

  • #11428 by @iloveeyjafjalla — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
65/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
java
Domain
backend

Research direction

Start in client/.../impl/consumer/DefaultMQPushConsumerImpl.java: the normal shutdown path around line 908 stops only consumeMessageService, and the failed-start branch near lines 991-993 does the same. Add a shutdown call on consumeMessagePopService in both places, following the non-pop service. Done when DefaultMQPushConsumerImplTest, using its mocked consumeMessagePopService and testShutdown, verifies the pop service's shutdown is called. Check the two comments first, since the reporter says they plan to open the PR themselves.

Written by the indexing model from the issue text.

Description

Before Creating the Bug Report
  • I found a bug, not just asking a question, which should be created in GitHub Discussions.
  • I have searched the GitHub Issues and GitHub Discussions of this repository and believe that this is not a duplicate.
  • I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.
Runtime platform environment

OS: any (client side, DefaultMQPushConsumer)

RocketMQ version

branch: develop
Git commit id: a7edeecfb

JDK Version

Compiler: Temurin 1.8.0_504 (target 1.8, same as CI)

Describe the Bug

DefaultMQPushConsumerImpl.start() creates and starts two consume services, but shutdown() stops only the first one:

this.consumeMessageService.start();
// POPTODO
this.consumeMessagePopService.start();       // DefaultMQPushConsumerImpl.java:984-986
case RUNNING:
    this.consumeMessageService.shutdown(awaitTerminateMillis);      // :908 — the pop service is never stopped

The pop service is not idle after it is started: ConsumeMessagePopOrderlyService.start() schedules lockMQPeriodically() at a fixed rate on a single-thread executor created with new ThreadFactoryImpl("ConsumeMessageScheduledThread_") (client/.../ConsumeMessagePopOrderlyService.java:64-77), and ThreadFactoryImpl creates non-daemon threads by default (common/.../ThreadFactoryImpl.java:35, :59). ConsumeMessagePopConcurrentlyService.start() likewise starts its consume executor. Both services implement shutdown(long) (ConsumeMessagePopOrderlyService.java:80-88, ConsumeMessagePopConcurrentlyService.java:70), and no code in the client module ever calls it — the only references to the field are the two assignments, start() and submitPopConsumeRequest.

The same omission exists in the failed-start branch, which stops only consumeMessageService when the consumer group is already registered (DefaultMQPushConsumerImpl.java:991-993).

Steps to Reproduce
  1. DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group"); consumer.subscribe("topic", "*"); consumer.registerMessageListener(listener);
  2. consumer.start(); consumer.shutdown();
  3. The pop service is still running: for CLUSTERING (the default) the ConsumeMessageScheduledThread_ thread keeps firing lockMQPeriodically() every REBALANCE_LOCK_INTERVAL on an empty process-queue table, the threads are non-daemon, so a JVM that relies on the consumer's shutdown to release its resources never terminates.

At the unit level the existing harness covers it: DefaultMQPushConsumerImplTest already injects a mocked consumeMessagePopService (line ~250-254) and has a testShutdown, so asserting that the service is shut down with shutdown() fails deterministically today.

What Did You Expect to See?

DefaultMQPushConsumer.shutdown() "shut down this client and releasing underlying resources" (client/.../consumer/DefaultMQPushConsumer.java:789) — i.e. both consume services are stopped, exactly like the non-pop one in the same method. The pop services' own shutdown(long) methods are the proof that this was the intent.

What Did You See Instead?

After shutdown() the consumer's pop scheduler (and, for pop consumers, the pop consume executor) keeps running with non-daemon threads; the client appears stopped (serviceState = SHUTDOWN_ALREADY) while resources are still held, which can leave the JVM unable to exit.

Additional Context

Minimal fix: stop consumeMessagePopService next to consumeMessageService in both places — the normal shutdown path and the register-failure path.

I will open a PR with those two one-line changes plus a regression test in DefaultMQPushConsumerImplTest (red before: the pop service receives no shutdown call).

Dominant language
Java
Stars
22.6k
Forks
12k
Avg merge
6d 16h
Merged PRs (30d)
18

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from apache/rocketmq

All issues in apache/rocketmq

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.