Skip to content

Concurrency issue with Inq: concurrent map writes #203

@kalmant

Description

@kalmant

Description

Multiple goroutines calling MQObject.Inq at the same time (even on different MQObjects!) can result in fatal error: concurrent map writes.

Why

Inq calls getAttrInfo which writes the global mqInqLength map guarded by charAttrsAdded. However, such a guard is not reliable when there are concurrent calls.

Dump would look something like:

fatal error: concurrent map writes

goroutine 92 [running]:
github.com/ibm-messaging/mq-golang/v5/ibmmq.getAttrInfo({0xc00100a008, 0x2, 0x46efee?})
	/go/src/github.com/kalmant/my-repo/vendor/github.com/ibm-messaging/mq-golang/v5/ibmmq/mqiattrs.go:156 +0x12f
github.com/ibm-messaging/mq-golang/v5/ibmmq.MQObject.Inq({0xc0cb40?, 0xc000e0a0f0?, {0xc001006030?, 0xc001004000?}}, {0xc00100a008?, 0x2, 0x2})
	/go/src/github.com/kalmant/my-repo/vendor/github.com/ibm-messaging/mq-golang/v5/ibmmq/mqi.go:814 +0xc5
...

Suggested solution

If the goal is to ensure the code block in mqiattrs.go L144-159 only gets called once then using sync.Once would be a perfect solution.
I'm opening a PR (#204) with this solution. Feel free to close that if you believe there's a better solution or I misunderstood the root cause.

Versions

  • go version go1.21.X linux/amd64
  • mq-golang version: v5.5.1
  • MQ version: 9.3.X (but I don't think this matters)

Small code sample that demonstrates the issue

package main

import (
	"github.com/ibm-messaging/mq-golang/v5/ibmmq"
)

func getBackoutParameters(queue *ibmmq.MQObject) error {
	selectors := []int32{
		ibmmq.MQCA_BACKOUT_REQ_Q_NAME,
		ibmmq.MQIA_BACKOUT_THRESHOLD,
	}
	// values are not important from the POV of reproducing the issue
	_, err := queue.Inq(selectors)
	return err
}

func initializeQueues() []*ibmmq.MQObject {
	// NOTE: these queues would be created by opening them on *different* `ibmmq.MQQueueManager` instances
	// using e.g. `ibmmq.MQOO_INPUT_EXCLUSIVE|ibmmq.MQOO_INQUIRE` as options

	// let's say we return a slice of 10 pointers
	return nil
}

func main() {
	queues := initializeQueues()
	for _, q := range queues {
		q := q
		go func() {
			if err := getBackoutParameters(q); err != nil {
				panic(err)
			}
		}()
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions