Make setting state idempotent (#2512)
* Make Setting state twice is idempotent pass * Add passing tests * PR comment & comments
This commit is contained in:
parent
f41931b566
commit
3e9c734da5
|
@ -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{},
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue