It would be quite hard in core NATS, but should be doable in JetStream. I even wrote some code locally to prototype it. JetStream has “workqueue” retention policy and “sealed” property on stream. If this is not enough stream metadata can be used to indicate closed channel.
Hi! I’m the author of the other NATS.jl package! Congratulations for your efforts, your take seems to be very well polished.
I started my take some time ago to test the base NATs protocol but guess I made a mistake in that the client will connect to all the members of a cluster and will distribute messages across them and will listen to all of them, while probably only one connection is ok and the other urls are for failover.
My take was based on in-memory Julia Channels to try to ensure that the client could be used by a multithreaded application safely and that the operations were non-blocking. Is this something that is handled in some way in your package?
If you want some feedback, I’d get rid of all the “default connections” and in general global state outside of the main client itself, and I would keep passing the client explicitely.
I guess I could point to your package from my repo as I wasn’t planning on maintaining this effort.
I won’t complain if you want to register it in the general registry under NATS.jl
Thanks. I saw your package long after I started my development (even gave star to it) so it was hard to contribute to your code at this stage without destroying your API.
Interesting approach, but in practice I don’t think so it will speed up message deliveries or increase robustness. Critical part for performance seems to be protocol parser and buffering of outbound publications. I also had some chats with NATS server authors and really important is to not break “at most once” guarantee, what might be hard if you try to deliver message to multiple servers.
ADR-40 document describes how reconnects should be performed.
Yes! Underneath I use channels as well, connection should be threadsafe. I just found syntax with do more readable. Tests are ran with multithreading on CI, no serious issues so far, but maybe I omitted some use cases.
For multithreaded subscription message handling there is async_handlers (I think will rename to spawn) flag which allows to process each message in a separate task.
I found most of the time I use only single connection, so this default connection was for making code less verbose. I agree it might be over engineering and lead to confusion. Will think about deleting it or provide different syntax or API, something like with block or some other wrapper package. This needs some discussion, maybe will prepare some RFC document with possible solutions I see. Definitely passing connection object (which all other clients do) is not something I want to see in my code, cause this not feel to be Julia way.
Cool, I am pretty determined to invest more time into NATS client.
If more people request it surely will do, but it is too early to do it now I think, maybe in next month or two I will have final API ready (for instance default connection feature redesign). I need also to improve docs and run more tests in Kubernetes and JetStream environment.
I see, I think it’s just a matter of object.method(a, b) that in Julia becomes method(object, a, b), no big deal for me, in every other client from other languages (that I’m aware of), there is no implicit client connection, it’s all bound to objects, I think it would be best to keep it in that way.
I can see in some specific application, e.g. a web server or a worker, the convenience of having methods that just refer to the fact that you just want to publish a message or receive one, and the connection is implicitely the one of the application, but in a library, I think it’s best to pass around everything, especially stateful objects that hold external resources such as tcp connections.
Also, I don’t think you need to provide solutions to the method(main_object, arg1, arg2, …) with blocks or special syntax, hopefully someone else will propose a good solution, or we will just get over it and accept the method(object, a1, a2) syntax instead of the obj.method(…) one.
Also I was rethinking design of this default connection, and it seems what I really want is dynamically scoped variable. It looks like ScopedValues.jl will be available in 1.11 out of the box.
There is nice article linked in PR from Java design JEP 446: Scoped Values (Preview)
Use cases described there are exactly what I had in mind. I created draft PR with implementation, example usage is in PR description.