Fix potential state reset when trying to join a room (#3040)

When trying to join a room in short sequence, it is possible that a
state reset occurs. This fixes it by using `singleflight`.
This commit is contained in:
Till 2023-05-30 15:27:11 +02:00 committed by GitHub
parent f956a8c1d9
commit 3dcca4017c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 8 deletions

View file

@ -20,14 +20,16 @@ import (
"strings"
"github.com/gorilla/mux"
"github.com/matrix-org/dendrite/setup/base"
userapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
"github.com/nats-io/nats.go"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"golang.org/x/sync/singleflight"
"github.com/matrix-org/dendrite/setup/base"
userapi "github.com/matrix-org/dendrite/userapi/api"
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
"github.com/matrix-org/dendrite/clientapi/api"
@ -84,6 +86,14 @@ func Setup(
unstableFeatures["org.matrix."+msc] = true
}
// singleflight protects /join endpoints from being invoked
// multiple times from the same user and room, otherwise
// a state reset can occur. This also avoids unneeded
// state calculations.
// TODO: actually fix this in the roomserver, as there are
// possibly other ways that can result in a stat reset.
sf := singleflight.Group{}
if cfg.Matrix.WellKnownClientName != "" {
logrus.Infof("Setting m.homeserver base_url as %s at /.well-known/matrix/client", cfg.Matrix.WellKnownClientName)
wkMux.Handle("/client", httputil.MakeExternalAPI("wellknown", func(r *http.Request) util.JSONResponse {
@ -264,9 +274,17 @@ func Setup(
if err != nil {
return util.ErrorResponse(err)
}
return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
)
// Only execute a join for roomIDOrAlias and UserID once. If there is a join in progress
// it waits for it to complete and returns that result for subsequent requests.
resp, _, _ := sf.Do(vars["roomIDOrAlias"]+device.UserID, func() (any, error) {
return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomIDOrAlias"],
), nil
})
// once all joins are processed, drop them from the cache. Further requests
// will be processed as usual.
sf.Forget(vars["roomIDOrAlias"] + device.UserID)
return resp.(util.JSONResponse)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
@ -300,9 +318,17 @@ func Setup(
if err != nil {
return util.ErrorResponse(err)
}
return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomID"],
)
// Only execute a join for roomID and UserID once. If there is a join in progress
// it waits for it to complete and returns that result for subsequent requests.
resp, _, _ := sf.Do(vars["roomID"]+device.UserID, func() (any, error) {
return JoinRoomByIDOrAlias(
req, device, rsAPI, userAPI, vars["roomID"],
), nil
})
// once all joins are processed, drop them from the cache. Further requests
// will be processed as usual.
sf.Forget(vars["roomID"] + device.UserID)
return resp.(util.JSONResponse)
}, httputil.WithAllowGuests()),
).Methods(http.MethodPost, http.MethodOptions)
v3mux.Handle("/rooms/{roomID}/leave",