Skip to content

Commit dd4e7f3

Browse files
committed
Deal with MQ C client calling MQCB callbacks with wrong hObj (#217)
1 parent 425dd02 commit dd4e7f3

File tree

2 files changed

+66
-5
lines changed

2 files changed

+66
-5
lines changed

ibmmq/mqiCBD.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ func copyCBDtoC(mqcbd *C.MQCBD, gocbd *MQCBD) {
6464
// CallbackArea is always set to NULL here. The user's values are saved/restored elsewhere
6565
mqcbd.CallbackArea = (C.MQPTR)(C.NULL)
6666

67-
setMQIString((*C.char)(&mqcbd.CallbackName[0]), gocbd.CallbackName, 128) // There's no MQI constant for the length
67+
// This is never actually used because it's the (XOR) alternative to the CallbackFunction. Ignore
68+
// anything set by the application because it would give an error. We can't hide the
69+
// field now for compatibility reasons, just in case. But it should be marked as deprecated.
70+
setMQIString((*C.char)(&mqcbd.CallbackName[0]), "", 128) // There's no MQI constant for the length
6871

6972
mqcbd.MaxMsgLength = C.MQLONG(gocbd.MaxMsgLength)
7073

ibmmq/mqicb.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,31 @@ This file deals with asynchronous delivery of MQ messages via the MQCTL/MQCB ver
3030
3131
extern void MQCALLBACK_Go(MQHCONN, MQMD *, MQGMO *, PMQVOID, MQCBC *);
3232
extern void MQCALLBACK_C(MQHCONN hc,MQMD *md,MQGMO *gmo,PMQVOID buf,MQCBC *cbc);
33+
34+
// These functions deal with stashing the hObj value across callbacks, because
35+
// the MQ C client will sometimes set hObj=0 for EVENTS (eg qmgr stopping) instead
36+
// of the registered hObj. That can lead to unexpected callback invocations.
37+
static void *saveHObj(PMQCBD mqcbd, MQHOBJ hObj) {
38+
mqcbd->CallbackArea = malloc(sizeof(MQHOBJ));
39+
if (mqcbd->CallbackArea) {
40+
memcpy(mqcbd->CallbackArea,&hObj,sizeof(MQHOBJ));
41+
}
42+
return mqcbd->CallbackArea;
43+
}
44+
45+
static MQHOBJ getHObj(PMQCBC mqcbc) {
46+
MQHOBJ ho = 0;
47+
if (mqcbc->CallbackArea != NULL) {
48+
memcpy(&ho,mqcbc->CallbackArea,sizeof(MQHOBJ));
49+
}
50+
return ho;
51+
}
52+
53+
static void freeHObj(void *p) {
54+
if (p) {
55+
free(p);
56+
}
57+
}
3358
*/
3459
import "C"
3560
import (
@@ -47,6 +72,7 @@ type MQCB_FUNCTION func(*MQQueueManager, *MQObject, *MQMD, *MQGMO, []byte, *MQCB
4772
// be passed onwards
4873
type cbInfo struct {
4974
hObj *MQObject
75+
stashedHObj unsafe.Pointer
5076
callbackFunction MQCB_FUNCTION
5177
callbackArea interface{}
5278
connectionArea interface{}
@@ -98,14 +124,28 @@ func MQCALLBACK_Go(hConn C.MQHCONN, mqmd *C.MQMD, mqgmo *C.MQGMO, mqBuffer C.PMQ
98124
verb: "MQCALLBACK",
99125
}
100126

101-
key := makeKey(hConn, mqcbc.Hobj)
127+
// The MQ C client seems to sometimes use 0 as the hObj for EVENTs even for
128+
// callbacks that should be going to something registered for a specific queue. This
129+
// is different from using local bindings and is possibly a bug in the underlying MQ library.
130+
// To try to work round this, the real hObj has been stashed during the REGISTER phase and if it's
131+
// available via the C context structures, then we try to use that in
132+
// an attempt to find the appropriate function.
133+
passedHo := mqcbc.Hobj
134+
savedHo := C.getHObj(mqcbc)
135+
logTrace("HOs in callback are passed: %d stashed: %d", passedHo, savedHo)
136+
137+
if passedHo == C.MQHO_NONE && savedHo != C.MQHO_NONE {
138+
passedHo = savedHo
139+
}
140+
141+
key := makeKey(hConn, passedHo)
102142
mapLock()
103143
info, ok := cbMap[key]
104144
mapUnlock()
105145

106-
// The MQ Client libraries sometimes call us with an EVENT that is
107-
// not associated with a particular hObj.
108-
// The way I've chosen is to find the first entry in
146+
// With the stashed hObj available, we should now be able to find the
147+
// correct callback routine from the map. But just in case we can't, we will
148+
// have a fallback position. The way I've chosen is to find the first entry in
109149
// the map associated with the hConn and call its registered function with
110150
// a dummy hObj.
111151
if !ok {
@@ -197,6 +237,7 @@ func (object *MQObject) CB(goOperation int32, gocbd *MQCBD, gomd *MQMD, gogmo *M
197237
if f1 != nil {
198238
f1(gogmo.OtelOpts, object.qMgr, object, gogmo, true)
199239
}
240+
200241
copyCBDtoC(&mqcbd, gocbd)
201242
copyMDtoC(&mqmd, gomd)
202243
copyGMOtoC(&mqgmo, gogmo)
@@ -207,6 +248,13 @@ func (object *MQObject) CB(goOperation int32, gocbd *MQCBD, gomd *MQMD, gogmo *M
207248
// defined here. And that in turn will call the user's callback function
208249
mqcbd.CallbackFunction = (C.MQPTR)(unsafe.Pointer(C.MQCALLBACK_C))
209250

251+
// See earlier comments about how the hObj is not always passed back. Here's where
252+
// we stash it via a malloc
253+
p := C.NULL
254+
if mqOperation == C.MQOP_REGISTER {
255+
p = C.saveHObj(&mqcbd, object.hObj)
256+
}
257+
210258
C.MQCB(object.qMgr.hConn, mqOperation, (C.PMQVOID)(unsafe.Pointer(&mqcbd)),
211259
object.hObj,
212260
(C.PMQVOID)(unsafe.Pointer(&mqmd)), (C.PMQVOID)(unsafe.Pointer(&mqgmo)),
@@ -218,6 +266,10 @@ func (object *MQObject) CB(goOperation int32, gocbd *MQCBD, gomd *MQMD, gogmo *M
218266
}
219267

220268
if mqcc != C.MQCC_OK {
269+
// Don't leak these 4 bytes when the register failed
270+
if mqOperation == C.MQOP_REGISTER {
271+
C.freeHObj(p)
272+
}
221273
traceExitErr("CB", 3, &mqreturn)
222274
return &mqreturn
223275
}
@@ -226,11 +278,17 @@ func (object *MQObject) CB(goOperation int32, gocbd *MQCBD, gomd *MQMD, gogmo *M
226278
switch mqOperation {
227279
case C.MQOP_DEREGISTER:
228280
mapLock()
281+
// ... and this is where the corresponding free should happen
282+
info, ok := cbMap[key]
283+
if ok {
284+
C.freeHObj(info.stashedHObj)
285+
}
229286
delete(cbMap, key)
230287
mapUnlock()
231288
case C.MQOP_REGISTER:
232289
// Stash the hObj and real function to be called
233290
info := &cbInfo{hObj: object,
291+
stashedHObj: p,
234292
callbackFunction: gocbd.CallbackFunction,
235293
connectionArea: nil,
236294
callbackArea: gocbd.CallbackArea,

0 commit comments

Comments
 (0)