aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTill <2353100+S7evinK@users.noreply.github.com>2022-06-03 06:43:51 +0200
committerGitHub <noreply@github.com>2022-06-03 06:43:51 +0200
commit3e9c734da5151aac6b7073c0797d26cde529ced7 (patch)
tree67c5b204e3a996fc9e359a5bde1070dcc82cccb9
parentf41931b56692c553ddf625ad54ffc8916b52e621 (diff)
Make setting state idempotent (#2512)
* Make Setting state twice is idempotent pass * Add passing tests * PR comment & comments
-rw-r--r--clientapi/routing/sendevent.go43
-rw-r--r--sytest-whitelist5
2 files changed, 45 insertions, 3 deletions
diff --git a/clientapi/routing/sendevent.go b/clientapi/routing/sendevent.go
index 70bf72f8..2e864ade 100644
--- a/clientapi/routing/sendevent.go
+++ b/clientapi/routing/sendevent.go
@@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"net/http"
+ "reflect"
"sync"
"time"
@@ -96,14 +97,21 @@ func SendEvent(
mutex.(*sync.Mutex).Lock()
defer mutex.(*sync.Mutex).Unlock()
- startedGeneratingEvent := time.Now()
-
var r map[string]interface{} // must be a JSON object
resErr := httputil.UnmarshalJSONRequest(req, &r)
if resErr != nil {
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
// 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.
@@ -208,6 +216,37 @@ func SendEvent(
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(
ctx context.Context,
r map[string]interface{},
diff --git a/sytest-whitelist b/sytest-whitelist
index 6af8d89f..5f6797a3 100644
--- a/sytest-whitelist
+++ b/sytest-whitelist
@@ -715,4 +715,7 @@ Presence can be set from sync
PUT /rooms/:room_id/redact/:event_id/:txn_id is idempotent
Unnamed room comes with a name summary
Named room comes with just joined member count summary
-Room summary only has 5 heroes \ No newline at end of file
+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 \ No newline at end of file