From 8bfabf58ec713a52a2e0665836a6630a5d02191a Mon Sep 17 00:00:00 2001 From: diamondburned Date: Sat, 1 May 2021 21:41:39 -0700 Subject: [PATCH] Make SetterMethods ContainerUpdaterMethods This commit separated SetterMethods that are specifically for updating containers to another method type, named ContainerUpdaterMethod. This change is done to force a context parameter into container setters, allowing the frontend to know if an incoming update is valid or not, based on the state of the context given. The validity check should be the same as any other context: select { case <-ctx.Done(): return default: addEvent() } It is crucial, however, to do the checking and updating in the same thread or lock as the context is cancelled. This explicit synchronization is required to prevent any race condition whatsoever with cancellation of the context. The backend must pass in the right context, that is, any context that inherits the cancellation from the frontend. Passing in the invalid context is undefined behavior and will eventually cause a data race. --- cchat.go | 30 +++++++++--------- .../cchat-generator/generate_interface.go | 3 ++ repository/gob/repository.gob | Bin 38124 -> 38343 bytes repository/interface.go | 23 +++++++++++--- repository/main.go | 30 +++++++++--------- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/cchat.go b/cchat.go index f0c9ccc..789ecf9 100644 --- a/cchat.go +++ b/cchat.go @@ -355,7 +355,7 @@ type Identifier interface { // Labels given to the frontend may contain images or avatars, and the frontend // has the choice to display them or not. type LabelContainer interface { - SetLabel(text.Rich) + SetLabel(context.Context, text.Rich) } // ListMember represents a single member in the member list. Note that this @@ -452,7 +452,7 @@ type MemberDynamicSection interface { type MemberListContainer interface { // RemoveMember removes a member from a section. If neither the member nor the // section exists, then the client should ignore it. - RemoveMember(sectionID ID, memberID ID) + RemoveMember(ctx context.Context, sectionID ID, memberID ID) // SetMember adds or updates (or upsert) a member into a section. This operation // must not change the section's member count. As such, changes should be done // separately in SetSection. If the section does not exist, then the client @@ -462,12 +462,12 @@ type MemberListContainer interface { // Typically, the backend should try and avoid calling this method and instead // update the labeler in the name. This method should only be used for adding // members. - SetMember(sectionID ID, member ListMember) + SetMember(ctx context.Context, sectionID ID, member ListMember) // SetSections (re)sets the list of sections to be the given slice. Members from // the old section list should be transferred over to the new section entry if // the section name's content is the same. Old sections that don't appear in the // new slice should be removed. - SetSections(sections []MemberSection) + SetSections(ctx context.Context, sections []MemberSection) } // MemberLister adds a member list into a message server. @@ -537,12 +537,12 @@ type MessageUpdate interface { // allowed to have multiple views. This is usually done with tabs or splits, but // the backend should update them all nonetheless. type MessagesContainer interface { - DeleteMessage(MessageDelete) - UpdateMessage(MessageUpdate) + DeleteMessage(context.Context, MessageDelete) + UpdateMessage(context.Context, MessageUpdate) // CreateMessage inserts a message into the container. The frontend must // guarantee that the messages are in order based on what's returned from // Time(). - CreateMessage(MessageCreate) + CreateMessage(context.Context, MessageCreate) } // Messenger is for servers that contain messages. This is similar to Discord or @@ -618,10 +618,10 @@ type ReadContainer interface { // their read indicators. The backend can use this to free up users/authors that // are no longer in the server, for example when they are offline or have left // the server. - DeleteIndications(authorIDs []ID) + DeleteIndications(ctx context.Context, authorIDs []ID) // AddIndications adds a map of users/authors to the respective message ID of // the server that implements ReadIndicator. - AddIndications([]ReadIndication) + AddIndications(context.Context, []ReadIndication) } // ReadIndicator adds a read indicator API for frontends to show. An example of @@ -721,7 +721,7 @@ type ServerUpdate interface { // as servers can be infinitely nested. Frontends should also reset the entire // node and its children when SetServers is called again. type ServersContainer interface { - UpdateServer(ServerUpdate) + UpdateServer(context.Context, ServerUpdate) // SetServer is called by the backend service to request a reset of the server // list. The frontend can choose to call Servers() on each of the given servers, // or it can call that later. The backend should handle both cases. @@ -731,7 +731,7 @@ type ServersContainer interface { // should only be considered empty if it's an empty non-nil slice. An // unavailable list, on the other hand, can be treated as backend issues, e.g. a // connection issue. - SetServers([]Server) + SetServers(context.Context, []Server) } // Service is a complete service that's capable of multiple sessions. It has to @@ -835,11 +835,11 @@ type TypingContainer interface { // of typers. This function is usually not needed, as the client will take care // of removing them after TypingTimeout has been reached or other conditions // listed in ServerMessageTypingIndicator are met. - RemoveTyper(authorID ID) + RemoveTyper(ctx context.Context, authorID ID) // AddTyper appends the typer (author) into the frontend's list of typers, or it // pushes this typer on top of others. The frontend should assume current time // every time AddTyper is called. - AddTyper(User) + AddTyper(context.Context, User) } // TypingIndicator optionally extends ServerMessage to provide bidirectional @@ -885,7 +885,7 @@ type TypingIndicator interface { type UnreadContainer interface { // SetUnread sets the container's unread state to the given boolean. The // frontend may choose how to represent this. - SetUnread(unread bool, mentioned bool) + SetUnread(ctx context.Context, unread bool, mentioned bool) } // UnreadIndicator adds an unread state API for frontends to use. The unread @@ -908,7 +908,7 @@ type UnreadIndicator interface { // the frontend has no use in knowing the error. As such, marking messages as // read is best-effort. The backend is in charge of synchronizing the read state // with the server and coordinating it with reasonable rate limits, if needed. - MarkRead(messageID ID) + MarkRead(ctx context.Context, messageID ID) } // User is the interface for an identifiable author. The interface defines that diff --git a/cmd/internal/cchat-generator/generate_interface.go b/cmd/internal/cchat-generator/generate_interface.go index 553993a..8ff7795 100644 --- a/cmd/internal/cchat-generator/generate_interface.go +++ b/cmd/internal/cchat-generator/generate_interface.go @@ -67,6 +67,9 @@ func generateInterfaces(ifaces []repository.Interface) jen.Code { case repository.SetterMethod: stmt.Params(generateFuncParams(method.Parameters, "")...) stmt.Params(generateFuncParamsErr(repository.NamedType{}, method.ErrorType)...) + case repository.ContainerUpdaterMethod: + stmt.Params(generateFuncParamsCtx(method.Parameters, "")...) + stmt.Params(generateFuncParamsErr(repository.NamedType{}, method.ErrorType)...) case repository.IOMethod: stmt.Params(generateFuncParamsCtx(method.Parameters, "")...) stmt.Params(generateFuncParamsErr(method.ReturnValue, method.ErrorType)...) diff --git a/repository/gob/repository.gob b/repository/gob/repository.gob index 0eb4c60f207db91d837fe4be045899bff289f539..b2f390eadc1c1465cbb54542f5d8cb742701fc34 100644 GIT binary patch delta 572 zcmaE}lIi$rrVXZkY;x@X_sB6$HVhHn9N?EN%jhsU(M@aefjmx~(1Mi2lGGyK)RK(+ zl>d8~85zZ#^Ycm)GxJi5P~;f@@0%>>r^2Gy_J7~zFWHya5QfO3DEz;VkCBmyBRV!X zwWut$h=Boa89$o3&F;nLm=VUPph*h@jp6l8EiO(>PjxOz1v(SSEPRR~<{&A?u=qu} z5t91_(JVG#oP0srcydSGAB5x3jhMW*UL3dVX5I!LB%h&c-aM&!1tUVYG@AWfK##M8 i7UK`Q?X7}%Y?#d0serHm-Tui@otn61H}B~*;06E@wA|$Y delta 302 zcmX@Un(56-rVXZkY;Ifs?~!AiY#1WCIlwPlcCx95_T&$~yo`G`JLgi_TEhSxP z3sR8efR<%e7_m*BDh;#yUY#K$%x