Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Superlifter wont deliver (lacinia) promise if a fetch request is rejected #27

Open
lennartbuit opened this issue Jan 27, 2022 · 3 comments

Comments

@lennartbuit
Copy link

lennartbuit commented Jan 27, 2022

Heya 👋

Say you have some datasource, that can fail for some reason:

(defrecord Fail [nonce]
  u/Datasource
  (-identity [_] nonce)
  (-fetch! [_ _] (prom/rejected (ex-info "Not good" {}))))

With a resolver that uses with-superlifter/enqueue!:

(defn failing-resolver
  [ctx args val]
  (with-superlifer ctx
    (enqueue! (Fail. (rand-int 100)))

Then with-superlifter will produce a lacinia "resolver result" promise that is never delivered. This request will now hang.


I think the problem here is how enqueue works. Enqueue takes your (urania) AST, and appends a then step:

delivering-muse (u/map (fn [result]
(prom/resolve! p result)
result)
muse)]

Now, this is not the place that the urania tree is executed, that happens in update-buckets!, via (urania) u/execute!:

(-> (u/execute! (u/collect muses)
(merge (:urania-opts new)
(when cache
{:cache (->urania cache)})))

Urania, however, will short circuit asts that contain failing datasources. The all step here is rejected:

https://github.com/funcool/urania/blob/3d3c61206d8a7de675af5d97ad08706ad3307a15/src/urania/core.cljc#L236

Therefore, this then step is skipped (the promise is rejected):

https://github.com/funcool/urania/blob/3d3c61206d8a7de675af5d97ad08706ad3307a15/src/urania/core.cljc#L237-L241

And therefore the superlifter amended then is never interpreted. Urania essentially short circuits and superlifter can never deliver the promise created in enqueue! because of that.


So; we tried something, but I'm not necessarily sure whether that is something that we should do in superlifter like this. The rejection of the datasource does bubble up to u/execute!. So in update-buckets! we can know what calls to enqueue! resulted in a rejection.

So in this tree, I've changed the queue shape to have both a muse, and the (enqueue!) promise. So that if u/execute! fails, I can know what enqueue promises it needs to reject.

Thoughts?

@oliyh
Copy link
Owner

oliyh commented Jan 28, 2022

Hello,

Superlifter should not hang if the execution is rejected so I'm in favour of this behaviour. I need to spend a bit more time reading your implementation though.

Thanks

@lennartbuit
Copy link
Author

lennartbuit commented Jan 28, 2022

Thats alright, we have workarounds in place.

I'm not fully convinced of my own implementation. I altered the shape of the queue inside superlifter (from list of muses to list of maps of muses and promises) and I'm not sure whether that is breaking. So by all means, challenge my implementation ^^.

When we agree on approach, I can add a bit of coverage, touch it up and PR, thats alright with me. Just let me know :).

@lennartbuit
Copy link
Author

So; talking to @oliyh on slack we came to the following conclusion:

Altering the shape of the queue can be breaking, but we are okay with that. We will make the queue a list of records (instead of a list of muses) so that we can make better guarantees about its shape in the future.

If you ended up extending start-trigger! and you don't regard the queue as an opaque list, we may break your custom trigger fn. If you only slice the list, or do any list operations without relying on it being a list of muses, you'll be fine.


I can pick this up in a few days time, I'll continue the shared tree, add some tests and such. You know, the usual.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants