Belyenochi opened a new issue, #1231:
URL: https://github.com/apache/rocketmq-client-go/issues/1231
## BUG REPORT
### 1. Issue Description
**What did you do (The steps to reproduce)?**
Initialize multiple producers connecting to **different NameServer
addresses** in the same process:
```go
// First producer - connects to NameServer A
p1, _ := rocketmq.NewProducer(
producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"ns-a.example.com:9876"})),
producer.WithTrace(&primitive.TraceConfig{
TraceTopic: "trace-topic",
NamesrvAddrs: []string{"ns-a.example.com:9876"},
}),
)
// Second producer - connects to NameServer B (DIFFERENT from first)
p2, _ := rocketmq.NewProducer(
producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"ns-b.example.com:9876"})),
producer.WithTrace(&primitive.TraceConfig{
TraceTopic: "trace-topic",
NamesrvAddrs: []string{"ns-b.example.com:9876"},
}),
)
```
**What did you expect to see?**
Either:
- Both producers initialize successfully, OR
- The second producer returns an error explaining the conflict
**What did you see instead?**
Panic on the second producer initialization:
```
panic: runtime error: invalid memory address or nil pointer dereference
goroutine 1 [running]:
github.com/apache/rocketmq-client-go/v2/internal.(*traceDispatcher).Start(0x0)
internal/trace.go:306
github.com/apache/rocketmq-client-go/v2/producer.newTraceInterceptor(...)
producer/interceptor.go:47
```
### 2. Environment
- **OS:** Linux (Ubuntu 22.04) / Also reproducible on macOS
- **Go version:** 1.21+
- **Client version:** v2.1.1 (latest)
- **RocketMQ version:** N/A (issue is in Go client)
### 3. Root Cause Analysis
The bug is in `producer/interceptor.go` lines 44-48:
```go
func newTraceInterceptor(dispatcher internal.TraceDispatcher)
primitive.Interceptor {
if dispatcher != nil { // ← This check is ineffective!
dispatcher.Start() // ← PANIC here
}
// ...
}
```
**The Problem: Go Interface Nil Trap**
When `internal.NewTraceDispatcher()` fails (e.g., due to ClientID conflict
in `GetOrNewRocketMQClient`), it returns a **nil pointer of concrete type**
`*traceDispatcher`:
```go
// trace.go line 238
func NewTraceDispatcher(traceCfg *primitive.TraceConfig) *traceDispatcher {
cli := GetOrNewRocketMQClient(cliOp, nil)
if cli == nil {
return nil // Returns (*traceDispatcher)(nil), not interface nil!
}
// ...
}
```
When this nil pointer is assigned to the `TraceDispatcher` interface
parameter:
```go
dispatcher := internal.NewTraceDispatcher(traceCfg) // type:
TraceDispatcher interface
// Internal representation: (type=*traceDispatcher, value=nil)
```
The interface itself is **not nil** (because it has type information), so
`dispatcher != nil` evaluates to `true`, but calling `dispatcher.Start()`
dereferences a nil pointer → **PANIC**.
```
┌─────────────────────────────────────────┐
│ Interface variable: dispatcher │
├───────────────────┬─────────────────────┤
│ type: *traceDispatcher │ value: nil │
└───────────────────┴─────────────────────┘
↓
dispatcher != nil → TRUE (type is not nil)
dispatcher.Start() → PANIC (value is nil)
```
### 4. Suggested Fix
**Option A: Fix the nil check using reflection**
```go
import "reflect"
func newTraceInterceptor(dispatcher internal.TraceDispatcher)
primitive.Interceptor {
// Proper nil check for interface wrapping nil pointer
if dispatcher != nil && !reflect.ValueOf(dispatcher).IsNil() {
dispatcher.Start()
}
// ...
}
```
**Option B (Recommended): Return error from NewTraceDispatcher**
```go
// trace.go
func NewTraceDispatcher(traceCfg *primitive.TraceConfig) (*traceDispatcher,
error) {
cli := GetOrNewRocketMQClient(cliOp, nil)
if cli == nil {
return nil, errors.New("failed to create RocketMQ client for trace
dispatcher")
}
// ...
return &traceDispatcher{...}, nil
}
// interceptor.go
func WithTrace(traceCfg *primitive.TraceConfig) Option {
return func(options *producerOptions) {
dispatcher, err := internal.NewTraceDispatcher(traceCfg)
if err != nil {
rlog.Warning("trace dispatcher creation failed",
map[string]interface{}{
"error": err,
})
return
}
// ...
}
}
```
**Option C: Return interface nil directly**
```go
// trace.go - change return type to interface
func NewTraceDispatcher(traceCfg *primitive.TraceConfig) TraceDispatcher {
cli := GetOrNewRocketMQClient(cliOp, nil)
if cli == nil {
return nil // Now returns true interface nil
}
return &traceDispatcher{...}
}
```
### 5. Importance
**Blocker** - This causes unrecoverable panics in production when:
- Running multiple producers/consumers in microservices connecting to
different MQ clusters
- Multi-tenant applications with environment isolation (prod/test/dev)
### 6. Current Workaround
Wrap producer creation with `recover()`:
```go
func createProducerSafe(opts ...producer.Option) (p rocketmq.Producer, err
error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("producer init panic: %v", r)
}
}()
return rocketmq.NewProducer(opts...)
}
```
---
**Related Code References:**
- `producer/interceptor.go:44-48` - Ineffective nil check
- `internal/trace.go:234-239` - Returns nil pointer on failure
- `internal/client.go:209-213` - GetOrNewRocketMQClient returns nil on
NameServer conflict
--
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]