mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-07 05:09:45 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7843051ac |
@@ -1 +0,0 @@
|
||||
Preserve all users' read receipts when batching room receipt updates.
|
||||
@@ -1 +0,0 @@
|
||||
Updated admin command names (list-users -> list, list-rooms -> list, and reload-mods -> reload), old names are now aliases. Contributed by @PerformativeJade
|
||||
+2
-21
@@ -455,28 +455,9 @@
|
||||
#
|
||||
#sender_idle_timeout = 180
|
||||
|
||||
# Federation sender retry backoff base (seconds).
|
||||
# Federation sender transaction retry backoff limit (seconds).
|
||||
#
|
||||
# This period will be doubled for each failed federation request until
|
||||
# either the remote server becomes healthy, or the value is clamped to
|
||||
# `sender_retry_backoff_limit`.
|
||||
#
|
||||
#sender_retry_backoff_base = 60
|
||||
|
||||
# Federation sender retry backoff limit (seconds).
|
||||
#
|
||||
# Defaults to one week. Requests will never stop being retried if their
|
||||
# backoff period exceeds this value, however the maximum amount of time
|
||||
# between each request will instead be clamped at this value.
|
||||
#
|
||||
# The backoff period is reset if a successful request is made, or
|
||||
# continuwuity receives a request from the server that is being backed off
|
||||
# from.
|
||||
#
|
||||
# It is not recommended to lower this value below 48 hours or above
|
||||
# 1 year.
|
||||
#
|
||||
#sender_retry_backoff_limit = 806400
|
||||
#sender_retry_backoff_limit = 86400
|
||||
|
||||
# Appservice URL request connection timeout. Defaults to 35 seconds as
|
||||
# generally appservices are hosted within the same network.
|
||||
|
||||
@@ -4,7 +4,7 @@ # `!admin rooms`
|
||||
Commands for managing rooms
|
||||
|
||||
|
||||
## `!admin rooms list`
|
||||
## `!admin rooms list-rooms`
|
||||
|
||||
List all rooms the server knows about
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ ## `!admin server admin-notice`
|
||||
|
||||
Send a message to the admin room
|
||||
|
||||
## `!admin server reload`
|
||||
## `!admin server reload-mods`
|
||||
|
||||
Hot-reload the server
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ # `!admin users`
|
||||
Commands for managing local users
|
||||
|
||||
|
||||
## `!admin users create`
|
||||
## `!admin users create-user`
|
||||
|
||||
Create a new user
|
||||
|
||||
@@ -92,7 +92,7 @@ ## `!admin users disable-login`
|
||||
|
||||
Disables login for the specified user without deactivating or locking their account. This prevents the user from obtaining new access tokens, but does not invalidate existing sessions.
|
||||
|
||||
## `!admin users list`
|
||||
## `!admin users list-users`
|
||||
|
||||
List local users in the database
|
||||
|
||||
|
||||
Generated
+110
-126
@@ -16,21 +16,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
|
||||
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"@emnapi/wasi-threads": "1.2.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
||||
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
|
||||
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -39,9 +39,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
|
||||
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -125,13 +125,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rsbuild/core": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.1.5.tgz",
|
||||
"integrity": "sha512-7TW4U1SH7VxQZzSTIOzvwj5lo9uNTmGpsmTXFr4axQAE4giLDKP3kVUA1ZW4P3/Mz4QQJJyvZP29mVcb8kZCfg==",
|
||||
"version": "2.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.1.10.tgz",
|
||||
"integrity": "sha512-lwxC5w88U2AMv6aNwG3VH7+AV33N4JJQjD//egVWFsMbV+OE/bnfCsurSpX8s3lGyRYkJMwUVN+YXMbmmYZFfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rspack/core": "~2.1.3",
|
||||
"@rspack/core": "~2.1.8",
|
||||
"@swc/helpers": "^0.5.23"
|
||||
},
|
||||
"bin": {
|
||||
@@ -169,30 +169,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.1.3.tgz",
|
||||
"integrity": "sha512-4UGXJqUHmm36tWG1GgFZz3p8sQ5JuSgWI+gq1xPPoihS41uYJL3cXuJnirTeWsmVrusmYZTQhgU3tl3VYGcJYg==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.1.8.tgz",
|
||||
"integrity": "sha512-tmAyHzDbPiy8V7HvQqtuPsbs6dPgwV0YjzW5XrPRV9gzf+Hdm7pvsZJKE1QKO9WV5RuvGYav98xIX6O+abZxzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"@rspack/binding-darwin-arm64": "2.1.3",
|
||||
"@rspack/binding-darwin-x64": "2.1.3",
|
||||
"@rspack/binding-linux-arm64-gnu": "2.1.3",
|
||||
"@rspack/binding-linux-arm64-musl": "2.1.3",
|
||||
"@rspack/binding-linux-riscv64-gnu": "2.1.3",
|
||||
"@rspack/binding-linux-riscv64-musl": "2.1.3",
|
||||
"@rspack/binding-linux-x64-gnu": "2.1.3",
|
||||
"@rspack/binding-linux-x64-musl": "2.1.3",
|
||||
"@rspack/binding-wasm32-wasi": "2.1.3",
|
||||
"@rspack/binding-win32-arm64-msvc": "2.1.3",
|
||||
"@rspack/binding-win32-ia32-msvc": "2.1.3",
|
||||
"@rspack/binding-win32-x64-msvc": "2.1.3"
|
||||
"@rspack/binding-darwin-arm64": "2.1.8",
|
||||
"@rspack/binding-darwin-x64": "2.1.8",
|
||||
"@rspack/binding-linux-arm64-gnu": "2.1.8",
|
||||
"@rspack/binding-linux-arm64-musl": "2.1.8",
|
||||
"@rspack/binding-linux-riscv64-gnu": "2.1.8",
|
||||
"@rspack/binding-linux-riscv64-musl": "2.1.8",
|
||||
"@rspack/binding-linux-x64-gnu": "2.1.8",
|
||||
"@rspack/binding-linux-x64-musl": "2.1.8",
|
||||
"@rspack/binding-wasm32-wasi": "2.1.8",
|
||||
"@rspack/binding-win32-arm64-msvc": "2.1.8",
|
||||
"@rspack/binding-win32-ia32-msvc": "2.1.8",
|
||||
"@rspack/binding-win32-x64-msvc": "2.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding-darwin-arm64": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.1.3.tgz",
|
||||
"integrity": "sha512-oOGI0RSL89Ehu9T22rugmfUY9OC2eBqLMeWRYsu7bhlUrjoXeVfGBBSEXCse666BQ1sAiM8hD/k7nqVria/okQ==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.1.8.tgz",
|
||||
"integrity": "sha512-kia+eWtyWPvR4ntg1bWYoVU8nLPbUg2fG3zgBEocsTcsh5ZENSiEPxEKymDgMyIMONUqj611E0775cdUBoNmqw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -204,9 +204,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-darwin-x64": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.1.3.tgz",
|
||||
"integrity": "sha512-sVqWXNiFTMXAyN362y6IA+eJc8LXZKfHdhEJ/zDuMmRp+u2IvhgaF8tk3vX/OmeB9jydVjySijuiqk8No/FoCA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.1.8.tgz",
|
||||
"integrity": "sha512-08pBkFhlD3Y3Qzh94w/Fc3skaIE3e96kl2P14m8+tnYTcglpOfpA2OwS3iHt9fOqy0HjoAVe6/MW3cBgs5iabA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -218,9 +218,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.1.3.tgz",
|
||||
"integrity": "sha512-aCy9Zli/2Qf+Ee5otXfFQ6mhv5fEyn0wIoBVmouqtJoqOO21et6UTtJ+LHLsMDolwGLyHERAljeSFSmYX3/O5A==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.1.8.tgz",
|
||||
"integrity": "sha512-KLniMc9GzhKpVqhPzaJo3KJwzdAllXVVqZIk/uL1QipXOxs57fgM4u7IexKPFVla0o/u1PQG/Ah2YLDmda24Ow==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -235,9 +235,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-arm64-musl": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.1.3.tgz",
|
||||
"integrity": "sha512-flIE7eluz0d21Fn28EVm3vPwoJooOSqtmjLFVSuOMcoCbwV9clfor195oIrAppp/W7dL/3XquFuVfrsa01Jy8Q==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.1.8.tgz",
|
||||
"integrity": "sha512-yUKAxHNGnICtw5RnxFWu4dHtsz/tdt7rbeFcsINNVre9HcrRxf5XP+FbOGL/SMxd9oM9XCo10paU2WckTKwbEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -252,9 +252,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-riscv64-gnu": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-2.1.3.tgz",
|
||||
"integrity": "sha512-yojg8elye1nhsNeGncw0NrZ4pMGF7NVebR80CLg72TXyzfYwFJlFTdU5yUYb1Gy+JXIvrSCwzQt2QkyiEvmkfg==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-2.1.8.tgz",
|
||||
"integrity": "sha512-gg4S1jaitwYPHR9HZ3zNGH1EK2GXINm66p4kEpOP1gbc+akyOouVF/dMcu9NGPlRg58FbEhVRZYKu7Z/zcpKHg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -269,9 +269,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-riscv64-musl": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-2.1.3.tgz",
|
||||
"integrity": "sha512-6PvbXb3FOK4X3S5QGvoSW/sqExmsvAoPnQ/YSFrXvTphkXFezA7wnobmGBHT8JQP31hcFRzJHIZSIOKktzSzzg==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-2.1.8.tgz",
|
||||
"integrity": "sha512-b/aU5j1h368SLNyz5u+flqpZVhzSZ1UIslaj9sZJuAvqkGWv3xsjc/28/PTo/RYXCxd0FNVAxTxWHKvRiAAS8w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -286,9 +286,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-x64-gnu": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.1.3.tgz",
|
||||
"integrity": "sha512-lMXjoGKf0SnviH596fmTszgtnXLHmWOoE90G8grG9MvKVa3pelRmfps5ewZL9s8ENf3NXRfOxIhIf/M9as6MqA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.1.8.tgz",
|
||||
"integrity": "sha512-EyegohSx0BJRqieCg9f/caCqFARRWkqI5hwJt6k530MoOTLeq8I3vsbeg24/2MktwIC1dmJi8bl0+WhPKQs4eQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -303,9 +303,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-linux-x64-musl": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.1.3.tgz",
|
||||
"integrity": "sha512-0esT35v7pW2ZsJTMc/zDUHwpNXlPqyUCyuiLJvrAAUcdENrgOVe4DmFrgVJ2hwqI4GjeN1VBnGRJ8c+edAHH5w==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.1.8.tgz",
|
||||
"integrity": "sha512-I6E+goN+UQ297q4r1qdbiAyNCI3t0+a5Y0xDIAPOZfRDRxDTnH/LF8/y65gjsJoKRKyn7zxRC0T/NURTkRNQ9A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -320,9 +320,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-wasm32-wasi": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.1.3.tgz",
|
||||
"integrity": "sha512-UsrDjD59UEP0mhfN/Z+uTc3vLgiUvIr+mn92WC1sbQi9gtZohTYvaQYFhWuMkBqsACGcmZp704JAbbSrVrVYCA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.1.8.tgz",
|
||||
"integrity": "sha512-om7GAKWAU3lcSvbCon2m7mzw8v9OTrO2LW2MZ1lGe/uVJJmwGGkl9HVoXFyWFLrN6YVFyx8iP+AkN4owDWB9Cw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -330,15 +330,15 @@
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@emnapi/core": "1.11.3",
|
||||
"@emnapi/runtime": "1.11.3",
|
||||
"@napi-rs/wasm-runtime": "1.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@rspack/binding-win32-arm64-msvc": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.1.3.tgz",
|
||||
"integrity": "sha512-42RS/SwKBTkNvXIPZXqTn0yEFN8zwGRfRe/ly0GDvPp/KxpLMFWxJmLxgtoLompU+0UCGvV4KpcBENt3oWs6BA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.1.8.tgz",
|
||||
"integrity": "sha512-WDnsP/SUb9zbxyGX9XjPw5AXrX86u5oidn0MDdfJduOOqdCSpHwmRjlQ8NUJhbBq9WqVJMFlcab7NwZVWX/yyg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -350,9 +350,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-win32-ia32-msvc": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.1.3.tgz",
|
||||
"integrity": "sha512-thk43H1JHHbetNF3txcsGXeOwZi2m6Luf/uVUqNORXjxr5VV8woHAgaAx4QXVZRg70z1VDjd8mBWnHBnKI0FGA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.1.8.tgz",
|
||||
"integrity": "sha512-QiMQMPNDiY3dhhaIdaFPzcPDC06cEYkNY89ea+EmDvNVgZq6V+2mFS/WnzZVMeEbGAYJCjsv/ABhhLT1hlYMvg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -364,9 +364,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/binding-win32-x64-msvc": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.1.3.tgz",
|
||||
"integrity": "sha512-ObaUcj+BHo/aBL1weyM3orApaHyAR5Phr3YNEpQEifxjZbNKM1iO5X5prN4OqEv3H+7o5e/Wh9Fy3N7Vvd+psA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.1.8.tgz",
|
||||
"integrity": "sha512-b7sA5eB64vo2mbsuc//MOYzVLeCKHPn0dfP/GmNEoHdWbhRgZ/orZLWurYMQj04ELTLW6YCJEy59g5KRzNYHfw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -378,13 +378,13 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rspack/core": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.1.3.tgz",
|
||||
"integrity": "sha512-1iGnxLrP+iyY0ZSjLZeQTaxrISpQz4yLyqJPmaF/l4uw27/dBcrloszAeLOJQ9jiv7EJtU0T5zB+LOFPpivIxA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.1.8.tgz",
|
||||
"integrity": "sha512-na1kyA6Mj8/LWw9O3A8NsrG9rNKN3Iq2WiXrEuIwsU5r/Nl/evm3hO7bWKHxgsRyydI6W7okwx3MXgf8rzel6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rspack/binding": "2.1.3"
|
||||
"@rspack/binding": "2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
@@ -419,20 +419,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/core": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.18.tgz",
|
||||
"integrity": "sha512-DBpsr/6XAItQkZPL5FSjthLGuzdG9ks/7EOxkqaaXp93uAT54LO0PD5Dkoy6ydup1PnvkpHyiHpUyDcnHHTVhg==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.19.tgz",
|
||||
"integrity": "sha512-m0KfQNNXxjP975652nIOwUTqrvbJapRp4NpnoQMc5heawGhkC4+F7zTHvxY+huvzGYnKtL2fMMTrgWhEjKkrug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mdx-js/mdx": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@rsbuild/core": "^2.1.5",
|
||||
"@rsbuild/core": "^2.1.8",
|
||||
"@rsbuild/plugin-react": "~2.1.0",
|
||||
"@rspress/shared": "2.0.18",
|
||||
"@rspress/shared": "2.0.19",
|
||||
"@shikijs/rehype": "^4.2.0",
|
||||
"@types/unist": "^3.0.3",
|
||||
"@unhead/react": "^2.1.15",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/react": "^19.2.17",
|
||||
"@unhead/react": "^2.1.16",
|
||||
"body-scroll-lock": "4.0.0-beta.0",
|
||||
"clsx": "2.1.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
@@ -443,10 +444,9 @@
|
||||
"mdast-util-mdxjs-esm": "^2.0.1",
|
||||
"medium-zoom": "1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-lazy-with-preload": "^2.2.1",
|
||||
"react-reconciler": "0.33.0",
|
||||
"react-render-to-markdown": "19.1.0",
|
||||
"react-router-dom": "^7.18.1",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
@@ -460,9 +460,9 @@
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"shiki": "^4.2.0",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-remove": "^4.0.0",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"unist-util-visit-children": "^3.0.0"
|
||||
"unist-util-visit-children": "^3.0.0",
|
||||
"vfile": "^6.0.3"
|
||||
},
|
||||
"bin": {
|
||||
"rspress": "bin/rspress.js"
|
||||
@@ -472,9 +472,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/plugin-client-redirects": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.18.tgz",
|
||||
"integrity": "sha512-q70ufN0S125kl0QlucO7JuiZ0WJaxFOtPPoGu7eWwam9fb+i4YsWnOWtAKSKGH4JP5+897mgpDPP+dmfl9DPCw==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.19.tgz",
|
||||
"integrity": "sha512-BUwgEyj8vCkU58cV3Gp97aTSnfGJm7aXh40K0Dot1BqsCQ8ekdsMuaLXrICROdP9/ctd4oALtLBkOE+PKTa4Sg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -485,9 +485,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/plugin-sitemap": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.18.tgz",
|
||||
"integrity": "sha512-XfaXzZx+ASN9OMTc0fOvW8QmbmyCyDWkO8xJj4XF4E0YJtsu7j/65+v8/KXXdwpywOfUIQ6M94/meRTy2urNmQ==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.19.tgz",
|
||||
"integrity": "sha512-9ydrpj9hmmXWKWKIXUqJ1UrfxSvfxhHZRBZLb5OkTlOuQBNAg6BpsrW1WHoJqJr7p+3zqlz7S/9HTzSA3+EYcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -498,14 +498,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rspress/shared": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.18.tgz",
|
||||
"integrity": "sha512-GJswqJQCPSxvBt5r+gJzz8Em8EEK/sOmCQjTpemTvldvxr1Lva85BGVnSQZOgofnM0nrjt18kn4mmnuuSstbpA==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.19.tgz",
|
||||
"integrity": "sha512-INrETllWuR49lksqCz+xeLSO6rKtHA+5Ix2YQWcP9nerCuTSyGYwH8eBC0HrIacJkGHxB8wkkbV99tyzGtY8Wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rsbuild/core": "^2.1.5",
|
||||
"@rsbuild/core": "^2.1.8",
|
||||
"@shikijs/rehype": "^4.2.0",
|
||||
"@types/react": "^19.2.17",
|
||||
"mdast-util-mdx-jsx": "^3.2.0",
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
},
|
||||
@@ -723,7 +725,6 @@
|
||||
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1083,13 +1084,13 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@unhead/react": {
|
||||
"version": "2.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.15.tgz",
|
||||
"integrity": "sha512-5hfAaZ3XJq9JkspRzZdSPsMrXXA8v/SKiEOxZcN9L40o44byF/50bcQuOLgSSCAx8802mI5VG32KZXWTtsLu9Q==",
|
||||
"version": "2.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.17.tgz",
|
||||
"integrity": "sha512-KmcYksDjlLozL0fxUjIDwH/0k6+Lg2HWHLUPlkRW8Gl08hdB1AbLB5iU9QxFvkaT8p0Q4dFEqZWnz3g6RMeiRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"unhead": "2.1.15"
|
||||
"unhead": "2.1.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
@@ -1272,8 +1273,7 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
@@ -3176,9 +3176,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"version": "19.2.8",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3186,16 +3186,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"version": "19.2.8",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.7"
|
||||
"react": "^19.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-lazy-with-preload": {
|
||||
@@ -3736,9 +3736,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unhead": {
|
||||
"version": "2.1.15",
|
||||
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz",
|
||||
"integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==",
|
||||
"version": "2.1.17",
|
||||
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.17.tgz",
|
||||
"integrity": "sha512-HLMKXOszRhAPBrr6VlqCeVeJq2kbC4kXwzGLEZvvojPLWNYTJw22xG7Bfwhsvs31+IBet3Wl8ADg9dwYdyphfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3810,22 +3810,6 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-remove": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz",
|
||||
"integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0",
|
||||
"unist-util-visit-parents": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-stringify-position": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum RoomCommand {
|
||||
/// List all rooms the server knows about
|
||||
#[command(name = "list", alias = "list-rooms")]
|
||||
#[clap(alias = "list")]
|
||||
ListRooms {
|
||||
page: Option<usize>,
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum ServerCommand {
|
||||
},
|
||||
|
||||
/// Hot-reload the server
|
||||
#[command(name = "reload", alias = "reload-mods")]
|
||||
#[clap(alias = "reload")]
|
||||
ReloadMods,
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum UserCommand {
|
||||
/// Create a new user
|
||||
#[command(name = "create", alias = "create-user")]
|
||||
#[clap(alias = "create")]
|
||||
CreateUser {
|
||||
/// Username of the new user
|
||||
username: String,
|
||||
@@ -163,7 +163,7 @@ pub enum UserCommand {
|
||||
},
|
||||
|
||||
/// List local users in the database
|
||||
#[command(name = "list", alias = "list-users")]
|
||||
#[clap(alias = "list")]
|
||||
ListUsers,
|
||||
|
||||
/// Lists all the rooms (local and remote) that the specified user is
|
||||
|
||||
@@ -149,9 +149,6 @@ async fn verify<B: AsRef<[u8]> + Sync>(
|
||||
)));
|
||||
}
|
||||
|
||||
// Ping the server as healthy
|
||||
services.federation.mark_healthy(&output.origin);
|
||||
|
||||
Ok(output.origin)
|
||||
},
|
||||
| Err(err) =>
|
||||
|
||||
+2
-25
@@ -573,30 +573,9 @@ pub struct Config {
|
||||
#[serde(default = "default_sender_idle_timeout")]
|
||||
pub sender_idle_timeout: u64,
|
||||
|
||||
/// Federation sender retry backoff base (seconds).
|
||||
/// Federation sender transaction retry backoff limit (seconds).
|
||||
///
|
||||
/// This period will be doubled for each failed federation request until
|
||||
/// either the remote server becomes healthy, or the value is clamped to
|
||||
/// `sender_retry_backoff_limit`.
|
||||
///
|
||||
/// default: 60
|
||||
#[serde(default = "default_sender_retry_backoff_base")]
|
||||
pub sender_retry_backoff_base: u64,
|
||||
|
||||
/// Federation sender retry backoff limit (seconds).
|
||||
///
|
||||
/// Defaults to one week. Requests will never stop being retried if their
|
||||
/// backoff period exceeds this value, however the maximum amount of time
|
||||
/// between each request will instead be clamped at this value.
|
||||
///
|
||||
/// The backoff period is reset if a successful request is made, or
|
||||
/// continuwuity receives a request from the server that is being backed off
|
||||
/// from.
|
||||
///
|
||||
/// It is not recommended to lower this value below 48 hours or above
|
||||
/// 1 year.
|
||||
///
|
||||
/// default: 806400
|
||||
/// default: 86400
|
||||
#[serde(default = "default_sender_retry_backoff_limit")]
|
||||
pub sender_retry_backoff_limit: u64,
|
||||
|
||||
@@ -2833,8 +2812,6 @@ fn default_sender_timeout() -> u64 { 180 }
|
||||
|
||||
fn default_sender_idle_timeout() -> u64 { 180 }
|
||||
|
||||
fn default_sender_retry_backoff_base() -> u64 { 60 }
|
||||
|
||||
fn default_sender_retry_backoff_limit() -> u64 { 86400 }
|
||||
|
||||
fn default_appservice_timeout() -> u64 { 35 }
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
string::{str_from_bytes, string_from_bytes},
|
||||
sys::compute::available_parallelism,
|
||||
time::{
|
||||
exponential_backoff::should_continue_backoff, now_millis as millis_since_unix_epoch,
|
||||
timepoint_ago, timepoint_from_now,
|
||||
exponential_backoff::{continue_exponential_backoff, continue_exponential_backoff_secs},
|
||||
now_millis as millis_since_unix_epoch, timepoint_ago, timepoint_from_now,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,23 +1,38 @@
|
||||
use std::time::Duration;
|
||||
use std::{cmp, time::Duration};
|
||||
|
||||
/// Returns false if the backoff interval has expired based on the inputs,
|
||||
/// meaning the operation should be retried.
|
||||
/// Returns false if the exponential backoff has expired based on the inputs
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn should_continue_backoff(
|
||||
pub fn continue_exponential_backoff_secs(
|
||||
min: u64,
|
||||
max: u64,
|
||||
elapsed: Duration,
|
||||
tries: u32,
|
||||
) -> bool {
|
||||
let min = Duration::from_secs(min);
|
||||
let max = Duration::from_secs(max);
|
||||
continue_exponential_backoff(min, max, elapsed, tries)
|
||||
}
|
||||
|
||||
/// Returns false if the exponential backoff has expired based on the inputs
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn continue_exponential_backoff(
|
||||
min: Duration,
|
||||
max: Duration,
|
||||
elapsed: Duration,
|
||||
tries: u32,
|
||||
) -> bool {
|
||||
elapsed < next_interval(min, max, tries)
|
||||
let min = min.saturating_mul(tries).saturating_mul(tries);
|
||||
let min = cmp::min(min, max);
|
||||
elapsed < min
|
||||
}
|
||||
|
||||
/// Determines the interval that should be waited before retrying the operation
|
||||
/// using the algorithm: `(min * retries).min(max)`.
|
||||
/// Determines the minimum number of backoff seconds
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn next_interval(min: Duration, max: Duration, retries: u32) -> Duration {
|
||||
// TODO(nex): jitter?
|
||||
min.saturating_mul(retries).min(max)
|
||||
pub fn min_exp_backoff_duration(min: u64, max: u64, retries: u32) -> Duration {
|
||||
let min = Duration::from_secs(min)
|
||||
.saturating_mul(retries)
|
||||
.saturating_mul(retries);
|
||||
Duration::from_secs(max).min(min)
|
||||
}
|
||||
|
||||
@@ -131,8 +131,6 @@ pub async fn execute_on<'i, T, PathBuilderInput>(
|
||||
))));
|
||||
}
|
||||
|
||||
self.ensure_remote_is_healthy(dest)?;
|
||||
|
||||
let actual = self
|
||||
.services
|
||||
.client
|
||||
@@ -185,13 +183,8 @@ async fn perform<T>(
|
||||
| Ok(response) =>
|
||||
self.handle_response::<T>(dest, actual, &method, &url, response)
|
||||
.await,
|
||||
| Err(error) => {
|
||||
if error.is_connect() {
|
||||
debug_info!("{dest} is unhealthy due to a connect error");
|
||||
self.hit_unhealthy(dest.to_owned());
|
||||
}
|
||||
Err(handle_error(actual, &method, &url, error).expect_err("always returns error"))
|
||||
},
|
||||
| Err(error) =>
|
||||
Err(handle_error(actual, &method, &url, error).expect_err("always returns error")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +237,6 @@ async fn handle_response<T>(
|
||||
parts,
|
||||
body.as_ref(),
|
||||
))
|
||||
.inspect(|_| self.mark_healthy(dest))
|
||||
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
mod execute;
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
|
||||
use assign::assign;
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{
|
||||
Error, Result, Server, SyncRwLock, debug,
|
||||
utils::{math::Expected, millis_since_unix_epoch, time::exponential_backoff::next_interval},
|
||||
};
|
||||
use conduwuit::{Result, Server};
|
||||
pub(crate) use execute::FederationPathBuilderInput;
|
||||
use http::StatusCode;
|
||||
use ruma::{
|
||||
OwnedServerName, ServerName,
|
||||
api::error::{ErrorKind, LimitExceededErrorData, RetryAfter},
|
||||
};
|
||||
|
||||
use crate::{Dep, client, moderation, server_keys};
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
remote_health: SyncRwLock<HashMap<OwnedServerName, (u32, u64)>>,
|
||||
}
|
||||
|
||||
struct Services {
|
||||
@@ -29,7 +17,6 @@ struct Services {
|
||||
moderation: Dep<moderation::Service>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
@@ -39,103 +26,8 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
server_keys: args.depend::<server_keys::Service>("server_keys"),
|
||||
moderation: args.depend::<moderation::Service>("moderation"),
|
||||
},
|
||||
remote_health: SyncRwLock::new(HashMap::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
|
||||
async fn clear_cache(&self) {
|
||||
let mut map = self.remote_health.write();
|
||||
map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Checks if a remote is "healthy". "Healthy" is defined by either:
|
||||
///
|
||||
/// * The remote has not been marked as having a failed request, OR
|
||||
/// * The next retry timestamp is in the past
|
||||
pub fn is_healthy(&self, server_name: &ServerName) -> bool {
|
||||
let map = self.remote_health.read();
|
||||
let unix_now = millis_since_unix_epoch();
|
||||
if let Some((_, next_retry)) = map.get(server_name) {
|
||||
unix_now >= *next_retry
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how long the server should wait before attempting to contact the
|
||||
/// remote again.
|
||||
pub fn retry_after(&self, server_name: &ServerName) -> Option<Duration> {
|
||||
let map = self.remote_health.read();
|
||||
let unix_now = millis_since_unix_epoch();
|
||||
map.get(server_name)
|
||||
.map(|(_, next_retry)| Duration::from_millis((*next_retry).expected_sub(unix_now)))
|
||||
}
|
||||
|
||||
/// Marks or updates a remote's health status as unhealthy. If the remote is
|
||||
/// not already marked as unhealthy, a new entry is created. Otherwise, the
|
||||
/// retry count is incremented and
|
||||
pub fn hit_unhealthy(&self, server_name: OwnedServerName) {
|
||||
let unix_now = millis_since_unix_epoch();
|
||||
let mut map = self.remote_health.write();
|
||||
let sn2 = server_name.clone(); // for logging since map.entry() moves
|
||||
let (retries, next_retry) = map.entry(server_name).or_default();
|
||||
if *next_retry > unix_now {
|
||||
// Don't update the retry marker if we are already in a backoff
|
||||
// period. This prevents the backoff skyrocketing if multiple
|
||||
// concurrent or closely-related requests fail and consequently try
|
||||
// to mark as offline.
|
||||
return;
|
||||
}
|
||||
|
||||
let min = Duration::from_secs(self.services.server.config.sender_retry_backoff_base);
|
||||
let max = Duration::from_secs(self.services.server.config.sender_retry_backoff_limit);
|
||||
|
||||
*retries = retries.saturating_add(1);
|
||||
*next_retry = unix_now.saturating_add(
|
||||
u64::try_from(next_interval(min, max, *retries).as_millis())
|
||||
.expect("backoff milliseconds should not exceed u64::MAX"),
|
||||
);
|
||||
debug!(
|
||||
"{} is (now) unhealthy ({} retries, blocked until: {})",
|
||||
sn2, *retries, *next_retry
|
||||
);
|
||||
}
|
||||
|
||||
/// Marks a server as "healthy" by removing it from the health map.
|
||||
///
|
||||
/// TODO: flush senders too
|
||||
pub fn mark_healthy(&self, server_name: &ServerName) {
|
||||
// TODO: We need to make sure the sender flush DOESN'T trigger if this is called
|
||||
// by the senders themselves.
|
||||
let mut map = self.remote_health.write();
|
||||
if map.remove(server_name).is_some() {
|
||||
debug!("{} is now healthy", server_name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a rate-limited error if the remote is unhealthy.
|
||||
fn ensure_remote_is_healthy(&self, server_name: &ServerName) -> Result<()> {
|
||||
if self.is_healthy(server_name) {
|
||||
Ok(())
|
||||
} else {
|
||||
let retry_after = self
|
||||
.retry_after(server_name)
|
||||
.expect("remote is unhealthy and must have an accompanying retry timestamp");
|
||||
Err(Error::Request(
|
||||
ErrorKind::LimitExceeded(assign!(LimitExceededErrorData::new(), {
|
||||
retry_after: Some(RetryAfter::Delay(retry_after)),
|
||||
})),
|
||||
format!(
|
||||
"Remote server is currently unhealthy (not retrying for another {} seconds)",
|
||||
retry_after.as_secs()
|
||||
)
|
||||
.into(),
|
||||
StatusCode::TOO_MANY_REQUESTS,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,21 +152,15 @@ pub fn pack_receipts<I>(receipts: I) -> Raw<SyncEphemeralRoomEvent<ReceiptEventC
|
||||
where
|
||||
I: Iterator<Item = Raw<AnySyncEphemeralRoomEvent>>,
|
||||
{
|
||||
let mut json: BTreeMap<OwnedEventId, Receipts> = BTreeMap::new();
|
||||
let mut json = BTreeMap::new();
|
||||
for value in receipts {
|
||||
let receipt = serde_json::from_str::<SyncEphemeralRoomEvent<ReceiptEventContent>>(
|
||||
value.json().get(),
|
||||
);
|
||||
match receipt {
|
||||
| Ok(value) =>
|
||||
for (event, receipt_types) in value.content {
|
||||
let event_receipts = json.entry(event).or_default();
|
||||
for (receipt_type, users) in receipt_types {
|
||||
event_receipts
|
||||
.entry(receipt_type)
|
||||
.or_default()
|
||||
.extend(users);
|
||||
}
|
||||
for (event, receipt) in value.content {
|
||||
json.insert(event, receipt);
|
||||
},
|
||||
| _ => {
|
||||
debug!("failed to parse receipt: {:?}", receipt);
|
||||
@@ -181,46 +175,3 @@ pub fn pack_receipts<I>(receipts: I) -> Raw<SyncEphemeralRoomEvent<ReceiptEventC
|
||||
.expect("received valid json"),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::{
|
||||
events::receipt::{Receipt, ReceiptType},
|
||||
user_id,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn receipt(event_id: OwnedEventId, user_id: OwnedUserId) -> Raw<AnySyncEphemeralRoomEvent> {
|
||||
let content = ReceiptEventContent::from_iter([(
|
||||
event_id,
|
||||
BTreeMap::from([(
|
||||
ReceiptType::Read,
|
||||
BTreeMap::from([(user_id, Receipt::default())]),
|
||||
)]),
|
||||
)]);
|
||||
Raw::from_json(
|
||||
serde_json::value::to_raw_value(&SyncEphemeralRoomEvent::new(content))
|
||||
.expect("receipt event serializes"),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pack_receipts_keeps_users_reading_the_same_event() {
|
||||
let event_id: OwnedEventId = "$event:example.org".try_into().expect("valid event ID");
|
||||
let packed = pack_receipts(
|
||||
[
|
||||
receipt(event_id.clone(), user_id!("@alice:example.org").to_owned()),
|
||||
receipt(event_id.clone(), user_id!("@bob:example.org").to_owned()),
|
||||
]
|
||||
.into_iter(),
|
||||
);
|
||||
let packed: SyncEphemeralRoomEvent<ReceiptEventContent> =
|
||||
serde_json::from_str(packed.json().get()).expect("packed receipt is valid");
|
||||
|
||||
let users = &packed.content[&event_id][&ReceiptType::Read];
|
||||
assert_eq!(users.len(), 2);
|
||||
assert!(users.contains_key(user_id!("@alice:example.org")));
|
||||
assert!(users.contains_key(user_id!("@bob:example.org")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,14 @@
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use conduwuit::{
|
||||
debug_info, debug_warn, info,
|
||||
utils::{should_continue_backoff, time::exponential_backoff::next_interval},
|
||||
debug_info, debug_warn, info, utils::time::exponential_backoff::min_exp_backoff_duration,
|
||||
};
|
||||
use conduwuit_core::{
|
||||
Error, Event, Result, at, debug, err, error,
|
||||
matrix::pdu::sticky,
|
||||
result::LogErr,
|
||||
utils::{
|
||||
ReadyExt, calculate_hash,
|
||||
ReadyExt, calculate_hash, continue_exponential_backoff_secs,
|
||||
future::TryExtExt,
|
||||
stream::{BroadbandExt, IterStream, WidebandExt},
|
||||
},
|
||||
@@ -143,15 +142,7 @@ async fn handle_response<'a>(
|
||||
) {
|
||||
match response {
|
||||
| Ok(dest) => self.handle_response_ok(&dest, futures, statuses).await,
|
||||
| Err((dest, e)) => {
|
||||
if e.status_code().is_server_error()
|
||||
&& let Destination::Federation(dest) = &dest
|
||||
{
|
||||
debug!("{dest} is now unhealthy due to server error response: {e:?}");
|
||||
self.services.federation.hit_unhealthy(dest.clone());
|
||||
}
|
||||
Self::handle_response_err(dest, statuses, &e);
|
||||
},
|
||||
| Err((dest, e)) => Self::handle_response_err(dest, statuses, &e),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +171,6 @@ async fn handle_response_ok<'a>(
|
||||
) {
|
||||
let _cork = self.db.db.cork();
|
||||
self.db.delete_all_active_requests_for(dest).await;
|
||||
if let Destination::Federation(server_name) = dest {
|
||||
self.services.federation.mark_healthy(server_name);
|
||||
}
|
||||
|
||||
// Find events that have been added since starting the last request
|
||||
let new_events = self
|
||||
@@ -394,28 +382,18 @@ fn should_attempt_send(
|
||||
dest: &Destination,
|
||||
statuses: &mut CurTransactionStatus,
|
||||
) -> Result<(bool, bool)> {
|
||||
if let Destination::Federation(server_name) = dest {
|
||||
let status = statuses
|
||||
.entry(dest.clone())
|
||||
.or_insert(TransactionStatus::Running);
|
||||
let retry = matches!(status, TransactionStatus::Failed(_, _));
|
||||
return match self.services.federation.retry_after(server_name) {
|
||||
| None => Ok((true, retry)),
|
||||
| Some(t) => Ok((t.as_millis() > 0, retry)),
|
||||
};
|
||||
}
|
||||
|
||||
let (mut allow, mut retry) = (true, false);
|
||||
statuses
|
||||
.entry(dest.clone())
|
||||
.and_modify(|e| match e {
|
||||
| TransactionStatus::Failed(tries, time) => {
|
||||
let min = Duration::from_secs(self.server.config.sender_retry_backoff_base);
|
||||
let max = Duration::from_secs(self.server.config.sender_retry_backoff_limit);
|
||||
if should_continue_backoff(min, max, time.elapsed(), *tries)
|
||||
// Fail if a request has failed recently (exponential backoff)
|
||||
let min = self.server.config.sender_timeout;
|
||||
let max = self.server.config.sender_retry_backoff_limit;
|
||||
if continue_exponential_backoff_secs(min, max, time.elapsed(), *tries)
|
||||
&& !matches!(dest, Destination::Appservice(_))
|
||||
{
|
||||
let retry_after = next_interval(min, max, *tries);
|
||||
let retry_after = min_exp_backoff_duration(min, max, *tries);
|
||||
debug_warn!("Not retrying destination for another {retry_after:?}");
|
||||
allow = false;
|
||||
} else {
|
||||
@@ -468,8 +446,6 @@ async fn select_edus(&self, server_name: &ServerName) -> Result<(EduVec, u64)> {
|
||||
events.extend(presence.into_iter().flatten());
|
||||
events.extend(receipts.into_iter().flatten());
|
||||
|
||||
// TODO(nex): some EDUs like typing need flattening
|
||||
|
||||
Ok((events, max_edu_count.load(Ordering::Acquire)))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user