Make setting state idempotent (#2512)

* Make Setting state twice is idempotent pass

* Add passing tests

* PR comment & comments
This commit is contained in:
Till 2022-06-03 06:43:51 +02:00 committed by GitHub
parent f41931b566
commit 3e9c734da5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 45 additions and 3 deletions

View file

@ -19,6 +19,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"sync" "sync"
"time" "time"
@ -96,14 +97,21 @@ func SendEvent(
mutex.(*sync.Mutex).Lock() mutex.(*sync.Mutex).Lock()
defer mutex.(*sync.Mutex).Unlock() defer mutex.(*sync.Mutex).Unlock()
startedGeneratingEvent := time.Now()
var r map[string]interface{} // must be a JSON object var r map[string]interface{} // must be a JSON object
resErr := httputil.UnmarshalJSONRequest(req, &r) resErr := httputil.UnmarshalJSONRequest(req, &r)
if resErr != nil { if resErr != nil {
return *resErr return *resErr
} }
if stateKey != nil {
// If the existing/new state content are equal, return the existing event_id, making the request idempotent.
if resp := stateEqual(req.Context(), rsAPI, eventType, *stateKey, roomID, r); resp != nil {
return *resp
}
}
startedGeneratingEvent := time.Now()
// If we're sending a membership update, make sure to strip the authorised // If we're sending a membership update, make sure to strip the authorised
// via key if it is present, otherwise other servers won't be able to auth // via key if it is present, otherwise other servers won't be able to auth
// the event if the room is set to the "restricted" join rule. // the event if the room is set to the "restricted" join rule.
@ -208,6 +216,37 @@ func SendEvent(
return res return res
} }
// stateEqual compares the new and the existing state event content. If they are equal, returns a *util.JSONResponse
// with the existing event_id, making this an idempotent request.
func stateEqual(ctx context.Context, rsAPI api.ClientRoomserverAPI, eventType, stateKey, roomID string, newContent map[string]interface{}) *util.JSONResponse {
stateRes := api.QueryCurrentStateResponse{}
tuple := gomatrixserverlib.StateKeyTuple{
EventType: eventType,
StateKey: stateKey,
}
err := rsAPI.QueryCurrentState(ctx, &api.QueryCurrentStateRequest{
RoomID: roomID,
StateTuples: []gomatrixserverlib.StateKeyTuple{tuple},
}, &stateRes)
if err != nil {
return nil
}
if existingEvent, ok := stateRes.StateEvents[tuple]; ok {
var existingContent map[string]interface{}
if err = json.Unmarshal(existingEvent.Content(), &existingContent); err != nil {
return nil
}
if reflect.DeepEqual(existingContent, newContent) {
return &util.JSONResponse{
Code: http.StatusOK,
JSON: sendEventResponse{existingEvent.EventID()},
}
}
}
return nil
}
func generateSendEvent( func generateSendEvent(
ctx context.Context, ctx context.Context,
r map[string]interface{}, r map[string]interface{},

View file

@ -716,3 +716,6 @@ PUT /rooms/:room_id/redact/:event_id/:txn_id is idempotent
Unnamed room comes with a name summary Unnamed room comes with a name summary
Named room comes with just joined member count summary Named room comes with just joined member count summary
Room summary only has 5 heroes Room summary only has 5 heroes
Setting state twice is idempotent
Joining room twice is idempotent
Inbound federation can return missing events for shared visibility