Skip to main content

max / makenotwork

3.2 KB · 81 lines History Blame Raw
1 //! Multithreaded's side of the `livechat` crate.
2 //!
3 //! The crate owns transport, fan-out, ordering and bounded retention, and knows
4 //! nothing about communities, memberships or bans. Everything app-specific
5 //! arrives through four traits, and this module is those four impls plus the
6 //! process-wide state they need. Design and the decisions behind it: wiki
7 //! `livechat-design`.
8 //!
9 //! <!-- wiki: livechat-design -->
10 //!
11 //! | Trait | Here | Backed by |
12 //! |---|---|---|
13 //! | `ChatRooms` | [`rooms`] | `communities.chat_policy` + state |
14 //! | `ChatAuthz` | [`authz`] | `evaluate_write_access`, memberships, session perks |
15 //! | `ChatIdentity` | [`identity`] | the `users` row, behind a bounded cache |
16 //! | `ChatModeration` | [`moderation`] | `community_bans` + `mod_log` |
17 //!
18 //! One room per community: `RoomId` is the community id, and there is no rooms
19 //! table. The routes over these impls live in `crate::routes::chat`.
20
21 use livechat::{Chat, ChatError, HubLimits, RateLimits};
22
23 pub mod authz;
24 pub mod identity;
25 pub mod moderation;
26 pub mod rooms;
27
28 pub use identity::IdentityCache;
29
30 /// Connection budget for this process.
31 ///
32 /// `MemoryMax=512M` is a hard cgroup cap on the unit and an OOM restarts the
33 /// whole site, not just chat, so these are an availability control rather than
34 /// tidiness. Each listener costs a task, a buffer and a broadcast receiver, and
35 /// the crate refuses a connection over budget with a 503 rather than
36 /// allocating and hoping.
37 const HUB_LIMITS: HubLimits = HubLimits {
38 // A handful of tabs. Not a spam control: without it one client can eat the
39 // global budget on its own.
40 max_connections_per_user: 4,
41 // The number that keeps the box alive. Well under what the cap would bear
42 // so chat cannot be the thing that OOMs a forum.
43 max_connections_total: 1_500,
44 // Messages buffered per room before a slow listener is told it fell behind
45 // and re-fetches from its cursor. Deep enough to absorb a busy moment,
46 // shallow enough that 1500 idle-ish connections are not carrying a large
47 // backlog each.
48 room_buffer: 256,
49 };
50
51 /// Send budget per (user, room).
52 ///
53 /// Deliberately not `USER_POST_RATE_LIMIT` (15 per 60s), which governs forum
54 /// posts and is far too tight for chat: it would throttle a person having a
55 /// normal conversation. Chat gets its own budget because it is a different
56 /// activity, not because the forum's number needs relaxing.
57 const RATE_LIMITS: RateLimits = RateLimits {
58 // Enough to paste a few lines in a row without being punished for it.
59 burst: 10,
60 // One every two seconds sustained. A person talking never meets this; a
61 // script is held to something a room and a 512M box can absorb.
62 sustain_per_min: 30,
63 };
64
65 /// Build this process's chat state.
66 pub fn new_chat() -> Chat {
67 Chat::new(HUB_LIMITS, RATE_LIMITS)
68 }
69
70 /// Wrap a host-side failure for the crate.
71 ///
72 /// One function so every impl reports the same way, and so a query failure
73 /// stays distinguishable from a user being refused: `ChatError::Host` is a
74 /// fault to log, while a `DenyReason` is expected traffic.
75 pub(crate) fn host_error<E>(err: E) -> ChatError
76 where
77 E: std::error::Error + Send + Sync + 'static,
78 {
79 ChatError::host(err)
80 }
81