Add Context Router with Middleware Support - #321
Conversation
72adad9 to
cb97a80
Compare
MattBrittan
left a comment
There was a problem hiding this comment.
I've added a few comments to your code. My main point here is that I don't think this should require changes in client (because client.Router is depreciated so you should not try to duplicate this).
Having reviewed this I'm still a bit uncertain about it's utility - it adds quite a bit of code that could be fairly quickly handled in OnPublishReceived (without the need for the added overhead). My initial thought is that this library should only provide a basic router that will meet the majority of needs (to help users get started and provide an example) - users with more complex requirements can use OnPublishReceived to do whatever they need.
Note that my reluctance here is not really related to your code. Experience from the V3 client indicates that we should try to keep the client as small as possible (we need to support anything added so want to limit additions to stuff that most users need, whist providing ways for other users to meet their unique needs themselves). The reason for this approach is that if I accept this PR the next request may well be to add support for something like #71 (which is a valid request but not common enough to be worth adding here - we cannot be all things to all users).
I will be very interested in feedback from others reading this PR because othere may see this as an essential addition (it's not something that I would use myself).
Currently my feeling is that it might be more appropriate to:
- Add a
readmetopaho/extensions&autopaho/extensionsstating that items in this folder are not part of the core library, are not covered by semantic versioning, may be broken by updates eleswhere, support will be limited etc. - Move this PR into those folder (after removing the changes to
client.goetc). - Possibly reference it in the main README (I do need to update that anyway)
| // ContextRouter is an alternative message router that receives context.Context along with the message. | ||
| // It is used when Router is not set, allowing context-aware message handling for operations | ||
| // that require context propagation, cancellation, or timeout control. | ||
| ContextRouter ContextRouter |
There was a problem hiding this comment.
Router Router notes that it's depreciated so I'm not keen to see an additional router added here (the idea is that users utilise OnPublishReceived (the README notes "ClientConfig.Router will be removed in a future release"). There are a few reasons for this change, but the main one is that its difficult for us (library authors) to predict how this will be used (and example being routing based on Subscription identifier - see #168 for some discussion on this).
| m := PublishFromPacketPublish(pb) | ||
|
|
||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| defer cancel() |
There was a problem hiding this comment.
Can you please explain the utility of this? cancel will be called when Route completes atc which time any called routines should have completed (so I think that just using context.Background might make more sense?). Sorry - it may just be that I don't understand your aim here?
There was a problem hiding this comment.
Agree, will be changed to use context.Background only, before i think we can get base context from mqtt client, like when mqtt shutdown and has long running process at handler, so it can cancel with some timeout.
| } | ||
| } | ||
|
|
||
| func (r *StandardContextRouter) wrapHandler(h MessageContextHandler) MessageContextHandler { |
There was a problem hiding this comment.
If there are multiple handlers then the middleware will be called multiple times and I wonder if that is what a user would expect? (guess it depends on what the middleware does).
There was a problem hiding this comment.
Yes it depend on what middleware, i think middleware like for recoverer it expected to called multiple times when processing message at handler, because if we has panic when processing mqtt packet it can make application crash.
|
Hi @MattBrittan , thanks for your feedback, already update this pr, also for extensions is only at paho, not on autopaho, at autopaho only for example use it. |
|
@MattBrittan here's my 2 cents. The StandardRouter type itself is useful enough to where I basically use it as the only handler right now. The topic matching is useful. That said, I haven't tried to (or needed to (yet)) extend the StandardRouter for further purposes. I think your instincts are right that people's use cases will be varied enough that it's probably wiser to pursue extensibility over thorough functionality. To that end, it might be better to focus on having composable building blocks. One way that would make it easier for people to implement their own router-like constructs would be to make the route matching logic exported. The StandardRouter doesn't actually do a whole lot more than loop through the registered handlers, check if they match, then call all the handlers for said subscription. By having access to the match function, it would be pretty easy to implement your own router (such as one that passes along a context). One microcosm of the varied use cases is that the fact that this router uses context.Background means that I wouldn't be able to use it effectively for my case. My applications always pass in a single parent context from the main function for cancellation purposes. This implementation does not provide that, but maybe that's perfectly fine for the author's case. Even though I need to pass context along to a lot of my handlers, I wouldn't be able to use this router to accomplish it. For middleware, it would be perfectly fine for me to simply add additional handlers to the front of the Just brainstorming on my own a bit here, but a good way to nudge users into the right direction might be to build out a combination of documentation and examples to show various ways to accomplish different cases. I think mosquitto does this well for its plugin API. There are a lot of examples that show what you can accomplish, and it's a good justification to avoid adding too many bells and whistles to the API. As far as accepting this Router, I would say that's really up to you on how you want to handle extensions. If this were my project, I might request that this be moved to be included only as an example, or be added to some contrib repo. |
|
I agree that using Proposed API: This allows applications to pass their own parent context (e.g., from signal.NotifyContext), while keeping the existing Route signature working as before. |
|
@nickajacks1 thanks very much for your thoughts (as a maintainer I ofter second guess myself when responding to PR's like this so do really appreciate another view).
The challange with this is that once we make something public we then cannot change it without a new major version (well technically this only applies once we hit V1). As such I've been fairly reluctant to make things public unless there is a compelling reason (have written some fairly nasty workarounds to avoid breaking compatability in the V3 client!). I believe the standard approach here is to recommend that users copy the relevant code (40 odd lines in this case) and I think thats generally workable? I don't really have a feel for how many users need a modified router (suepect it's fairly low and their requirements are fairly diverse). Always love seeing the unexpected ways users utilise the library!.
This was one of my concerns - everyone has different requirements and I don't think there is a one size fits all option. Personally I'd probably just wrap the handlers :-).
Would be very happy to see submisions like this :-).
This was pretty much my feeling. However the challange is the split between paho and autopaho (don't really want an autopaho example to import a paho example) - this is why I thought the extension folder might be a better option. I did consider suggesting a contrib repo but don't think usage is currently big enough to justify this (trying to keep admin to a minimum!) and feel that specifically calling out that this is not covered by SEMVER should be OK for now. Fully agree with regards to extra dependencies (would be unlikely to accept PR's that add additional production dependencies). |
I would not really worry about this. The Other that that I think that this change would make your PR easier to use/understand. I think quite a few devs think of |
MattBrittan
left a comment
There was a problem hiding this comment.
Really just the race condition (others are copied from the current router).
| if pb.Topic != "" { | ||
| // Register new alias | ||
| r.debug.Printf("registering new topic alias '%d' for topic '%s'", *pb.Properties.TopicAlias, m.Topic) | ||
| r.aliases[*pb.Properties.TopicAlias] = pb.Topic |
There was a problem hiding this comment.
Bug (duplicated from main router) - we only have an RLock so should not be modifying aliases. Will raise a separate issue for this (noting it as this was picked up whilst reviewing this PR)
| done := make(chan bool) | ||
|
|
||
| handler := func(ctx context.Context, p *paho.Publish) { | ||
| callCount++ |
There was a problem hiding this comment.
Data race? callCount will be incremented from multiple goroutines (Route only holds a read lock so believe there is a race).
| } | ||
| if t, ok := r.aliases[*pb.Properties.TopicAlias]; ok { | ||
| r.debug.Printf("aliased topic '%d' translates to '%s'", *pb.Properties.TopicAlias, m.Topic) | ||
| topic = t |
There was a problem hiding this comment.
This code (from original) seems likely to cause confusion because it resolves the alias but the message passed to the handler (m) will still have the alias. I'll log a separate issue re this as its an issue in the existing router too.
|
Apologies for the massive delay on this. I think including this as an example makes sense. I think there is a data race in the test so if you can resolve that I'll accept the PR. |
Add Context Router with Middleware Support
Description
This PR introduces a context-aware router extension (
paho/extensions/routercontext/) that provides topic-based message routing with middleware support. Handlers receivecontext.Contextfor cancellation and timeout control.Key Features
context.Contextalong with the message+(single-level) and#(multi-level) patternssync.RWMutexfor concurrent accessChanges
Extension (
paho/extensions/routercontext/):Routerstruct with handler registration and routingHandlerandMiddlewaretypesExamples (
autopaho/examples/routercontext/):Tests (
paho/extensions/routercontext/routercontext_test.go):Documentation:
Usage
Testing
Closes
closes #286
ECA Compliance: Commits are signed with an ECA'd email address.