diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 27174dd98..4262b18ac 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -20,8 +20,6 @@ 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" "github.com/matrix-org/gomatrixserverlib/fclient" "github.com/matrix-org/util" @@ -30,6 +28,9 @@ import ( "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" "github.com/matrix-org/dendrite/clientapi/auth" @@ -86,6 +87,12 @@ 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 != "" { @@ -268,11 +275,14 @@ func Setup( if err != nil { return util.ErrorResponse(err) } + // 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 }) + // drop the result from the cache, so subsequent requests go through as normal. sf.Forget(vars["roomIDOrAlias"] + device.UserID) return resp.(util.JSONResponse) }, httputil.WithAllowGuests()), @@ -308,11 +318,14 @@ func Setup( if err != nil { return util.ErrorResponse(err) } + // 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 }) + // drop the result from the cache, so subsequent requests go through as normal. sf.Forget(vars["roomID"] + device.UserID) return resp.(util.JSONResponse) }, httputil.WithAllowGuests()), diff --git a/go.mod b/go.mod index 0fac0b999..c3a3f846b 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( golang.org/x/crypto v0.8.0 golang.org/x/image v0.5.0 golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e + golang.org/x/sync v0.1.0 golang.org/x/term v0.7.0 gopkg.in/h2non/bimg.v1 v1.1.9 gopkg.in/yaml.v2 v2.4.0 @@ -124,7 +125,6 @@ require ( golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.9.0 // indirect golang.org/x/sys v0.7.0 // indirect - golang.org/x/sync v0.1.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.6.0 // indirect diff --git a/go.sum b/go.sum index ad3017f2f..e823cf271 100644 --- a/go.sum +++ b/go.sum @@ -605,6 +605,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=