Conversation
mikolajpp
left a comment
There was a problem hiding this comment.
Looking very nice so far. Some nits in the comments.
desk/sur/groups.hoon
Outdated
| :: $incoming-request: a request coming into the agent | ||
| :: | ||
| :: .id: request id | ||
| :: .http-id: http request id, if any. means we should give a response |
There was a problem hiding this comment.
This comment line is a bit confusing: what means we should give a response? If request-id is present?
| =/ =flag:g | ||
| ?- -.c-groups | ||
| %group flag.c-groups | ||
| %ask flag.c-groups | ||
| %join flag.c-groups | ||
| %leave flag.c-groups |
There was a problem hiding this comment.
It is wild we have to do this in this way. I think if %create was not there, flag it could still be referenced without dispatching on the type...
| %not-authorized | ||
| ~['Cannot ban or unban an admin ship'] |
There was a problem hiding this comment.
The use of %not-authorized here is confusing (we use it both for authorized ships who try to access privileged commands, and for an unauthorized ship trying to join the group). I think we should make a semantic distinction and introduce something like HTTP 400 Bad Request, here and elesewhere.
desk/app/groups.hoon
Outdated
| :: this is always a request from ourselves, never others | ||
| :: |
There was a problem hiding this comment.
This comment is not clear to me: what does it refer to? Since we are in +go-core, everything we receive are responses to our own requests.
desk/app/groups.hoon
Outdated
| =. requests | ||
| (~(put by requests) id u.request(result `body.response)) | ||
| :: this should only ever be from ourselves | ||
| =/ =path :(weld /v1 go-area /request/(scot %uv id)) |
There was a problem hiding this comment.
This can be a +weld less.
| =/ =path :(weld /v1 go-area /request/(scot %uv id)) | |
| =/ =path (weld [~.v1 go-area] /request/(scot %uv id)) |
|
Action and command obviously overlay cleanly onto our existing "seven things" model, but response conflicts. We can understand the addition of venter to move us to a "nine things" model, right? Would be good to explicate in the comment at the top of the sur file. Something like the following. (Forgive the name suggestions, I can't help myself, but we should probably resolve the conflict somehow to aid conversation.) :: --action--> --command-->
:: <··status·· <··result··
:: client subscriber publisher
:: <--response-- <--update-- |
|
@Fang- I think response was probably the wrong term to use back in the 7 things model, since most of the time its not directly responses from things we've done but sometimes what others have done. maybe event would have been better? idk |
|
maybe revision would fit? |
|
On the other hand, I don't think response was ever being predicated on being generated in response to our action? The explainer in |
Fang-
left a comment
There was a problem hiding this comment.
Mostly looked at server side so far, haven't done a full pass over the client agents yet. Some comments/questions below.
| (my %groups^[~.groups^%3 ~ ~] ~) | ||
| %- agent:dbug | ||
| %^ verb | %warn | ||
| %^ verb & %dbug |
There was a problem hiding this comment.
Probably shouldn't be committing this amount of verbosity. (;
| ++ ca-init-req | ||
| |= req=request-id:v10:cv | ||
| ^+ ca-core | ||
| ca-core(request-id req) | ||
| :: | ||
| ++ ca-give-response-update | ||
| |= body=response-update-body:v10:cv | ||
| ^+ ca-core | ||
| =/ =response-update:v10:cv [request-id body] | ||
| =/ =path /request/(scot %p src.bowl)/(scot %uv request-id) | ||
| =. ca-core | ||
| (emit (tell:log %dbug ~['giving response update for request ' >request-id< >response-update<] ~)) | ||
| (give %fact ~[path] channel-response-update+!>(response-update)) |
There was a problem hiding this comment.
Kinda spooky how, if we forget to +ca-init-req, the request-id can just be the bunt and we wouldn't know. Certainly weird to give out a fact in that case, don't you think?
Of course there's limited entry points that want to set it, but doesn't that suggest we want those (just +ca-create and +ca-c-channel?) to take a request-id argument, instead of having a separate arm for setting it?
There was a problem hiding this comment.
Also, the assumption with putting src.bowl in the path here instead of remembering the source that originated the request in state, is that presently all requests get handled synchronously, right? Probably want to ::NOTE that assumption here.
But if they get handled synchronously, why do we need to store them in requests? (During %channel-command and +watch handling.)
There was a problem hiding this comment.
Also also, we never write into requests beyond the %sending state we gut:by during poke handling. Don't we want to update the request state here to contain the body?
| [%request ship=@ id=@ ~] | ||
| :: handle request response subscription | ||
| =/ =ship (slav %p ship.pole) | ||
| ?> =(ship src.bowl) | ||
| =/ id=request-id:v10:cv (slav %uv id.pole) | ||
| =/ request=incoming-request:v10:cv | ||
| (~(gut by requests) id [id ~ %sending ~ ~ |]) | ||
| =. requests | ||
| (~(put by requests) id request) | ||
| cor |
There was a problem hiding this comment.
The handling of requests here is identical to the %channel-command poke case. That doesn't seem right. I don't think we should invent a request based on a subscription for it coming in. Additionally, we probably want to send an initial fact if it did already exist.
There was a problem hiding this comment.
Noting that this is also the case in groups.
| $: %14 | ||
| =v-channels:v9:cv | ||
| =hooks:h | ||
| =requests:v10:cv |
There was a problem hiding this comment.
Presently we never clean these up/out, but we should periodically check all entries' final-at for that, right?
Right, that's one of the remaining tasks you noted:
request cleanup in channels-server
| =/ =incoming-request:v10:cv | ||
| (~(gut by requests) request-id [request-id ~ %sending ~ ~ |]) | ||
| =. requests | ||
| (~(put by requests) request-id incoming-request) |
There was a problem hiding this comment.
Given that a %channel-command is always a new request, not clear to me why we gut:by here. We'd want to just overwrite whatever's there, right?
| [%error type=action-error message=tang] | ||
| == | ||
| :: | ||
| +$ requests (map request-id incoming-request) |
There was a problem hiding this comment.
There's a mismatch here, where the subscription path is per source ship per request-id, but we only store them by request-id. Doesn't this let people clobber each other's request statuses?
| [group.new now.bowl %channel nest %add channel] | ||
| [now.bowl %default readers.new |] | ||
| =/ =action:v10:gv | ||
| [(end [3 4] eny.bowl) %group group.new %channel nest %add channel] |
There was a problem hiding this comment.
If we don't care to remember the requests, shouldn't we just keep sending plain unidentified actions? Alternatively, just pass *@uv or something similar that makes it obvious we don't care and won't use it.
| %invalid-name | ||
| %request-too-large | ||
| %unknown | ||
| == |
There was a problem hiding this comment.
Since these are basically identical between the agents, and we expect them to remain that way, could be worth factoring out into a /sur/vent or what have you. The result=(unit response-body) will be different, but easy to parameterize with a |$ type constructor.
| :: successful: discard after 5 minutes | ||
| ?: |(?=([~ %ok *] result.req) ?=([~ %no-change *] result.req)) |
There was a problem hiding this comment.
Why the reduced expiration time for successful requests? Is the assumption that in the success case, the result was near-instant and/or the delivery of the result most likely?
mikolajpp
left a comment
There was a problem hiding this comment.
Reviewed the groups agent. This line of work looks quite promising! Left quite some comments, most of them minor.
| # read from file, escape 's, replace newlines w/ gaps | ||
| # Use paste for portable line joining (works on both GNU and BSD systems) | ||
| HOON="$(cat $INPUT | sed "s;';\\\\';g" | paste -sd' ' -)" | ||
| HOON="$(cat $INPUT | sed "s;';\\\\';g" | perl -pe 's/\n/ /g')" |
There was a problem hiding this comment.
I am curious to know why this is better?
| =/ m (strand ,vase) | ||
| ;< our=ship bind:m get-our | ||
| ;< ~ bind:m (poke [our %hood] kiln-commit+!>([%groups |])) | ||
| (pure:m !>(%ok)) |
There was a problem hiding this comment.
| (pure:m !>(%ok)) | |
| (pure:m !>(&)) |
Noting in passing (also for myself, to fix this in the backend test runner) that this makes for a weird terminal output from click: it will show the numeric value of %ok, rather than the term itself.
| :: | ||
| :: versions | ||
| :: | ||
| ++ v10 |
There was a problem hiding this comment.
We must be careful here when merging to bump the version number.
Edit: this is because subscription lifecycle PR, which likely will go in before this PR, is currently at v11. v10 was already merged with some other PR.
| ++ v10 | ||
| =, v9 | ||
| |% | ||
| ++ response |
There was a problem hiding this comment.
| ++ response | |
| ++ response |
| %ok (r-group r-group.body) | ||
| %error (error +.body) | ||
| %pending s+status.body |
There was a problem hiding this comment.
| %ok (r-group r-group.body) | |
| %error (error +.body) | |
| %pending s+status.body | |
| %ok (r-group r-group.body) | |
| %error (error +.body) | |
| %pending s+status.body |
| [%pass wire %agent dock %poke group-command+!>(`c-groups:g`[%leave flag])] | ||
| =/ req-id (end [3 8] eny.bowl) | ||
| [%pass wire %agent dock %poke group-command+!>(`command:g`[req-id %leave flag])] |
There was a problem hiding this comment.
This is different from the logic introduced in +go-send-command. Should +go-send-command be used here? Though I guess after issuing +leave-group we are usually going to delete the group altogether, so perhaps this is fine.
| =/ request (~(get by requests) id) | ||
| =. cor (tell:l %dbug ~['receiving cmd poke ack' >id< >cmd.i.t.t.wire<]) | ||
| ?~ p.sign | ||
| ?~ request go-core |
There was a problem hiding this comment.
Can we not have a request entry for an issued command with venter? If this is unexpected we should log at least a warning or even an error here.
| =? cor ?=(^ http-id.u.request) | ||
| (give-http-response u.http-id.u.request response) | ||
| :: mark as fetched after delivering final error response to client | ||
| =/ new-req (~(got by requests) id) |
There was a problem hiding this comment.
We have just put a modified request into requests on L3986. We can just preserve it for use here.
| %kick | ||
| =/ id=request-id:v10:gv (slav %uv i.t.wire) | ||
| =/ =^wire (weld go-area /request/(scot %uv id)) | ||
| =/ =path (weld go-server-path /request/(scot %uv id)) | ||
| =/ =dock [p.flag server] | ||
| =. cor ((safe-watch wire dock path) &) |
There was a problem hiding this comment.
Will the server not kick us after fulfilling request? If so, we should probably condition the resubscription on the request status.
| ~> %spin.['join'] | ||
| ^- card | ||
| =/ =wire (weld fi-area /join/[?~(tok %public (scot %uv u.tok))]) | ||
| =/ req-id (end [3 8] eny.bowl) |
There was a problem hiding this comment.
Using entropy for id means that we could overwrite requests if we poke more than once in an event. I am not sure if this would be a problem for internally issued actions, so perhaps this is fine?
Or is entropy different on each agent activation. A cursory look at gall does not make me think this is the case. CC @Fang-
There was a problem hiding this comment.
Each agent activation has a fresh eny, yes.
It's generally considered good practice to hash or otherwise tweak the entropy, rather than taking raw bytes from eny directly. Here, for example, you might consider using the +mug of the action as a salt, to avoid the case you describe, a la (end 3^8 (shas (mug action) eny.bowl)).
mikolajpp
left a comment
There was a problem hiding this comment.
Some more comments. On the way to finish the review: only channels and channels-server remain.
|
|
||
| 1. Copy the changed files to the pier: `rsync -avL desk/* <path-to-pier>/groups/` | ||
| 2. Run `backend/commit-groups.sh` passing the pier path as an argument to compile the synced changes | ||
| 3. Wait and check for changes to the logs, sometimes commits can take upwards of 30s to 2 mins. |
There was a problem hiding this comment.
The drag on productivity for agents due to unbearably slow compilation is going to be even worse that it is for us. (That is, unless they can one-shot Hoon) Is it usable at this point for any non-trivial changes?
| :: | ||
| [%server %groups ship=@ name=@ %request rest=*] | ||
| =. cor (tell:l %dbug ~['received watch request' >pole<]) | ||
| =+ ship=(slav %p ship.pole) |
There was a problem hiding this comment.
A check is missing here.
| =+ ship=(slav %p ship.pole) | |
| =+ ship=(slav %p ship.pole) | |
| ?> =(our.bowl ship) |
| :: $r-channels: channel responses | ||
| :: | ||
| :: responses are generated in response to a channel change, and are | ||
| :: disseminated to clients we ourselves are using. |
There was a problem hiding this comment.
| :: disseminated to clients we ourselves are using. | |
| :: disseminated to client subscribers. |
| :: change | ||
| :: $a-channels: channels actions | ||
| :: | ||
| :: actions are requests to the agent as a channel client. most actions |
There was a problem hiding this comment.
This is confusing: there are in fact two different agents, unlike with groups.
| :: actions are requests to the agent as a channel client. most actions | |
| :: actions are requests to the %channels agent. most actions |
| :: commands are requests to the agent as a channel host. they are checked | ||
| :: for permissions. |
There was a problem hiding this comment.
| :: commands are requests to the agent as a channel host. they are checked | |
| :: for permissions. | |
| :: commands are requests to the %channels-server agent. they are checked | |
| :: for permissions. |
| :: | ||
| :: some actions happen to be the same as commands, but this can freely | ||
| :: change | ||
| :: $a-channels: channels actions |
There was a problem hiding this comment.
| :: $a-channels: channels actions | |
| :: $a-channels: channel actions |
| [%request ship=@ id=@ ~] | ||
| :: handle request response subscription | ||
| =/ =ship (slav %p ship.pole) | ||
| ?> =(ship src.bowl) | ||
| =/ id=request-id:v10:cv (slav %uv id.pole) | ||
| =/ request=incoming-request:v10:cv | ||
| (~(gut by requests) id [id ~ %sending ~ ~ |]) | ||
| =. requests | ||
| (~(put by requests) id request) | ||
| cor |
There was a problem hiding this comment.
Noting that this is also the case in groups.
| |$ [data] | ||
| [rev=@ud data] | ||
| :: | ||
| ++ v10 |
There was a problem hiding this comment.
With recent type versions in groups I used a convention where: (1) we document the reason for type version change, such us "X depends on Y", or "X modified ...". (2) for new types we include the original comment.
See /sur/groups-ver.hoon for examples.
This creates a paper-trail accesible right in the code, and is really helpful for understanding changes between versions.
| /- cv=channels-ver | ||
| /+ j=channel-json | ||
| |_ =c-channels:c | ||
| |_ =command:v10:cv |
There was a problem hiding this comment.
Since we shield this type with lib-negotiate, it seems like this should not be versioned? Types that are in any way tied up with the client interface should not leak beyond the channels agent.
(Also see similar comments in groups)
| :: should we return bunts here or crash? | ||
| =/ =reply-meta:v9:cv | ||
| ?~ post=(~(get by v-posts) id.update) *reply-meta:v9:cv |
There was a problem hiding this comment.
Given that venter is supposed to give us an ability to return response errors back to the requester, we should probably crash, if that would be returned to the requester as a response error.
mikolajpp
left a comment
There was a problem hiding this comment.
Another batch of comments: reviewed %channels.
| ?> from-self | ||
| cor | ||
| ?: ?=(%toggle-post -.a-channels) | ||
| ?> from-self |
There was a problem hiding this comment.
This change is probably a mistake, it would allow remote ships to hide our own posts.
| :: TODO: add transfer/import channels | ||
| %channel-action-1 | ||
| =+ !<(=a-channels:v9:cv vase) | ||
| $(+< channel-action-2+!>(`action:v10:cv`[(end [3 4] eny.bowl) a-channels])) |
There was a problem hiding this comment.
This is at odds with behavior in groups: there we use 0v0 as the id, which in particular means repeated requests will overwrite.
| =/ new-req (~(got by requests) request-id) | ||
| =. requests | ||
| (~(put by requests) request-id new-req(fetched &)) |
There was a problem hiding this comment.
We don't mutate request in scope here, so we should either define an alias or simply use it as is.
| =/ new-req (~(got by requests) request-id) | |
| =. requests | |
| (~(put by requests) request-id new-req(fetched &)) | |
| =. requests | |
| (~(put by requests) request-id u.request(fetched &)) |
| [=kind:c ship=@ name=@ %request id=@ ~] | ||
| ?> ?=([%behn %wake ~] sign) |
There was a problem hiding this comment.
I understand we only ever receive timeouts on this wire, but I think it could be worth listening on /request/$id/timeout wire for legibility and to make any future changes easier.
| :: so that we have negotiated a matching version. If we want to do | ||
| :: anything in particular on a mismatched version, we can call | ||
| :: +can-poke:neg. | ||
| :: if we have a request-id, subscribe to responses and wrap command |
There was a problem hiding this comment.
This says "if we have a request-id", but there is no conditional. Is there something missing here?
| =/ request=incoming-request:v10:cv | ||
| (~(gut by requests) req-id [req-id ~ %nacked ~ ~ |]) | ||
| =. result.request | ||
| `[%error %unknown u.p.sign] |
There was a problem hiding this comment.
We don't set the status as %nacked unless we don't actually have the request. Is this correct? (We do the same in groups)
| =. final-at.request `now.bowl | ||
| =. requests | ||
| (~(put by requests) req-id request) | ||
| :: the behn timer will still fire for the timeout, but it should be fine |
There was a problem hiding this comment.
Saying "(...) for the request timout" would make it clearer. Also should be fine feels like we are not sure.
It would be better if it says something like "but we safely ignore it", and make sure that is true.
| =/ posts=v-posts:v9:cv | ||
| ?. ?=(%ok -.body.response-update) | ||
| posts.channel | ||
| posts.channel:(ca-u-channels update.body.response-update) |
There was a problem hiding this comment.
Interestingly, we preemptively update our local state without waiting for the fact. Does this mean each time a subscriber sends an action he will apply the same update twice, first from the response, second from the channel update fact?
I think we are guaranteed that these two updates will arrive one after another, without any intervening facts in between?
CC @Fang-
There was a problem hiding this comment.
I don't think we have any hard ordering guarantees here, the facts will travel on two separate ames flows. (Because they happen on two different subscriptions with two different wires.)
So, probably theoretically possible to hear other stuff in between the two. (But, you obviously won't hear two facts from the same subscription out of order.)
There was a problem hiding this comment.
Then the following scenario is possible:
If Alice's action A caused update fact B (with update B* arriving as request response), and a subsequent Kevin's action K caused fact update L, it is possible that Alice is going to hear the facts in either of the orders:
- B* < B < L
- B < L < B*
While we are guaranteed that B < L, because they arrive on the same flow, in case (2) we apply L followed by B* out of order, which can cause problems. While it is possible that our update operation is idempotent (though this needs a careful check – it is not a given. creating a resource twice, for instance, could cause crashes), they are definitely not commutative.
For instance, in channels, if B = create a post, and L = delete a post, in scenario (2) Alice ends up recreating the post wrongly in her local state. In groups, on the other hand, (2) will cause a resync, which triggers on updates arriving out of order.
These problems could be avoided in groups by checking timestamps in those two cases to avoid applying duplicated update fact B in (1), and in (2) to drop the out-of-order update B*. It seems like channels receive updates out of order by design, so this solution might not apply straight away.
| result `body.response | ||
| final-at ?.(is-final ~ `now.bowl) | ||
| == | ||
| :: if this is a successful create response, set up the channel |
There was a problem hiding this comment.
I am not quite sure what are we doing here: I think the comment should be attached to the branch where we do handle the creation, because here we actually handle a few different things.
| == | ||
| :: if this is a successful create response, set up the channel | ||
| =* body body.response-update | ||
| =^ meta ca-core |
There was a problem hiding this comment.
I think handling creation in a separate arm, like we used to, would prove to be less confusing. Are there good reasons why we decided to unify those two arms?
mikolajpp
left a comment
There was a problem hiding this comment.
Last batch of comments regarding channels-server!
Found a few things which looked like could be an issue, other than that mostly nits.
| =/ =incoming-request:v10:gv | ||
| (~(gut by requests) request-id [request-id ~ %sending ~ ~ |]) |
There was a problem hiding this comment.
But this still leaves the question whether we should not perhaps just overwrite it, as @Fang- noted in channels-server.
| ~& "%channel-server: revoke perms for {<affected>}" | ||
| =. cor (emit (tell:log %dbug ~['%channel-server: revoke perms for' >affected<] ~)) |
| :: if we have no update, ca-core should already have cards | ||
| :: for an errored response | ||
| ?~ update | ||
| ca-core |
There was a problem hiding this comment.
It is hard to understand what this comment refers to without some more context. Are we saying a possible error was already generated somewhere else, for example in +ca-c-post?
| :: if a command leaves the data unchanged we still want to give a | ||
| :: response so that the client knows the operation completed |
There was a problem hiding this comment.
Ideally this comment should be attached to the place it refers to: it is enough to attach it to the first conditional below.
| |_ $: =nest:c | ||
| channel=v-channel:c | ||
| gone=_| | ||
| =request-id:v10:cv |
There was a problem hiding this comment.
The ordering of arguments here is different from the one in groups. We should choose one and stick to it. (I prefer this ordering to the one from groups, as it groups channel-related data together)
| ?. |(=(src.bowl author.u.post) (is-admin:ca-perms src.bowl)) | ||
| :- ~ | ||
| %- ca-give-response-update | ||
| :* %error | ||
| %not-authorized | ||
| ~['Cannot edit post from other authors unless admin'] | ||
| == |
There was a problem hiding this comment.
Isn't this the same error as on L1155 above? We should either combine these two conditionals into one, or unify (alternatively, meaningfully distinguish) the error messages
There was a problem hiding this comment.
Before this change there was only one assertion (corresponding to L1169 after change), now there are two. Are we fixing some bug at the same time?
| ::TODO send response based on hook outcome | ||
| ((slog p.result) [[~ replies] ca-core]) |
There was a problem hiding this comment.
Is there any reason we +slog here? Given the logging facilities we have now, I think we should stop using +slog on principle and prefer to log with appropriate priority.
| ?. |(=(src.bowl author.u.reply) (is-admin:ca-perms src.bowl)) | ||
| :- [~ replies] | ||
| %- ca-give-response-update | ||
| :* %error | ||
| %not-authorized | ||
| ~['Cannot modify reply from other authors unless admin'] | ||
| == |
There was a problem hiding this comment.
I recall that above in +ca-c-post arm there were two conditionals rather than one, one comparing with the author of the existing reply, another with the author of the reply command. Does not this mean here we can modify our own reply to appear as originating from another user?
| (run-hooks event nest 'edit blocked') | ||
| ?: ?=(%.n -.result) | ||
| ((slog p.result) [~ replies]) | ||
| ((slog p.result) [[~ replies] ca-core]) |
There was a problem hiding this comment.
Let's log rather than slog :-)
| ::TODO send response based on hook outcome | ||
| ((slog p.result) [[~ replies] ca-core]) |
There was a problem hiding this comment.
Slog we should not, let's log! 🎵
Summary
Addresses TLON-4432. There's lots to be said here.
This does not create a library to facilitate these things yet. Instead we've manually added handling for request/response to both %groups and %channels. This does not implement venter in the way laid out by by ~niblyx-malnus here: https://github.com/niblyx-malnus/venter-pattern but it does use the basic principles.
The major thing still in progress is writing backend tests. May want to use new aqua testing not sure yet.
Finishing up:
some cleanup of commented out code in various placesrandom examples of showing errors, not sure we want this in all the places I put itcleanup of random logs both hoon and client sideThings left undone for follow-up PRs:
Changes
How did I test?
Manually through the UI trying different actions in various configurations between two ships.
Risks and impact
Rollback plan
Rollback would require protocol shenanigans and migrations
Screenshots / videos