leeo-zyf opened a new issue, #1316: URL: https://github.com/apache/rocketmq-clients/issues/1316
### Before Creating the Bug Report - [x] I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq-clients/discussions). - [x] I have searched the [GitHub Issues](https://github.com/apache/rocketmq-clients/issues) and [GitHub Discussions](https://github.com/apache/rocketmq-clients/discussions) of this repository and believe that this is not a duplicate. - [x] I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ. ### Programming Language of the Client Go ### Runtime Platform Environment - SDK version: v5.1.4 (regression from v5.1.1-rc1) - Language: Go - Component: Producer (defaultProducer / defaultClient) - Broker: Apache RocketMQ 5.x ### RocketMQ Version of the Client/Server Comparison: v5.1.1-rc1 vs v5.1.4 v5.1.1-rc1: - startUp() return:Immediate after ticker starts - defaultClient has inited field:No - Behavior with empty initTopics:Works normally (lazy route fetch on first Send) - Telemetry stream lifecycle:Created lazily on first getMessageQueues call v5.1.4: - startUp() return:Blocks until inited=true - defaultClient has inited field:Yes (atomic.Bool) - Behavior with empty initTopics:Deadlocks forever - Telemetry stream lifecycle:Same, but startUp waits for it synchronously v5.1.1-rc1 startUp() tail (lines 493–549): ticker.Tick(f, time.Second*30, cli.done) return nil // ← returns immediately, no blocking ```go v5.1.4 startUp() tail (lines 596–607): ticker.Tick(f, time.Second*30, cli.done) // wait syncSettings finish ← NEW, BLOCKING for !cli.inited.Load() { if cli.startUpError != nil { return cli.startUpError } sugarBaseLogger.Infoln("wait for sync settings finish") time.Sleep(time.Second) } ### Run or Compiler Version _No response_ ### Describe the Bug In **v5.1.4**, `Producer.Start()` blocks forever when the producer is created without `WithTopics(...)`. The root cause is a new blocking wait loop added to `defaultClient.startUp()` that waits for `cli.inited` to become `true`. However, `cli.inited` is only set by `onSettingsCommand()`, which can only be triggered after a Telemetry stream is established with a broker — and that only happens inside `getMessageQueues()`, which is only called when `initTopics` is non-empty. When `initTopics` is empty (the default, and the most common usage pattern), the loop spins forever: ```go // client.go:599-606 (v5.1.4) for !cli.inited.Load() { // inited stays false forever if cli.startUpError != nil { return cli.startUpError } sugarBaseLogger.Infoln("wait for sync settings finish") time.Sleep(time.Second) // ← goroutine blocked here indefinitely } This behavior was not present in v5.1.1-rc1, where startUp() returned immediately after starting the route-refresh ticker. ### Steps to Reproduce ```go producer, _ := golang.NewProducer( &golang.Config{ Endpoint: "...", NameSpace: "...", Credentials: &credentials.SessionCredentials{...}, }, golang.WithMaxAttempts(3), // NOTE: WithTopics(...) intentionally omitted — this is the standard usage // when topics are only known at send time ) err := producer.Start() // ← blocks forever, never returns ### What Did You Expect to See? Design Intent vs. Actual Behavior The intent of the new blocking wait is understandable: ensure the producer has received broker settings (retry policy, message-type validation, etc.) before Start() returns, so the first Send() call is guaranteed to operate with a fully configured client. However, the implementation has a fundamental coupling problem: - cli.inited depends on onSettingsCommand() - onSettingsCommand() depends on a Telemetry stream being open - A Telemetry stream is only opened via getMessageQueues() - getMessageQueues() requires a topic to query - Topics are only available at startup if the user passed WithTopics(...) The Settings handshake is a client-level concern (it applies to the producer as a whole), but the implementation ties it to a topic-level operation. These two concerns should not be coupled. In the existing API contract, WithTopics() is documented as an optional pre-warming hint for route prefetching — it was never intended to be a prerequisite for Start() to succeed. ### What Did You See Instead? Evidence: goroutine dump From a production goroutine dump collected while the service was completely unresponsive: ```go The goroutine stuck in the infinite wait loop: goroutine 49529 [sleep]: time.Sleep(0x3b9aca00) .../runtime/time.go:363 github.com/apache/rocketmq-clients/golang/v5.(*defaultClient).startUp(0xc002893080) .../rocketmq-clients/golang/[email protected]/client.go:604 ← inside the loop github.com/apache/rocketmq-clients/golang/v5.(*defaultProducer).Start(0xc00799a4d0) .../rocketmq-clients/golang/[email protected]/producer.go:56 Hundreds of request goroutines piled up waiting for the mutex: goroutine 511012 [sync.Mutex.Lock, 1 minutes]: ...AcquireRocketMqProducer (producer.go:47) goroutine 183826 [sync.Mutex.Lock, 3 minutes]: ...AcquireRocketMqProducer (producer.go:47) goroutine 305925 [sync.Mutex.Lock, 2 minutes]: ...AcquireRocketMqProducer (producer.go:47) goroutine 679238 [sync.Mutex.Lock]: ...AcquireRocketMqProducer (producer.go:47) All of them trace back to the same service handler, proving a complete send-path blockage caused solely by Start() never returning. ### Additional Context Proposed Fix Option A — Skip the wait when initTopics is empty (minimal, non-breaking) ```go // client.go, end of startUp() ticker.Tick(f, time.Second*30, cli.done) // Only wait for Settings if topics were pre-registered. // When initTopics is empty there is no Telemetry stream yet; // the stream (and Settings) will be established lazily on first Send. if len(cli.initTopics) == 0 { return nil } for !cli.inited.Load() { if cli.startUpError != nil { return cli.startUpError } sugarBaseLogger.Infoln("wait for sync settings finish") time.Sleep(time.Second) } ``` Option B — Establish a Telemetry stream unconditionally at startup (more robust) ```go Initiate a Telemetry connection to cli.accessPoint directly in startUp(), independent of topic route queries. This removes the coupling entirely and makes the Settings handshake reliable regardless of whether WithTopics was used. ``` Option C — Add a timeout to the wait loop (safety net, not a full fix) ```go deadline := time.Now().Add(30 * time.Second) for !cli.inited.Load() { if cli.startUpError != nil { return cli.startUpError } if time.Now().After(deadline) { return fmt.Errorf("timed out waiting for broker Settings after 30s; " + "no topics pre-registered via WithTopics, Telemetry stream not established") } time.Sleep(time.Second) } ``` This at minimum turns an infinite hang into a fast-fail with a clear error message. --- Workaround (for users on v5.1.4 today) Pass at least one known business topic via WithTopics(...) when creating the producer. A single topic is sufficient to trigger the Telemetry handshake; other topics used in Send() calls continue to work via the existing lazy-fetch path: ```go producer, _ := golang.NewProducer( config, golang.WithMaxAttempts(3), golang.WithTopics("your_topic"), // ← unblocks Start() ) ``` Note: this is a workaround, not the correct long-term fix. WithTopics should remain optional as it was before v5.1.4. --- Related - defaultClient.startUp(): client.go:540–608 - defaultClient.inited field: client.go:239 - onSettingsCommand() sets inited: client.go:726 - defaultClientSession.startUp() propagates error: client.go:122–125 - WithTopics() option: producer_options.go:78 -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
