Symptom
Two subscriptions to the same service path that differ only by a query filter do not stay
independent:
/MyDevice/Acquisition?channelNameFilter=First
/MyDevice/Acquisition?channelNameFilter=Second
Once the second subscription registers, the first receives no further data and the second receives
both streams. Which of the two wins is stable within a run but varies between runs.
Reproduced with two concurrent subscriptions from a single client against a C++ OpenCMW worker, and
again against SubscriptionMatcher alone in a unit test with no broker involved.
Cause
Notifications over mds carry no client request ID, so OpenCmwDataSource.handleRequest() (case
W_NOTIFY) finds the owning subscription by matching the notified topic against the registered
subscription URIs, using:
private final BiPredicate<URI, URI> subscriptionMatcher = new SubscriptionMatcher();
The no-arg constructor sets isPathOnly = true, so test() compares the URI path and ignores the
query. Both subscriptions share the path, so every notification matches every entry and
findFirst() returns an arbitrary one.
SubscriptionMatcher already supports path-and-context matching (testPathAndContext, enabled by
passing Filter classes to the constructor), with its own passing tests. It is never enabled: every
construction site in io.opencmw:client and io.opencmw:server uses the no-arg form.
testPathAndContext only distinguishes query keys that have a Filter registered in
FilterRegistry. Selecting context matching is therefore necessary but not sufficient on its own:
whoever owns a key must also register a Filter for it. Both halves are missing here - the mode is
never selected, and there is currently no way to select it without passing Filter classes to the
constructor, which registers them globally as a side effect.
Environment
io.opencmw 2026.1.0, also reproduces on main
- Java 17 client, C++ OpenCMW worker behind an
mdp/mds broker
PR follows.
Symptom
Two subscriptions to the same service path that differ only by a query filter do not stay
independent:
Once the second subscription registers, the first receives no further data and the second receives
both streams. Which of the two wins is stable within a run but varies between runs.
Reproduced with two concurrent subscriptions from a single client against a C++ OpenCMW worker, and
again against
SubscriptionMatcheralone in a unit test with no broker involved.Cause
Notifications over
mdscarry no client request ID, soOpenCmwDataSource.handleRequest()(caseW_NOTIFY) finds the owning subscription by matching the notified topic against the registeredsubscription URIs, using:
The no-arg constructor sets
isPathOnly = true, sotest()compares the URI path and ignores thequery. Both subscriptions share the path, so every notification matches every entry and
findFirst()returns an arbitrary one.SubscriptionMatcheralready supports path-and-context matching (testPathAndContext, enabled bypassing
Filterclasses to the constructor), with its own passing tests. It is never enabled: everyconstruction site in
io.opencmw:clientandio.opencmw:serveruses the no-arg form.testPathAndContextonly distinguishes query keys that have aFilterregistered inFilterRegistry. Selecting context matching is therefore necessary but not sufficient on its own:whoever owns a key must also register a
Filterfor it. Both halves are missing here - the mode isnever selected, and there is currently no way to select it without passing
Filterclasses to theconstructor, which registers them globally as a side effect.
Environment
io.opencmw2026.1.0, also reproduces onmainmdp/mdsbrokerPR follows.