absinthe
absinthe copied to clipboard
Subscription priming/ordinals
This is the absinthe
part of my attempt to address the issue discussed over in https://elixirforum.com/t/subscription-catchup-state-delta/47459. There will be a corresponding absinthe_phoenix
PR shortly.
I still need to add documentation, so consider this a WIP, but I'd appreciate feedback on whether this is going to be an acceptable way forward before I spend time writing it :)
There are 3 main elements on which the solution is built:
Continuations
This is a trimmed down version of the continuation system I wrote to implement the @defer
directive in #559. I used this generalised system because it seems like it could be a useful addition for other features in the future (@defer
being the obvious example) and I didn't want to just write code that was specific to subscription priming. Plus @benwilson512 mentioned at the time that he liked the idea :)
Subscription priming
This is a rework/renaming of the subscription-catchup system I wrote a while back. Subscription configurations are given an optional prime
parameter which is a function called when a user first subscribes. The result of that function is sent only to the subscribing user, allowing them to "catch up" to where the subscription state is for all other subscribers.
Subscription ordinals
(Feel free to come up with a better name - "ordinals" was the best I could think of).
This feature addresses the issue of the asynchronous handling of subscription updates which have a strict semantic ordering, such as state updates. This is the specific issue that is explained in the Elixir Forum post above. Subscription configs can now be given an ordinal
function which is passed the root_value
of the subscription update and returns a term which will be used for ordering of updates. The actual functional bit of this (dropping out-of-date updates) is performed over in the corresponding absinthe_phoenix
changes (which I'll reference here as soon as I've opened that PR). The key functionality here, though, is to have the user able to specify a way to determine ordering on updates. This could be a timestamp, simple integer or any other value that will be <
when compared between older and newer updates. (I did consider allowing a customisable comparator function too but that seemed like overkill to me - happy to do so though).
For all these changes I've tried to leave everything backwards compatible, so not specifying a prime
or ordinal
function will behave exactly as it does now.
Hey @bernardd thanks for this. Hoping to review later today.
If i understood correctly, this prime mechanism can be also used to just send data to the connected client as a callback after the topic subscription right?
Having this in mind WDYT of this name alternative suggestions?
prime
- data
- data_callback
- client_callback
- callback
ordinal
- data_callback_filter_by
- client_callback_filter_by
- data_filter_by
- callback_filter_by
It could be used for that, certainly, though only at the initialisation of the subscription, not at arbitrary later points.
I'm reluctant to endorse something like data
because it's about the most generic possible word in programming and doesn't really convey the particular intent or behaviour of this feature.
Pinging @benwilson512 again here on a review; priming in particular would be very helpful for our use case.
@bernardd As a quick sanity check, does this handle the race condition highlighted here where the item pushed would happen prior to the client actually subscribing to the phoenix pubsub topic?
Well this is awkward. I was all set to explain how it did and...it doesn't. I'd entirely missed the subtlety that PubSub.subscribe
isn't called until after the subscription itself has returned. I think this should be fixable, though. I'll get back to you :)
Okay, it totally does handle that condition now :)
The prime
callback function is now passed through in the continuation and not actually called until the continuation is executed. This means that the initial subscription request has returned back up to absinthe_phoenix, which has in turn had its chance to call PubSub.subscribe
. It then calls Absinthe.continue
which executes the prime
function. Since the subscription is already in place, the state produced by the prime call must be calculated strictly after that point, leaving no window in which for state updates to get lost.
Note that this did require one extra tweak: continuations can now return a special :no_more_results
result, which indicates to the caller that there's actually nothing more to send back to the client even though there was a continuation returned by the previous run
or continue
call.
OK this is cool.
Hey @benwilson512 - are you happy with the general approach here? If so I'll start adding some docs to get this closer to being mergeable. Cheers.
@bernardd a couple of quick updates:
- I am quite happy with the approach here.
- I am on paternity leave for the next two months. As I am able in this time I hope to catch up on some of my open source projects, and this PR is the top of the list.
As a general note, I am extremely grateful for your patience with this project and myself. You've submitted really thoughtful work over the years and I have done a very poor job of keeping up with reviews or helping guide them towards a merge.
Thanks for sticking with it!
Bike shed about the name "ordinal".
I like ordinal
in general, but it's not super clear at first. Once you understand what it does, it does make sense, but it doesn't help devs intuitively understand what it's for.
FWIW, I feel the same way about prime
. Not opposed, but it's not intuitive. Something like after_subscribe
or on_subscribe
or equivalent is probably more clear, though a bit more verbose.
Shout out to @bernardd for sneaking in mathematical terms tho. ;)
- I am on paternity leave for the next two months. As I am able in this time I hope to catch up on some of my open source projects, and this PR is the top of the list.
Congratulations! Don't push yourself on my account - take care of yourself and your family first :) I know how much work a new kid can be even under the best of circumstances.
As a general note, I am extremely grateful for your patience with this project and myself. You've submitted really thoughtful work over the years and I have done a very poor job of keeping up with reviews or helping guide them towards a merge.
Not a worry - in case I didn't already know it, taking over ExAws has reminded me just how easy it is to let PRs slide when it's not top priority at your day job :)
I like
ordinal
in general, but it's not super clear at first. Once you understand what it does, it does make sense, but it doesn't help devs intuitively understand what it's for.
That's what docs are for :P More seriously, I'm all for a more intuitive name if we can come up with one.
Shout out to @bernardd for sneaking in mathematical terms tho. ;)
"Prime" was entirely Ben's idea, and "ordinal" was simply because I literally couldn't think of a better word :)
Hey, I am working on a project where I more or less desperately need this feature at this point. So I was looking at the code, and realized that there is no subscription callback and no way I could think of to hack it in any halfway sane way to make it work. Then I came here to offer to make this feature for you from scratch, but now I am delighted to see that @bernardd has already taken to it. Is there anything I can help with to get this done faster? This has not been touched for some time now and its pretty important for my project. For now I will use the feature branch. Also if you want a good name for it, just call it after_subscription_callback, or just subscription_callback, because that is literally what it is. prime and ordinal are confusing imo
Also just consider the following observation from me testing this: It would have already been enough to just have a callback where I know that the subscription has already happened. Everything else can be done more or less manually by just calling the publish functions, in my case having the prime functionality where I also send the result of this function to the client is a welcome addition as it solves another problem that I have - but it does not HAVE to be the case. There is a possibility that you do book keeping manually because you have a complex system of which subscriptions are actually triggered, and so you might or might not trigger the prime subscription, you do not know for sure yet, but you still need to register and unregistered the subscription in your bookkeeping. The feature that is missing the most and which makes it impossible to do something manually about it, is that you do not know when you have finished subscribing and unsubscribing... which this feature definitely handles, but it also does more then that which might again limit the possibilities for some people as it is a constrain on what you can do.
Are there any plans to continue this (or similar) work? I would love a way to send an initial value when a user establishes to a subscription. I'd be happy to try contributing if there's a bandwidth issue!