-
Notifications
You must be signed in to change notification settings - Fork 11
Make shape requests interruptible. #65
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
Conversation
magnetised
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the expected client behaviour if it receives a 200 response when the shape has been invalidated? With the current version it would just retry on the same handle but get a different response since the underlying query had changed. Think that's just weird.
|
|
||
| defp fetch_upstream(sync_client, conn, request) do | ||
| # Skip interruptibility for initial requests. | ||
| defp fetch_upstream(%{shape_definition: nil, client: client}, conn, request) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not the "initial request" version -- that would be when handle is nil. this is a generic version where the shape is not predefined in the server code, so the version that allows for any shape defined by the request params.
What you really want is to ignore requests where live=false as they're never going to be long polling anyway. Is it worth interrupting anything other than a long poll?
| cursor = | ||
| case Map.get(params, "cursor", nil) do | ||
| nil -> nil | ||
| val -> String.to_integer(val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why convert to an integer here just to put it into the request params where it'll be cast to a string anyway
| key = Base.encode64(:crypto.strong_rand_bytes(18)) | ||
|
|
||
| ShapeRequestRegistry.register_shape(key, shape) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| key = Base.encode64(:crypto.strong_rand_bytes(18)) | |
| ShapeRequestRegistry.register_shape(key, shape) | |
| key = ShapeRequestRegistry.register_shape(key, shape) |
| end | ||
|
|
||
| %Client.Fetch.Response{ | ||
| status: 200, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be a 409 with a must-refetch message?
|
|
||
| {:DOWN, ^ref, :process, _pid, :normal} -> | ||
| # Task was successful, return the result. | ||
| Task.await(task, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no point using Task.async for this - you just want a linked process which you then wait on a message from (or an interrupt message). You're basically wanting an interruptible Task.await/1 but that doesn't exist and at the point where the task has finished why do Task.await to get the value rather than just send a valid response from the task process.
parent = self()
{:ok, pid} = Task.start_link(fn ->
send(parent, {:response, Client.Fetch.request(client, request)})
end)
# etc| GenServer.start_link(__MODULE__, opts, name: __MODULE__) | ||
| end | ||
|
|
||
| def register_shape(key, %ShapeDefinition{} = shape_definition) when is_binary(key) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to support anything other than a predefined shape here -- my poor naming choices in the client suggest otherwise but ignore that
|
Notes on this following chat with @magnetised:
Plus an optimisation might be to :
This:
Which seems like the optimal approach, assuming it is actually possible. |
out of the adapter/api impl
make interruptability an option add docs move api to Controller
so the request can be re-tried without a re-request from the client
978c9c2 to
614aa22
Compare
thruflo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great 👍 added a few comments, mainly about docs.
When building shape params on the server, for example using `sync_render`, it's useful to be able to respond to database changes that would cause the shape params to be different. For example, `/sync/projects` -> looks up posts for user, syncs `where id IN $userProjectIds`. Then the user joins another thread. Now, ... at the moment, this can wait until 20s for a long poll to return before the /sync/projects controller code runs to reconstruct the new shape params with the new where clause. What this PR does is provide a `Phoenix.Sync.interrupt(Projects.Project)` function that other backend code can call in order to interrupt current long polling requests. This is similar to using the abort controller with the javascript client. --------- Co-authored-by: Garry Hill <garry@magnetised.net>
When building shape params on the server, for example using
sync_render, it's useful to be able to respond to database changes that would cause the shape params to be different.For example,
/sync/projects-> looks up posts for user, syncswhere id IN $userProjectIds. Then the user joins another thread. Now, ... at the moment, this can wait until 20s for a long poll to return before the /sync/projects controller code runs to reconstruct the new shape params with the new where clause.What this PR does is provide a
Phoenix.Sync.interrupt(Projects.Project)function that other backend code can call in order to interrupt current long polling requests. This is similar to using the abort controller with the javascript client.