Summary
Since #1440, a subscription callback that declares two parameters receives a MessageInfo as its second argument. This works for deserialising subscriptions but silently does nothing for { isRaw: true } subscriptions: the second argument is always undefined.
The typings promise it:
// types/subscription.d.ts
type SubscriptionWithRawMessageCallback =
(message: Buffer, messageInfo?: MessageInfo) => void;
and Subscription.processResponse in lib/subscription.js is ready to forward it for the raw case (if (this._isRaw) { if (this._wantsMessageInfo && messageInfo) ... }), but the raw take path never produces one:
lib/node.js takes raw samples with rclnodejs.rclTakeRaw(subscription.handle) and calls subscription.processResponse(rawMessage) with no info, regardless of subscription.wantsMessageInfo.
src/rcl_subscription_bindings.cpp RclTakeRaw calls rcl_take_serialized_message(subscription, &msg, nullptr, nullptr), so the message info is never requested from rcl in the first place.
Only the deserialising path (RclTakeWithInfo, which calls rcl_take(..., &message_info, nullptr)) builds the info object.
Reproduce
const rclnodejs = require('rclnodejs');
(async () => {
await rclnodejs.init();
const node = new rclnodejs.Node('raw_info_repro');
const pub = node.createPublisher('std_msgs/msg/String', 'chatter');
let seen = 0;
node.createSubscription('std_msgs/msg/String', 'chatter', { isRaw: true },
(buffer, info) => {
console.log('raw buffer:', buffer.length, 'bytes; info:', info); // info is always undefined
if (++seen >= 3) process.exit(0);
});
node.spin();
setInterval(() => pub.publish({ data: 'hi' }), 100);
})();
Observed on rclnodejs 2.1.1 (ROS 2 Jazzy, rmw_fastrtps_cpp); the code on develop is unchanged, so 2.2.0 behaves the same.
Why it matters
Raw subscriptions are the natural choice for bridges that forward CDR bytes without deserialising (web/WebRTC relays, recorders). Those bridges need publisherGid most: when several publishers share one topic, which is normal for joint_states and /tf, a latest-sample-per-topic throttle overwrites the slow publisher with the fast one. We hit exactly this: a PTZ adapter publishing two joints at 17 Hz next to a joint_state_broadcaster at 170 Hz on one joint_states topic reached the browser only ~9% of the time. Keying by publisherGid is the clean fix, and the typings suggested it was available, but on the raw path it never is. Without it, a bridge has to partially decode every message to tell publishers apart.
Proposed fix
Keep rclTakeRaw as is for callbacks that do not want info, and add a sibling rclTakeRawWithInfo that calls rcl_take_serialized_message(subscription, &msg, &message_info, nullptr) and returns { buffer, info }, sharing the info-object construction with RclTakeWithInfo. In lib/node.js, the raw branch uses it when subscription.wantsMessageInfo and wraps the result in MessageInfo, mirroring the deserialising branch. Plus a test alongside test/test-message-info.js for a raw subscription with a two-parameter callback.
I have this change ready and would be happy to open a PR if you agree with the approach.
Summary
Since #1440, a subscription callback that declares two parameters receives a
MessageInfoas its second argument. This works for deserialising subscriptions but silently does nothing for{ isRaw: true }subscriptions: the second argument is alwaysundefined.The typings promise it:
and
Subscription.processResponseinlib/subscription.jsis ready to forward it for the raw case (if (this._isRaw) { if (this._wantsMessageInfo && messageInfo) ... }), but the raw take path never produces one:lib/node.jstakes raw samples withrclnodejs.rclTakeRaw(subscription.handle)and callssubscription.processResponse(rawMessage)with no info, regardless ofsubscription.wantsMessageInfo.src/rcl_subscription_bindings.cppRclTakeRawcallsrcl_take_serialized_message(subscription, &msg, nullptr, nullptr), so the message info is never requested from rcl in the first place.Only the deserialising path (
RclTakeWithInfo, which callsrcl_take(..., &message_info, nullptr)) builds the info object.Reproduce
Observed on rclnodejs 2.1.1 (ROS 2 Jazzy, rmw_fastrtps_cpp); the code on
developis unchanged, so 2.2.0 behaves the same.Why it matters
Raw subscriptions are the natural choice for bridges that forward CDR bytes without deserialising (web/WebRTC relays, recorders). Those bridges need
publisherGidmost: when several publishers share one topic, which is normal forjoint_statesand/tf, a latest-sample-per-topic throttle overwrites the slow publisher with the fast one. We hit exactly this: a PTZ adapter publishing two joints at 17 Hz next to ajoint_state_broadcasterat 170 Hz on onejoint_statestopic reached the browser only ~9% of the time. Keying bypublisherGidis the clean fix, and the typings suggested it was available, but on the raw path it never is. Without it, a bridge has to partially decode every message to tell publishers apart.Proposed fix
Keep
rclTakeRawas is for callbacks that do not want info, and add a siblingrclTakeRawWithInfothat callsrcl_take_serialized_message(subscription, &msg, &message_info, nullptr)and returns{ buffer, info }, sharing the info-object construction withRclTakeWithInfo. Inlib/node.js, the raw branch uses it whensubscription.wantsMessageInfoand wraps the result inMessageInfo, mirroring the deserialising branch. Plus a test alongsidetest/test-message-info.jsfor a raw subscription with a two-parameter callback.I have this change ready and would be happy to open a PR if you agree with the approach.