mirror of
https://github.com/element-hq/synapse.git
synced 2026-10-23 13:03:28 +00:00
Merge branch 'develop' into quenting/read-receipts/wrong-attribution
This commit is contained in:
10 files changed
+680
-159
No files matched your search
@@ -28,7 +28,7 @@ jobs:
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
||||
with:
|
||||
push: true
|
||||
labels: |
|
||||
@@ -183,7 +183,7 @@ jobs:
|
||||
password: ${{ steps.import-secrets.outputs.OCI_PASSWORD }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
||||
|
||||
- name: Set up docker layer caching
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Bump `cryptography` from `46.0.7` to `50.0.1` to resolve `CVE-2026-69248`, `CVE-2026-69249` and `CVE-2026-69247`, none of which affected Synapse. Also bump `pyOpenSSL` from `26.0.0` to `26.4.0` for compatibility with newer `cryptography` versions.
|
||||
@@ -0,0 +1 @@
|
||||
Advertise support for Matrix v1.15.
|
||||
@@ -0,0 +1 @@
|
||||
Fix bug where membership events would not be returned in `/sync` when using lazy-loaded members and using an old token for `since`.
|
||||
Generated
+99
-109
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.5.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
@@ -31,7 +31,7 @@ description = "The ultimate Python library in building OAuth and OpenID Connect
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"oidc\" or extra == \"jwt\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"jwt\" or extra == \"oidc\""
|
||||
files = [
|
||||
{file = "authlib-1.6.12-py2.py3-none-any.whl", hash = "sha256:e9229ad7fde610b139dd12f5edbe97eab9ee78bfb85691247e767727850b99ab"},
|
||||
{file = "authlib-1.6.12.tar.gz", hash = "sha256:0656d8482f28fc8221929d5f35b2bde5d13e10555ebc06b4561b0d622e83b1bd"},
|
||||
@@ -62,7 +62,7 @@ description = "Backport of CPython tarfile module"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.12\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\""
|
||||
files = [
|
||||
{file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"},
|
||||
{file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"},
|
||||
@@ -454,76 +454,66 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.7"
|
||||
version = "50.0.1"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.8"
|
||||
python-versions = "!=3.9.0,!=3.9.1,>=3.9"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457"},
|
||||
{file = "cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298"},
|
||||
{file = "cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246"},
|
||||
{file = "cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3"},
|
||||
{file = "cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f"},
|
||||
{file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15"},
|
||||
{file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455"},
|
||||
{file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65"},
|
||||
{file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968"},
|
||||
{file = "cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4"},
|
||||
{file = "cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b"},
|
||||
{file = "cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149"},
|
||||
{file = "cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e"},
|
||||
{file = "cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2"},
|
||||
{file = "cryptography-50.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94"},
|
||||
{file = "cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f"},
|
||||
{file = "cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671"},
|
||||
{file = "cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e"},
|
||||
{file = "cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6"},
|
||||
{file = "cryptography-50.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b"},
|
||||
{file = "cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""}
|
||||
cffi = {version = ">=2.0.0", markers = "platform_python_implementation != \"PyPy\""}
|
||||
typing-extensions = {version = ">=4.13.2", markers = "python_full_version < \"3.11.0\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"]
|
||||
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
|
||||
nox = ["nox[uv] (>=2024.4.15)"]
|
||||
pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"]
|
||||
sdist = ["build (>=1.0.0)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi (>=2024)", "cryptography-vectors (==46.0.7)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
name = "defusedxml"
|
||||
@@ -532,7 +522,7 @@ description = "XML bomb protection for Python stdlib modules"
|
||||
optional = true
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"saml2\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"saml2\""
|
||||
files = [
|
||||
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
|
||||
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
|
||||
@@ -557,7 +547,7 @@ description = "XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and l
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"saml2\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"saml2\""
|
||||
files = [
|
||||
{file = "elementpath-4.8.0-py3-none-any.whl", hash = "sha256:5393191f84969bcf8033b05ec4593ef940e58622ea13cefe60ecefbbf09d58d9"},
|
||||
{file = "elementpath-4.8.0.tar.gz", hash = "sha256:5822a2560d99e2633d95f78694c7ff9646adaa187db520da200a8e9479dc46ae"},
|
||||
@@ -607,7 +597,7 @@ description = "Python wrapper for hiredis"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"redis\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"redis\""
|
||||
files = [
|
||||
{file = "hiredis-3.3.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:f525734382a47f9828c9d6a1501522c78d5935466d8e2be1a41ba40ca5bb922b"},
|
||||
{file = "hiredis-3.3.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:6e2e1024f0a021777740cb7c633a0efb2c4a4bc570f508223a8dcbcf79f99ef9"},
|
||||
@@ -890,7 +880,7 @@ description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.12\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\""
|
||||
files = [
|
||||
{file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"},
|
||||
{file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"},
|
||||
@@ -931,7 +921,7 @@ description = "Jaeger Python OpenTracing Tracer implementation"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"opentracing\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"opentracing\""
|
||||
files = [
|
||||
{file = "jaeger-client-4.8.0.tar.gz", hash = "sha256:3157836edab8e2c209bd2d6ae61113db36f7ee399e66b1dcbb715d87ab49bfe0"},
|
||||
]
|
||||
@@ -1123,7 +1113,7 @@ description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"matrix-synapse-ldap3\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"matrix-synapse-ldap3\""
|
||||
files = [
|
||||
{file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"},
|
||||
{file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"},
|
||||
@@ -1240,7 +1230,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"url-preview\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"url-preview\""
|
||||
files = [
|
||||
{file = "lxml-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec"},
|
||||
{file = "lxml-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec"},
|
||||
@@ -1548,7 +1538,7 @@ description = "An LDAP3 auth provider for Synapse"
|
||||
optional = true
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"matrix-synapse-ldap3\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"matrix-synapse-ldap3\""
|
||||
files = [
|
||||
{file = "matrix_synapse_ldap3-0.4.0-py3-none-any.whl", hash = "sha256:bf080037230d2af5fd3639cb87266de65c1cad7a68ea206278c5b4bf9c1a17f3"},
|
||||
{file = "matrix_synapse_ldap3-0.4.0.tar.gz", hash = "sha256:cff52ba780170de5e6e8af42863d2648ee23f3bf0a9fea6db52372f9fc00be2b"},
|
||||
@@ -1833,7 +1823,7 @@ description = "OpenTracing API for Python. See documentation at http://opentraci
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"opentracing\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"opentracing\""
|
||||
files = [
|
||||
{file = "opentracing-2.4.0.tar.gz", hash = "sha256:a173117e6ef580d55874734d1fa7ecb6f3655160b8b8974a2a1e98e5ec9c840d"},
|
||||
]
|
||||
@@ -2028,7 +2018,7 @@ description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"postgres\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"postgres\""
|
||||
files = [
|
||||
{file = "psycopg2-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:103e857f46bb76908768ead4e2d0ba1d1a130e7b8ed77d3ae91e8b33481813e8"},
|
||||
{file = "psycopg2-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:210daed32e18f35e3140a1ebe059ac29209dd96468f2f7559aa59f75ee82a5cb"},
|
||||
@@ -2046,7 +2036,7 @@ description = ".. image:: https://travis-ci.org/chtd/psycopg2cffi.svg?branch=mas
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "platform_python_implementation == \"PyPy\" and (extra == \"postgres\" or extra == \"all\")"
|
||||
markers = "platform_python_implementation == \"PyPy\" and (extra == \"all\" or extra == \"postgres\")"
|
||||
files = [
|
||||
{file = "psycopg2cffi-2.9.0.tar.gz", hash = "sha256:7e272edcd837de3a1d12b62185eb85c45a19feda9e62fa1b120c54f9e8d35c52"},
|
||||
]
|
||||
@@ -2062,7 +2052,7 @@ description = "A Simple library to enable psycopg2 compatability"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "platform_python_implementation == \"PyPy\" and (extra == \"postgres\" or extra == \"all\")"
|
||||
markers = "platform_python_implementation == \"PyPy\" and (extra == \"all\" or extra == \"postgres\")"
|
||||
files = [
|
||||
{file = "psycopg2cffi-compat-1.1.tar.gz", hash = "sha256:d25e921748475522b33d13420aad5c2831c743227dc1f1f2585e0fdb5c914e05"},
|
||||
]
|
||||
@@ -2302,14 +2292,14 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.13.0"
|
||||
version = "2.15.1"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"},
|
||||
{file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"},
|
||||
{file = "pyjwt-2.15.1-py3-none-any.whl", hash = "sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193"},
|
||||
{file = "pyjwt-2.15.1.tar.gz", hash = "sha256:4f259e80cdfb6b3fc18a7de51fd1ef9ec79652f25019bae68975ca2468a34df8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2342,7 +2332,7 @@ description = "A development tool to measure, monitor and analyze the memory beh
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"cache-memory\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"cache-memory\""
|
||||
files = [
|
||||
{file = "Pympler-1.0.1-py3-none-any.whl", hash = "sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d"},
|
||||
{file = "Pympler-1.0.1.tar.gz", hash = "sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa"},
|
||||
@@ -2392,19 +2382,19 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=7.4.0)", "pytest-cov (>=2.10.1)", "
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "26.0.0"
|
||||
version = "26.4.0"
|
||||
description = "Python wrapper module around the OpenSSL library"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyopenssl-26.0.0-py3-none-any.whl", hash = "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81"},
|
||||
{file = "pyopenssl-26.0.0.tar.gz", hash = "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc"},
|
||||
{file = "pyopenssl-26.4.0-py3-none-any.whl", hash = "sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c"},
|
||||
{file = "pyopenssl-26.4.0.tar.gz", hash = "sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=46.0.0,<47"
|
||||
typing-extensions = {version = ">=4.9", markers = "python_version < \"3.13\" and python_version >= \"3.8\""}
|
||||
cryptography = ">=49.0.0,<51"
|
||||
typing-extensions = {version = ">=4.9", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"]
|
||||
@@ -2474,7 +2464,7 @@ description = "Python implementation of SAML Version 2 Standard"
|
||||
optional = true
|
||||
python-versions = ">=3.9,<4.0"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"saml2\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"saml2\""
|
||||
files = [
|
||||
{file = "pysaml2-7.5.0-py3-none-any.whl", hash = "sha256:bc6627cc344476a83c757f440a73fda1369f13b6fda1b4e16bca63ffbabb5318"},
|
||||
{file = "pysaml2-7.5.0.tar.gz", hash = "sha256:f36871d4e5ee857c6b85532e942550d2cf90ea4ee943d75eb681044bbc4f54f7"},
|
||||
@@ -2499,7 +2489,7 @@ description = "Extensions to the standard Python datetime module"
|
||||
optional = true
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"saml2\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"saml2\""
|
||||
files = [
|
||||
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||
@@ -2527,7 +2517,7 @@ description = "World timezone definitions, modern and historical"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"saml2\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"saml2\""
|
||||
files = [
|
||||
{file = "pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a"},
|
||||
{file = "pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1"},
|
||||
@@ -2931,7 +2921,7 @@ description = "Python client for Sentry (https://sentry.io)"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"sentry\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"sentry\""
|
||||
files = [
|
||||
{file = "sentry_sdk-2.57.0-py2.py3-none-any.whl", hash = "sha256:812c8bf5ff3d2f0e89c82f5ce80ab3a6423e102729c4706af7413fd1eb480585"},
|
||||
{file = "sentry_sdk-2.57.0.tar.gz", hash = "sha256:4be8d1e71c32fb27f79c577a337ac8912137bba4bcbc64a4ec1da4d6d8dc5199"},
|
||||
@@ -3131,7 +3121,7 @@ description = "Tornado IOLoop Backed Concurrent Futures"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"opentracing\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"opentracing\""
|
||||
files = [
|
||||
{file = "threadloop-1.0.2-py2-none-any.whl", hash = "sha256:5c90dbefab6ffbdba26afb4829d2a9df8275d13ac7dc58dccb0e279992679599"},
|
||||
{file = "threadloop-1.0.2.tar.gz", hash = "sha256:8b180aac31013de13c2ad5c834819771992d350267bddb854613ae77ef571944"},
|
||||
@@ -3147,7 +3137,7 @@ description = "Python bindings for the Apache Thrift RPC system"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"opentracing\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"opentracing\""
|
||||
files = [
|
||||
{file = "thrift-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efe85c4508adaf6c9e6f7fac2b1c3c9beb4b39b19c375519966335a70b28e64a"},
|
||||
{file = "thrift-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2baaec0c5cd7ba3eace54b26d81fdf0f5a85010468687cf4a131871d65abfed"},
|
||||
@@ -3252,23 +3242,23 @@ markers = {main = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.5.7"
|
||||
version = "6.5.9"
|
||||
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"opentracing\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"opentracing\""
|
||||
files = [
|
||||
{file = "tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4"},
|
||||
{file = "tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796"},
|
||||
{file = "tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:dea3bee433b73d6312d993ce89ad5f2b8d3ed9fde56e79ba5342b9edb78d342a"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:22bed49cdb55292d1d58f46008ba2cc3020e5b84995ef6ae749676c8b5309514"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0cc0876a58eee9143476f24901b1b8eb4d52443283ade2b6bca650dd1da04f84"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc0d703cc7ec92b47ebfe236c97cd765c1f2e45744a2e156e6677e54c948274d"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:93f9ab0226ef625b94cacb7981a5af104f1735ea02f92282abe5c389109fb31d"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5801bbf94dfde8d21087b6aa0b17f3a5dab6110dfe44e7e4492df5de393ac15b"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-win32.whl", hash = "sha256:dca9f377e80efe5dd4d52a2776b67492a81a39b2d931de169b6b36d0c1da444c"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-win_amd64.whl", hash = "sha256:f810730ac71eadf009e6e5cb6d534d096887310f5649cff19d7e0d19c0bf4ee7"},
|
||||
{file = "tornado-6.5.9-cp39-abi3-win_arm64.whl", hash = "sha256:b5f4d92798337260c6d9f9f0e400c7539dd5e55cf93479a3f8508c9aa09c2b3b"},
|
||||
{file = "tornado-6.5.9.tar.gz", hash = "sha256:4d868544ebdf2fc155239a74397f65ebfea9b4d3635296c96b5dfb0701c354f5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3334,7 +3324,7 @@ keyring = {version = ">=21.2.0", markers = "platform_machine != \"ppc64le\" and
|
||||
packaging = ">=24.0"
|
||||
readme-renderer = ">=35.0"
|
||||
requests = ">=2.20"
|
||||
requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0"
|
||||
requests-toolbelt = ">=0.8.0,!=0.9.0"
|
||||
rfc3986 = ">=1.4.0"
|
||||
rich = ">=12.0.0"
|
||||
urllib3 = ">=1.26.0"
|
||||
@@ -3389,7 +3379,7 @@ description = "non-blocking redis client for python"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"redis\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"redis\""
|
||||
files = [
|
||||
{file = "txredisapi-1.4.12-py3-none-any.whl", hash = "sha256:c698fff24a0b82e8932ef18c5623496b0a8b5195e4e94bf8524800a00ca6b7c1"},
|
||||
{file = "txredisapi-1.4.12.tar.gz", hash = "sha256:98e2440ff2e297c9048c5f9d516e7b1302d447cd9cd4568c986c35a8523de3c7"},
|
||||
@@ -3615,14 +3605,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.7.0"
|
||||
version = "2.8.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"},
|
||||
{file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"},
|
||||
{file = "urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3"},
|
||||
{file = "urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3650,7 +3640,7 @@ description = "An XML Schema validator and decoder"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"saml2\" or extra == \"all\""
|
||||
markers = "extra == \"all\" or extra == \"saml2\""
|
||||
files = [
|
||||
{file = "xmlschema-2.5.1-py3-none-any.whl", hash = "sha256:ec2b2a15c8896c1fcd14dcee34ca30032b99456c3c43ce793fdb9dca2fb4b869"},
|
||||
{file = "xmlschema-2.5.1.tar.gz", hash = "sha256:4f7497de6c8b6dc2c28ad7b9ed6e21d186f4afe248a5bea4f54eedab4da44083"},
|
||||
@@ -3671,7 +3661,7 @@ description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.12\" and platform_machine != \"ppc64le\" and platform_machine != \"s390x\""
|
||||
markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\" and python_version < \"3.12\""
|
||||
files = [
|
||||
{file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"},
|
||||
{file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"},
|
||||
|
||||
@@ -158,6 +158,7 @@ async fn build_versions_response(
|
||||
"v1.12".to_string(),
|
||||
"v1.13".to_string(),
|
||||
"v1.14".to_string(),
|
||||
"v1.15".to_string(),
|
||||
]),
|
||||
unstable_features: unstable_feature_map,
|
||||
})
|
||||
|
||||
+163
-43
@@ -23,6 +23,7 @@ import itertools
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
AbstractSet,
|
||||
@@ -74,6 +75,7 @@ from synapse.types import (
|
||||
MutableStateMap,
|
||||
Requester,
|
||||
RoomStreamToken,
|
||||
StateKey,
|
||||
StateMap,
|
||||
StrCollection,
|
||||
StreamKeyType,
|
||||
@@ -130,6 +132,96 @@ LAZY_LOADED_PROFILE_FIELDS_CACHE_DIGEST_SIZE = 16
|
||||
LAZY_LOADED_PROFILE_FIELDS_CACHE_DIGEST_KEY = os.urandom(32)
|
||||
|
||||
|
||||
@attr.s(slots=True, auto_attribs=True)
|
||||
class LazyLoadedMembersCache:
|
||||
"""The membership events a lazy-loading client has been sent, so that later
|
||||
syncs can leave them out. One per (user, device).
|
||||
|
||||
A member may only be left out if the client processed the response that
|
||||
carried it. Each entry therefore records the `next_batch` position of the
|
||||
response that carried it. A client that has processed a response syncs
|
||||
from its `next_batch` next, so a request from `since` means the client
|
||||
processed every response up to `since` and none after it. `start_request`
|
||||
forgets the entries recorded after `since`. They were in responses the
|
||||
client lost, by timing out a slow request and retrying it (#19978) or by
|
||||
reloading from an older persisted token (#20278). Entries recorded at or
|
||||
before `since` stay, so a client resuming from a token a few minutes old is
|
||||
only sent again what it lost in those minutes. An initial sync forgets
|
||||
everything.
|
||||
|
||||
Forgetting everything whenever `since` is before the newest entry would be
|
||||
simpler, and `_newest` alone would suffice. But Element Web only persists
|
||||
its sync token every few minutes and resumes from it on every reload, so
|
||||
that would re-send every cached member on each reload.
|
||||
|
||||
The notifier may build several results for one long-poll and returns only
|
||||
the first non-empty one. An empty result carries no members, so it records
|
||||
nothing.
|
||||
|
||||
Leaving out a member the client never received loses it for good. A
|
||||
`state_after` client does not apply state from the timeline, and a `state`
|
||||
client only sees the membership changes that fall inside the timeline.
|
||||
"""
|
||||
|
||||
# user_id -> (event_id of the membership we last sent, room position of the
|
||||
# `next_batch` of the response that carried it). Least recently used first.
|
||||
#
|
||||
# Not an `LruCache`, as that cannot be iterated over and `start_request`
|
||||
# needs to check every entry's position against `since`.
|
||||
_sent: OrderedDict[str, tuple[str, RoomStreamToken]] = attr.Factory(OrderedDict)
|
||||
|
||||
# An upper bound on the positions of the entries, so that `start_request`
|
||||
# need not look at them when nothing was recorded after `since`. That is the
|
||||
# steady state, where `since` is the `next_batch` of the last response.
|
||||
_newest: RoomStreamToken | None = None
|
||||
|
||||
def start_request(self, since_token: StreamToken | None) -> None:
|
||||
"""Forget the members the client did not receive. Called at the start of
|
||||
each sync request for this device."""
|
||||
if since_token is None:
|
||||
self._sent.clear()
|
||||
self._newest = None
|
||||
return
|
||||
|
||||
# Check for the common case where nothing has been recorded after
|
||||
# `since`.
|
||||
since = since_token.room_key
|
||||
if self._newest is None or self._newest.is_before_or_eq(since):
|
||||
return
|
||||
|
||||
# Remove all entries that were sent after `since`. This only has
|
||||
# `LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE` entries.
|
||||
for user_id, (_, sent_at) in list(self._sent.items()):
|
||||
if not sent_at.is_before_or_eq(since):
|
||||
del self._sent[user_id]
|
||||
|
||||
# Everything left is at or before `since`.
|
||||
self._newest = since
|
||||
|
||||
def was_sent(self, user_id: str, event_id: str) -> bool:
|
||||
"""Whether `event_id` is the membership of `user_id` we last sent."""
|
||||
entry = self._sent.get(user_id)
|
||||
if entry is None or entry[0] != event_id:
|
||||
return False
|
||||
self._sent.move_to_end(user_id)
|
||||
return True
|
||||
|
||||
def mark_sent(self, user_id: str, event_id: str, next_batch: StreamToken) -> None:
|
||||
"""Record that `event_id` is being sent in the response ending at
|
||||
`next_batch`."""
|
||||
sent_at = next_batch.room_key
|
||||
self._sent[user_id] = (event_id, sent_at)
|
||||
self._sent.move_to_end(user_id)
|
||||
while len(self._sent) > LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE:
|
||||
self._sent.popitem(last=False)
|
||||
|
||||
# Only move the bound forwards. Positions taken by this process only
|
||||
# ever advance, but a response that started earlier may record its
|
||||
# entries after a later one has.
|
||||
if self._newest is None or self._newest.is_before_or_eq(sent_at):
|
||||
self._newest = sent_at
|
||||
|
||||
|
||||
SyncRequestKey = tuple[Any, ...]
|
||||
|
||||
|
||||
@@ -345,9 +437,9 @@ class SyncHandler:
|
||||
timeout=hs.config.caches.sync_response_cache_duration,
|
||||
)
|
||||
|
||||
# ExpiringCache((User, Device)) -> LruCache(user_id => event_id)
|
||||
# ExpiringCache((User, Device)) -> LazyLoadedMembersCache
|
||||
self.lazy_loaded_members_cache: ExpiringCache[
|
||||
tuple[str, str | None], LruCache[str, str]
|
||||
tuple[str, str | None], LazyLoadedMembersCache
|
||||
] = ExpiringCache(
|
||||
cache_name="lazy_loaded_members_cache",
|
||||
server_name=self.server_name,
|
||||
@@ -1088,7 +1180,7 @@ class SyncHandler:
|
||||
member_ids[hero_id]
|
||||
for hero_id in summary["m.heroes"]
|
||||
if (
|
||||
cache.get(hero_id) != member_ids[hero_id]
|
||||
not cache.was_sent(hero_id, member_ids[hero_id])
|
||||
and hero_id not in existing_members
|
||||
)
|
||||
]
|
||||
@@ -1096,27 +1188,21 @@ class SyncHandler:
|
||||
missing_hero_state = await self.store.get_events(missing_hero_event_ids)
|
||||
|
||||
for s in missing_hero_state.values():
|
||||
cache.set(s.state_key, s.event_id)
|
||||
cache.mark_sent(s.state_key, s.event_id, now_token)
|
||||
state[(EventTypes.Member, s.state_key)] = s
|
||||
|
||||
return summary
|
||||
|
||||
def get_lazy_loaded_members_cache(
|
||||
self, cache_key: tuple[str, str | None]
|
||||
) -> LruCache[str, str]:
|
||||
# FIXME: This cache may be subject to losing members in the case that
|
||||
# a sync is interrupted and retried, see https://github.com/element-hq/synapse/issues/19978
|
||||
cache: LruCache[str, str] | None = self.lazy_loaded_members_cache.get(cache_key)
|
||||
) -> LazyLoadedMembersCache:
|
||||
cache = self.lazy_loaded_members_cache.get(cache_key)
|
||||
if cache is None:
|
||||
logger.debug("creating LruCache for %r", cache_key)
|
||||
cache = LruCache(
|
||||
max_size=LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE,
|
||||
clock=self.clock,
|
||||
server_name=self.server_name,
|
||||
)
|
||||
logger.debug("creating LazyLoadedMembersCache for %r", cache_key)
|
||||
cache = LazyLoadedMembersCache()
|
||||
self.lazy_loaded_members_cache[cache_key] = cache
|
||||
else:
|
||||
logger.debug("found LruCache for %r", cache_key)
|
||||
logger.debug("found LazyLoadedMembersCache for %r", cache_key)
|
||||
return cache
|
||||
|
||||
def get_lazy_loaded_profile_fields_cache(
|
||||
@@ -1155,6 +1241,7 @@ class SyncHandler:
|
||||
sync_config: SyncConfig,
|
||||
since_token: StreamToken | None,
|
||||
end_token: StreamToken,
|
||||
now_token: StreamToken,
|
||||
full_state: bool,
|
||||
joined: bool,
|
||||
) -> MutableStateMap[EventBase]:
|
||||
@@ -1169,6 +1256,7 @@ class SyncHandler:
|
||||
end_token: Token of the end of the current batch. Normally this will be
|
||||
the same as the global "now_token", but if the user has left the room,
|
||||
the point just after their leave event.
|
||||
now_token: The `next_batch` token of the response being built.
|
||||
full_state: Whether to force returning the full state.
|
||||
`lazy_load_members` still applies when `full_state` is `True`.
|
||||
joined: whether the user is currently joined to the room
|
||||
@@ -1254,6 +1342,11 @@ class SyncHandler:
|
||||
# sync's timeline and the start of the current sync's timeline.
|
||||
# See the docstring above for details.
|
||||
state_ids: StateMap[str]
|
||||
# The keys in `state_ids` whose state changed since `since_token`.
|
||||
# These are sent whether or not the lazy-loaded members cache says
|
||||
# the client has them. The rest are memberships fetched for
|
||||
# timeline senders, which the client may already have.
|
||||
changed_keys: set[StateKey] = set()
|
||||
# We need to know whether the state we fetch may be partial, so check
|
||||
# whether the room is partial stated *before* fetching it.
|
||||
is_partial_state_room = await self.store.is_partial_state_room(room_id)
|
||||
@@ -1273,7 +1366,10 @@ class SyncHandler:
|
||||
# is indeed the case.
|
||||
assert since_token is not None
|
||||
|
||||
state_ids = await self._compute_state_delta_for_incremental_sync(
|
||||
(
|
||||
state_ids,
|
||||
changed_keys,
|
||||
) = await self._compute_state_delta_for_incremental_sync(
|
||||
room_id,
|
||||
sync_config,
|
||||
batch,
|
||||
@@ -1309,30 +1405,28 @@ class SyncHandler:
|
||||
cache_key = (sync_config.user.to_string(), sync_config.device_id)
|
||||
cache = self.get_lazy_loaded_members_cache(cache_key)
|
||||
|
||||
# if it's a new sync sequence, then assume the client has had
|
||||
# amnesia and doesn't want any recent lazy-loaded members
|
||||
# de-duplicated.
|
||||
if since_token is None:
|
||||
logger.debug("clearing LruCache for %r", cache_key)
|
||||
cache.clear()
|
||||
else:
|
||||
# only send members which aren't in our LruCache (either
|
||||
# because they're new to this client or have been pushed out
|
||||
# of the cache)
|
||||
logger.debug("filtering state from %r...", state_ids)
|
||||
state_ids = {
|
||||
t: event_id
|
||||
for t, event_id in state_ids.items()
|
||||
if cache.get(t[1]) != event_id
|
||||
}
|
||||
logger.debug("...to %r", state_ids)
|
||||
# Only send members which aren't in our cache (either because
|
||||
# they're new to this client or have been pushed out of the
|
||||
# cache). For an initial sync the cache has already been
|
||||
# cleared.
|
||||
#
|
||||
# A state change since `since_token` is always sent, regardless
|
||||
# of whether it was previously sent to the client (e.g due to a
|
||||
# client retrying the request).
|
||||
logger.debug("filtering state from %r...", state_ids)
|
||||
state_ids = {
|
||||
t: event_id
|
||||
for t, event_id in state_ids.items()
|
||||
if t in changed_keys or not cache.was_sent(t[1], event_id)
|
||||
}
|
||||
logger.debug("...to %r", state_ids)
|
||||
|
||||
# add any member IDs we are about to send into our LruCache
|
||||
# add any member IDs we are about to send into our cache
|
||||
for t, event_id in itertools.chain(
|
||||
state_ids.items(), timeline_state.items()
|
||||
):
|
||||
if t[0] == EventTypes.Member:
|
||||
cache.set(t[1], event_id)
|
||||
cache.mark_sent(t[1], event_id, now_token)
|
||||
|
||||
state: dict[str, EventBase] = {}
|
||||
if state_ids:
|
||||
@@ -1505,14 +1599,16 @@ class SyncHandler:
|
||||
members_to_fetch: set[str] | None,
|
||||
timeline_state: StateMap[str],
|
||||
joined: bool,
|
||||
) -> StateMap[str]:
|
||||
) -> tuple[StateMap[str], set[StateKey]]:
|
||||
"""Calculate the state events to be included in an incremental sync response.
|
||||
|
||||
If lazy-loading of membership events is enabled (as indicated by
|
||||
`members_to_fetch` being not-`None`), the result will include the membership
|
||||
events for each member in `members_to_fetch`. The caller
|
||||
(`compute_state_delta`) is responsible for keeping track of which membership
|
||||
events we have already sent to the client, and hence ripping them out.
|
||||
events we have already sent to the client, and hence ripping them out. It
|
||||
may only rip out entries that are not state changes, so this also returns
|
||||
the keys whose state changed since `since_token`.
|
||||
|
||||
Note that whether this returns the state at the start or the end of the
|
||||
batch depends on `sync_config.use_state_after` (c.f. MSC4222).
|
||||
@@ -1533,7 +1629,10 @@ class SyncHandler:
|
||||
|
||||
Returns:
|
||||
A map from (type, state_key) to event_id, for each event that we believe
|
||||
should be included in the `state` or `state_after` part of the sync response.
|
||||
should be included in the `state` or `state_after` part of the sync
|
||||
response, and the subset of its keys whose state changed since
|
||||
`since_token`. The other keys are memberships fetched for timeline
|
||||
senders, which the client may have been sent before.
|
||||
"""
|
||||
if members_to_fetch is not None:
|
||||
# Lazy-loading is enabled. Only return the state that is needed.
|
||||
@@ -1549,6 +1648,7 @@ class SyncHandler:
|
||||
# timeline. If at the end we can just use the current state delta stream.
|
||||
if sync_config.use_state_after:
|
||||
delta_state_ids: MutableStateMap[str] = {}
|
||||
changed_keys: set[StateKey] = set()
|
||||
|
||||
if members_to_fetch:
|
||||
# We're lazy-loading, so the client might need some more member
|
||||
@@ -1603,9 +1703,11 @@ class SyncHandler:
|
||||
# Note that deltas are in stream ordering, so if there are
|
||||
# multiple deltas for a given type/state_key we'll always pick
|
||||
# the latest one.
|
||||
delta_state_ids[(delta.event_type, delta.state_key)] = delta.event_id
|
||||
key = (delta.event_type, delta.state_key)
|
||||
delta_state_ids[key] = delta.event_id
|
||||
changed_keys.add(key)
|
||||
|
||||
return delta_state_ids
|
||||
return delta_state_ids, changed_keys
|
||||
|
||||
# For a non-gappy sync if the events in the timeline are simply a linear
|
||||
# chain (i.e. no merging/branching of the graph), then we know the state
|
||||
@@ -1653,7 +1755,9 @@ class SyncHandler:
|
||||
await_full_state=False,
|
||||
)
|
||||
)
|
||||
return state_ids
|
||||
# A linear, non-gappy timeline means the only changes to the `state`
|
||||
# are lazy-loaded members.
|
||||
return state_ids, set()
|
||||
|
||||
if batch:
|
||||
state_at_timeline_start = (
|
||||
@@ -1712,7 +1816,16 @@ class SyncHandler:
|
||||
lazy_load_members=lazy_load_members,
|
||||
)
|
||||
|
||||
return state_ids
|
||||
# Everything that differs from the state at the previous sync is a
|
||||
# change. What is left is the memberships of timeline senders that
|
||||
# `_calculate_state` adds back for lazy loading.
|
||||
changed_keys = {
|
||||
key
|
||||
for key, event_id in state_ids.items()
|
||||
if state_at_previous_sync.get(key) != event_id
|
||||
}
|
||||
|
||||
return state_ids, changed_keys
|
||||
|
||||
async def _find_missing_partial_state_memberships(
|
||||
self,
|
||||
@@ -2650,7 +2763,13 @@ class SyncHandler:
|
||||
"""
|
||||
|
||||
since_token = sync_result_builder.since_token
|
||||
user_id = sync_result_builder.sync_config.user.to_string()
|
||||
sync_config = sync_result_builder.sync_config
|
||||
user_id = sync_config.user.to_string()
|
||||
|
||||
if sync_config.filter_collection.lazy_load_members():
|
||||
self.get_lazy_loaded_members_cache(
|
||||
(user_id, sync_config.device_id)
|
||||
).start_request(since_token)
|
||||
|
||||
blocks_all_rooms = (
|
||||
sync_result_builder.sync_config.filter_collection.blocks_all_rooms()
|
||||
@@ -3318,6 +3437,7 @@ class SyncHandler:
|
||||
sync_config,
|
||||
since_token,
|
||||
room_builder.end_token,
|
||||
now_token,
|
||||
full_state=full_state,
|
||||
joined=room_builder.rtype == "joined",
|
||||
)
|
||||
|
||||
@@ -33,7 +33,9 @@ from synapse.api.room_versions import RoomVersion, RoomVersions
|
||||
from synapse.events import EventBase
|
||||
from synapse.events.snapshot import EventContext
|
||||
from synapse.handlers.sync import (
|
||||
LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE,
|
||||
LAZY_LOADED_PROFILE_FIELDS_CACHE_MAX_AGE,
|
||||
LazyLoadedMembersCache,
|
||||
SyncConfig,
|
||||
SyncRequestKey,
|
||||
SyncResult,
|
||||
@@ -48,6 +50,7 @@ from synapse.types import (
|
||||
MultiWriterStreamToken,
|
||||
RoomStreamToken,
|
||||
StreamKeyType,
|
||||
StreamToken,
|
||||
UserID,
|
||||
create_requester,
|
||||
)
|
||||
@@ -2947,7 +2950,7 @@ class SyncStateAfterTestCase(tests.unittest.HomeserverTestCase):
|
||||
|
||||
# Calculating the incrementals state will return the second state, and not the
|
||||
# first.
|
||||
state = self.get_success(
|
||||
state, _ = self.get_success(
|
||||
self.sync_handler._compute_state_delta_for_incremental_sync(
|
||||
room_id=joined_room,
|
||||
sync_config=generate_sync_config(user, use_state_after=True),
|
||||
@@ -2979,7 +2982,7 @@ class SyncStateAfterTestCase(tests.unittest.HomeserverTestCase):
|
||||
since_token = self.hs.get_event_sources().get_current_token()
|
||||
end_stream_token = self.hs.get_event_sources().get_current_token()
|
||||
|
||||
state = self.get_success(
|
||||
state, _ = self.get_success(
|
||||
self.sync_handler._compute_state_delta_for_incremental_sync(
|
||||
room_id=joined_room,
|
||||
sync_config=generate_sync_config(user, use_state_after=True),
|
||||
@@ -2995,3 +2998,76 @@ class SyncStateAfterTestCase(tests.unittest.HomeserverTestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(state, {})
|
||||
|
||||
|
||||
class LazyLoadedMembersCacheTestCase(tests.unittest.TestCase):
|
||||
"""Direct tests of the per-device cache of lazy-loaded members sent."""
|
||||
|
||||
@staticmethod
|
||||
def _token(stream: int) -> StreamToken:
|
||||
"""A token whose room position is `stream`. Only the room position
|
||||
matters to the cache."""
|
||||
return StreamToken.START.copy_and_replace(
|
||||
StreamKeyType.ROOM, RoomStreamToken(stream=stream)
|
||||
)
|
||||
|
||||
def test_forgets_only_entries_after_since(self) -> None:
|
||||
"""A request from `since` forgets the entries recorded after it and
|
||||
keeps the rest."""
|
||||
cache = LazyLoadedMembersCache()
|
||||
cache.mark_sent("@a:test", "$a", self._token(5))
|
||||
cache.mark_sent("@b:test", "$b", self._token(7))
|
||||
|
||||
# The client synced past both responses.
|
||||
cache.start_request(self._token(7))
|
||||
self.assertTrue(cache.was_sent("@a:test", "$a"))
|
||||
self.assertTrue(cache.was_sent("@b:test", "$b"))
|
||||
|
||||
# The client lost the response that ended at 7.
|
||||
cache.start_request(self._token(6))
|
||||
self.assertTrue(cache.was_sent("@a:test", "$a"))
|
||||
self.assertFalse(cache.was_sent("@b:test", "$b"))
|
||||
|
||||
# A later request from an even older token forgets the rest.
|
||||
cache.start_request(self._token(4))
|
||||
self.assertFalse(cache.was_sent("@a:test", "$a"))
|
||||
|
||||
def test_initial_sync_forgets_everything(self) -> None:
|
||||
"""An initial sync has no `since`, and the client starts from nothing."""
|
||||
cache = LazyLoadedMembersCache()
|
||||
cache.mark_sent("@a:test", "$a", self._token(5))
|
||||
cache.start_request(None)
|
||||
self.assertFalse(cache.was_sent("@a:test", "$a"))
|
||||
|
||||
def test_different_event_is_not_sent(self) -> None:
|
||||
"""An entry only covers the membership event it was recorded with, so
|
||||
a changed membership counts as not sent."""
|
||||
cache = LazyLoadedMembersCache()
|
||||
cache.mark_sent("@a:test", "$a1", self._token(5))
|
||||
self.assertFalse(cache.was_sent("@a:test", "$a2"))
|
||||
|
||||
def test_out_of_order_marks_are_still_forgotten(self) -> None:
|
||||
"""A response that started earlier can record its entries after a
|
||||
later one. Its entries must still be forgotten by a request from
|
||||
before either response."""
|
||||
cache = LazyLoadedMembersCache()
|
||||
cache.mark_sent("@b:test", "$b", self._token(7))
|
||||
cache.mark_sent("@a:test", "$a", self._token(5))
|
||||
|
||||
cache.start_request(self._token(6))
|
||||
self.assertTrue(cache.was_sent("@a:test", "$a"))
|
||||
self.assertFalse(cache.was_sent("@b:test", "$b"))
|
||||
|
||||
def test_evicts_least_recently_used(self) -> None:
|
||||
"""The cache holds `LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE` entries and
|
||||
evicts the least recently used one. A hit counts as a use."""
|
||||
cache = LazyLoadedMembersCache()
|
||||
for i in range(LAZY_LOADED_MEMBERS_CACHE_MAX_SIZE):
|
||||
cache.mark_sent(f"@u{i}:test", f"$u{i}", self._token(1))
|
||||
# Touch the oldest entry so that it is no longer the eviction candidate.
|
||||
self.assertTrue(cache.was_sent("@u0:test", "$u0"))
|
||||
|
||||
cache.mark_sent("@new:test", "$new", self._token(1))
|
||||
self.assertTrue(cache.was_sent("@u0:test", "$u0"))
|
||||
self.assertFalse(cache.was_sent("@u1:test", "$u1"))
|
||||
self.assertTrue(cache.was_sent("@new:test", "$new"))
|
||||
@@ -45,7 +45,7 @@ from tests.federation.transport.test_knocking import (
|
||||
KnockingStrippedStateEventHelperMixin,
|
||||
)
|
||||
from tests.rest.client.test_rooms import make_request_with_cancellation_test
|
||||
from tests.server import TimedOutException
|
||||
from tests.server import FakeChannel, TimedOutException
|
||||
from tests.test_utils.event_injection import (
|
||||
inject_event,
|
||||
persist_message_and_state_event_in_one_batch,
|
||||
@@ -1684,3 +1684,334 @@ class SyncStateAfterArchivedRoomTestCase(unittest.HomeserverTestCase):
|
||||
],
|
||||
[bob_member_event_id_at_leave],
|
||||
)
|
||||
|
||||
|
||||
class SyncLazyLoadedMembersCacheTestCase(unittest.HomeserverTestCase):
|
||||
"""Tests for the per-device cache of lazy-loaded members already sent.
|
||||
|
||||
A membership left out of a response as already sent is lost for good if
|
||||
the client never processed the response that carried it. The cache must
|
||||
only trust a response once the client has synced from its `next_batch`.
|
||||
"""
|
||||
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
room.register_servlets,
|
||||
login.register_servlets,
|
||||
sync.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.alice = self.register_user("alice", "password")
|
||||
self.alice_device = "alice_device"
|
||||
self.alice_tok = self.login("alice", "password", device_id=self.alice_device)
|
||||
self.bob = self.register_user("bob", "password")
|
||||
self.bob_tok = self.login("bob", "password")
|
||||
|
||||
self.get_success(
|
||||
hs.get_datastores().main.set_features_for_user(
|
||||
self.alice, {ExperimentalFeature.MSC4222: True}
|
||||
)
|
||||
)
|
||||
|
||||
# Named room, so that hero calculation doesn't inject current
|
||||
# membership state into the response and confuse the assertions.
|
||||
self.room_id = self.helper.create_room_as(
|
||||
self.alice, tok=self.alice_tok, extra_content={"name": "test room"}
|
||||
)
|
||||
self.helper.join(self.room_id, self.bob, tok=self.bob_tok)
|
||||
|
||||
# Push bob's join out of the initial sync's timeline, so that his
|
||||
# membership has not been sent to the client when a test starts.
|
||||
for i in range(10):
|
||||
self.helper.send(self.room_id, body=f"pad {i}", tok=self.alice_tok)
|
||||
|
||||
def _sync_request(
|
||||
self,
|
||||
use_state_after: bool,
|
||||
since: str | None = None,
|
||||
*,
|
||||
timeline_limit: int = 10,
|
||||
timeline_not_types: list[str] | None = None,
|
||||
timeout_ms: int = 0,
|
||||
await_result: bool = True,
|
||||
) -> FakeChannel:
|
||||
"""Make a lazy-loading `/sync` request as alice."""
|
||||
timeline_filter: JsonDict = {"limit": timeline_limit}
|
||||
if timeline_not_types is not None:
|
||||
timeline_filter["not_types"] = timeline_not_types
|
||||
sync_filter: JsonDict = {
|
||||
"room": {
|
||||
"timeline": timeline_filter,
|
||||
"state": {"lazy_load_members": True},
|
||||
}
|
||||
}
|
||||
url = f"/sync?filter={json.dumps(sync_filter)}&timeout={timeout_ms}"
|
||||
if use_state_after:
|
||||
url += "&org.matrix.msc4222.use_state_after=true"
|
||||
if since is not None:
|
||||
url += f"&since={since}"
|
||||
return self.make_request(
|
||||
"GET", url, access_token=self.alice_tok, await_result=await_result
|
||||
)
|
||||
|
||||
def _sync(
|
||||
self,
|
||||
use_state_after: bool,
|
||||
since: str | None = None,
|
||||
*,
|
||||
timeline_limit: int = 10,
|
||||
timeline_not_types: list[str] | None = None,
|
||||
) -> JsonDict:
|
||||
"""Sync as alice and return the response body."""
|
||||
channel = self._sync_request(
|
||||
use_state_after,
|
||||
since,
|
||||
timeline_limit=timeline_limit,
|
||||
timeline_not_types=timeline_not_types,
|
||||
)
|
||||
self.assertEqual(channel.code, 200, channel.result)
|
||||
return channel.json_body
|
||||
|
||||
def _room(self, response: JsonDict) -> JsonDict:
|
||||
"""Extract the room dict for the test room from a sync response."""
|
||||
rooms = response["rooms"].get("join", {})
|
||||
self.assertIn(self.room_id, rooms, f"room missing from sync: {response}")
|
||||
return rooms[self.room_id]
|
||||
|
||||
def _timeline_ids(self, response: JsonDict) -> list[str]:
|
||||
return [e["event_id"] for e in self._room(response)["timeline"]["events"]]
|
||||
|
||||
def _state_events(
|
||||
self, response: JsonDict, use_state_after: bool
|
||||
) -> list[JsonDict]:
|
||||
"""Extract the list of state events from the test room in a sync response."""
|
||||
key = "org.matrix.msc4222.state_after" if use_state_after else "state"
|
||||
return self._room(response)[key]["events"]
|
||||
|
||||
def _state_ids(self, response: JsonDict, use_state_after: bool) -> list[str]:
|
||||
"""Extract the list of state event IDs from the test room in a sync
|
||||
response."""
|
||||
return [e["event_id"] for e in self._state_events(response, use_state_after)]
|
||||
|
||||
def _members_in_state(self, response: JsonDict, use_state_after: bool) -> list[str]:
|
||||
"""Extract the list of member state keys from the test room in a sync
|
||||
response."""
|
||||
return [
|
||||
e["state_key"]
|
||||
for e in self._state_events(response, use_state_after)
|
||||
if e["type"] == EventTypes.Member
|
||||
]
|
||||
|
||||
@parameterized.expand([("state", False), ("state_after", True)])
|
||||
def test_member_sent_once_when_acknowledged(
|
||||
self, _: str, use_state_after: bool
|
||||
) -> None:
|
||||
"""A lazy-loaded member is sent the first time it is needed and left
|
||||
out once the client has synced past the response that carried it."""
|
||||
t0 = self._sync(use_state_after)["next_batch"]
|
||||
|
||||
self.helper.send(self.room_id, body="one", tok=self.bob_tok)
|
||||
response = self._sync(use_state_after, t0)
|
||||
self.assertEqual(self._members_in_state(response, use_state_after), [self.bob])
|
||||
t1 = response["next_batch"]
|
||||
|
||||
self.helper.send(self.room_id, body="two", tok=self.bob_tok)
|
||||
response = self._sync(use_state_after, t1)
|
||||
self.assertEqual(self._members_in_state(response, use_state_after), [])
|
||||
|
||||
@parameterized.expand([("state", False), ("state_after", True)])
|
||||
def test_member_not_resent_on_resync_from_token_that_carried_it(
|
||||
self, _: str, use_state_after: bool
|
||||
) -> None:
|
||||
"""A client that resumes from an older token is only sent again the
|
||||
members carried by the responses after that token.
|
||||
|
||||
A member sent in the response that ended at the resumed-from token was
|
||||
processed by the client before it saved that token, so it must not be
|
||||
sent again. matrix-js-sdk saves its sync store every few minutes and
|
||||
resumes from the saved token on reload, so this is the common case.
|
||||
"""
|
||||
t0 = self._sync(use_state_after)["next_batch"]
|
||||
|
||||
# bob's membership is carried by the response that ends at `t1`.
|
||||
self.helper.send(self.room_id, body="one", tok=self.bob_tok)
|
||||
response = self._sync(use_state_after, t0)
|
||||
self.assertEqual(self._members_in_state(response, use_state_after), [self.bob])
|
||||
t1 = response["next_batch"]
|
||||
|
||||
# The client processes further responses, then reloads from `t1`.
|
||||
self.helper.send(self.room_id, body="two", tok=self.bob_tok)
|
||||
self._sync(use_state_after, t1)
|
||||
self.helper.send(self.room_id, body="three", tok=self.bob_tok)
|
||||
response = self._sync(use_state_after, t1)
|
||||
self.assertEqual(
|
||||
self._members_in_state(response, use_state_after),
|
||||
[],
|
||||
f"bob's membership re-sent on resume from the token that carried "
|
||||
f"it: {response}",
|
||||
)
|
||||
|
||||
@parameterized.expand([("state", False), ("state_after", True)])
|
||||
def test_member_resent_on_retry_from_same_token(
|
||||
self, _: str, use_state_after: bool
|
||||
) -> None:
|
||||
"""A client that gives up on a slow request and retries it must get the
|
||||
lazy-loaded members again. It never processed the first response."""
|
||||
t0 = self._sync(use_state_after)["next_batch"]
|
||||
|
||||
self.helper.send(self.room_id, body="hello", tok=self.bob_tok)
|
||||
for attempt in range(2):
|
||||
response = self._sync(use_state_after, t0)
|
||||
self.assertEqual(
|
||||
self._members_in_state(response, use_state_after),
|
||||
[self.bob],
|
||||
f"bob's membership missing on attempt {attempt}: {response}",
|
||||
)
|
||||
|
||||
def test_membership_change_resent_on_resync_from_older_token(self) -> None:
|
||||
"""A membership change must be in `state_after` every time the sync
|
||||
window covers it, even if it has been sent before.
|
||||
|
||||
Clients do not persist every `since` token. matrix-js-sdk saves its
|
||||
sync store every few minutes, so after a reload it resumes from a token
|
||||
older than the one the change was first delivered on. A `state_after`
|
||||
client never applies state from the timeline, so if the change is left
|
||||
out of `state_after` it keeps the stale membership until it clears its
|
||||
cache. A kicked user stays shown as joined.
|
||||
"""
|
||||
t0 = self._sync(use_state_after=True)["next_batch"]
|
||||
|
||||
self.helper.send(self.room_id, body="hello", tok=self.bob_tok)
|
||||
t1 = self._sync(use_state_after=True, since=t0)["next_batch"]
|
||||
|
||||
kick = self.helper.change_membership(
|
||||
self.room_id, self.alice, self.bob, "leave", tok=self.alice_tok
|
||||
)
|
||||
|
||||
# The live client sees the kick.
|
||||
response = self._sync(use_state_after=True, since=t1)
|
||||
self.assertIn(kick["event_id"], self._state_ids(response, use_state_after=True))
|
||||
|
||||
# The client reloads from the older persisted token.
|
||||
response = self._sync(use_state_after=True, since=t0)
|
||||
self.assertIn(kick["event_id"], self._timeline_ids(response))
|
||||
self.assertIn(
|
||||
kick["event_id"],
|
||||
self._state_ids(response, use_state_after=True),
|
||||
f"membership change missing from state_after on re-sync from an "
|
||||
f"older token: {response}",
|
||||
)
|
||||
|
||||
def test_gappy_membership_change_resent_on_resync_from_older_token(
|
||||
self,
|
||||
) -> None:
|
||||
"""The same for `state`. A membership change that falls in the gap of
|
||||
a limited timeline is only conveyed by `state`, so it must be there
|
||||
every time the sync window covers it."""
|
||||
t0 = self._sync(use_state_after=False, timeline_limit=1)["next_batch"]
|
||||
|
||||
self.helper.send(self.room_id, body="hello", tok=self.bob_tok)
|
||||
t1 = self._sync(use_state_after=False, since=t0, timeline_limit=1)["next_batch"]
|
||||
|
||||
kick = self.helper.change_membership(
|
||||
self.room_id, self.alice, self.bob, "leave", tok=self.alice_tok
|
||||
)
|
||||
# A later message, so that the kick falls into the gap of the
|
||||
# one-event timeline.
|
||||
self.helper.send(self.room_id, body="after", tok=self.alice_tok)
|
||||
|
||||
# The live client sees the kick in `state`.
|
||||
response = self._sync(use_state_after=False, since=t1, timeline_limit=1)
|
||||
self.assertTrue(self._room(response)["timeline"]["limited"])
|
||||
self.assertNotIn(kick["event_id"], self._timeline_ids(response))
|
||||
self.assertIn(
|
||||
kick["event_id"], self._state_ids(response, use_state_after=False)
|
||||
)
|
||||
|
||||
# The client reloads from the older persisted token.
|
||||
response = self._sync(use_state_after=False, since=t0, timeline_limit=1)
|
||||
self.assertNotIn(kick["event_id"], self._timeline_ids(response))
|
||||
self.assertIn(
|
||||
kick["event_id"],
|
||||
self._state_ids(response, use_state_after=False),
|
||||
f"membership change missing from state on re-sync from an older "
|
||||
f"token: {response}",
|
||||
)
|
||||
|
||||
@parameterized.expand([("state", False), ("state_after", True)])
|
||||
def test_member_not_resent_after_empty_long_poll_wakeup(
|
||||
self, _: str, use_state_after: bool
|
||||
) -> None:
|
||||
"""A long-poll that wakes up with nothing to send keeps waiting. The
|
||||
response it eventually returns must still leave out the members the
|
||||
client already has.
|
||||
|
||||
The notifier calls back into the sync handler on every wakeup within
|
||||
one request. An empty result is not a response the client lost.
|
||||
"""
|
||||
not_types = ["m.spurious"]
|
||||
t0 = self._sync(use_state_after, timeline_not_types=not_types)["next_batch"]
|
||||
|
||||
self.helper.send(self.room_id, body="one", tok=self.bob_tok)
|
||||
response = self._sync(use_state_after, t0, timeline_not_types=not_types)
|
||||
self.assertEqual(self._members_in_state(response, use_state_after), [self.bob])
|
||||
t1 = response["next_batch"]
|
||||
|
||||
channel = self._sync_request(
|
||||
use_state_after,
|
||||
t1,
|
||||
timeline_not_types=not_types,
|
||||
timeout_ms=30000,
|
||||
await_result=False,
|
||||
)
|
||||
|
||||
# Wakes the long-poll, but the filter drops the event, so the result is
|
||||
# empty and the notifier waits again.
|
||||
self.helper.send_event(
|
||||
self.room_id, type="m.spurious", content={}, tok=self.bob_tok
|
||||
)
|
||||
self.assertFalse(channel.is_finished())
|
||||
|
||||
self.helper.send(self.room_id, body="two", tok=self.bob_tok)
|
||||
channel.await_result()
|
||||
self.assertEqual(channel.code, 200, channel.result)
|
||||
response = channel.json_body
|
||||
self.assertEqual(len(self._timeline_ids(response)), 1)
|
||||
self.assertEqual(self._members_in_state(response, use_state_after), [])
|
||||
|
||||
@parameterized.expand([("state", False), ("state_after", True)])
|
||||
def test_membership_change_sent_when_cache_claims_it_was(
|
||||
self, _: str, use_state_after: bool
|
||||
) -> None:
|
||||
"""A membership change since `since` is sent even when the cache says
|
||||
the client already has it.
|
||||
|
||||
The cache only decides whether to leave out the memberships fetched for
|
||||
timeline senders. It can be wrong: a retry of a request can run while
|
||||
the original is still being built, and the original then marks members
|
||||
the retry has to send. A change must never depend on it.
|
||||
"""
|
||||
t0 = self._sync(use_state_after, timeline_limit=1)["next_batch"]
|
||||
|
||||
kick = self.helper.change_membership(
|
||||
self.room_id, self.alice, self.bob, "leave", tok=self.alice_tok
|
||||
)
|
||||
# A later message, so that the kick falls into the gap of the
|
||||
# one-event timeline and only `state` can carry it.
|
||||
self.helper.send(self.room_id, body="after the kick", tok=self.alice_tok)
|
||||
|
||||
# Claim the kick was already sent in the response that ended at `t0`.
|
||||
# Syncing from `t0` does not forget this, as the client has acknowledged
|
||||
# that response.
|
||||
t0_token = self.get_success(
|
||||
StreamToken.from_string(self.hs.get_datastores().main, t0)
|
||||
)
|
||||
cache = self.hs.get_sync_handler().get_lazy_loaded_members_cache(
|
||||
(self.alice, self.alice_device)
|
||||
)
|
||||
cache.mark_sent(self.bob, kick["event_id"], t0_token)
|
||||
|
||||
response = self._sync(use_state_after, t0, timeline_limit=1)
|
||||
self.assertNotIn(kick["event_id"], self._timeline_ids(response))
|
||||
self.assertIn(kick["event_id"], self._state_ids(response, use_state_after))
|
||||
Reference in new issue
Block a user