1pub mod wire;
7
8use crate::clones;
9use crate::ctx::Ctx;
10use crate::events;
11use crate::lifecycle::log;
12use crate::names::format_duration;
13use crate::protocol::RouterStatus;
14use crate::state::{CloneRow, CloneStatus};
15use crate::templates::now;
16use serde_json::json;
17use std::collections::HashMap;
18use std::io;
19use std::net::SocketAddr;
20use std::sync::Arc;
21use std::time::Duration;
22use tokio::io::{AsyncReadExt, AsyncWriteExt};
23use tokio::net::{TcpListener, TcpStream};
24use tokio::sync::Notify;
25use tokio::task::JoinHandle;
26use wire::FirstMessage;
27
28const STARTUP_TIMEOUT: Duration = Duration::from_secs(60);
30const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
31const RETRY_MS: u64 = 60_000;
33
34pub struct RouterState {
36 pub conns: HashMap<String, usize>,
38 pub last_active: HashMap<String, i64>,
40 pub cancel: HashMap<(i32, Vec<u8>), String>,
42 pub status: RouterStatus,
43 bound: bool,
45 closers: HashMap<u64, (String, Arc<Notify>)>,
47 next_conn: u64,
48}
49
50impl RouterState {
51 pub fn new(port: u16) -> RouterState {
52 RouterState {
53 conns: HashMap::new(),
54 last_active: HashMap::new(),
55 cancel: HashMap::new(),
56 status: RouterStatus {
57 listening: false,
58 listen: String::new(),
59 port,
60 error: None,
61 },
62 bound: false,
63 closers: HashMap::new(),
64 next_conn: 0,
65 }
66 }
67
68 pub fn open_conns(&self, id: &str) -> usize {
69 self.conns.get(id).copied().unwrap_or(0)
70 }
71
72 pub fn last_active(&self, id: &str) -> Option<i64> {
73 self.last_active.get(id).copied()
74 }
75
76 pub fn close_clone(&mut self, id: &str) {
78 for (clone, close) in self.closers.values() {
79 if clone == id {
80 close.notify_one();
82 }
83 }
84 }
85}
86
87pub fn listening(ctx: &Ctx) -> bool {
91 ctx.config().router && {
92 let r = ctx.router();
93 r.bound || r.status.listening
94 }
95}
96
97pub fn warning(ctx: &Ctx) -> Option<String> {
99 if !ctx.config().router {
100 return None;
101 }
102 let error = ctx.router().status.error.clone()?;
103 Some(format!(
104 "the router is not listening ({error}); it retries every minute. Until then use the \
105 direct URL (mitosis env <clone> --direct, or direct_url); clones do not sleep \
106 meanwhile, and mitosis wake <clone> starts one that already sleeps"
107 ))
108}
109
110fn retry_interval() -> Duration {
111 let ms = std::env::var("MITOSIS_ROUTER_RETRY_MS")
112 .ok()
113 .and_then(|v| v.parse::<u64>().ok())
114 .filter(|ms| *ms > 0)
115 .unwrap_or(RETRY_MS);
116 Duration::from_millis(ms)
117}
118
119pub async fn bind(ctx: &Ctx) -> Option<TcpListener> {
124 let cfg = ctx.config();
125 let addr = SocketAddr::new(cfg.router_listen, cfg.router_port);
126 match TcpListener::bind(addr).await {
127 Ok(l) => {
128 let mut r = ctx.router();
129 r.bound = true;
130 r.status = RouterStatus {
131 listening: false,
132 listen: addr.ip().to_string(),
133 port: addr.port(),
134 error: None,
135 };
136 Some(l)
137 }
138 Err(e) => {
139 let error = format!("cannot listen on {addr}: {e}");
140 let first = {
141 let mut r = ctx.router();
142 r.bound = false;
143 let first = r.status.error.as_deref() != Some(error.as_str());
144 r.status = RouterStatus {
145 listening: false,
146 listen: addr.ip().to_string(),
147 port: addr.port(),
148 error: Some(error.clone()),
149 };
150 first
151 };
152 if first {
153 log(
154 ctx,
155 &format!(
156 "router: {error}; retrying every {}",
157 format_duration(retry_interval())
158 ),
159 );
160 events::emit(
161 ctx,
162 events::ROUTER_FAILED,
163 None,
164 None,
165 json!({ "listen": addr.to_string(), "error": e.to_string() }),
166 );
167 }
168 None
169 }
170 }
171}
172
173pub fn spawn(ctx: Arc<Ctx>, listener: Option<TcpListener>) -> JoinHandle<()> {
176 tokio::spawn(async move {
177 let mut listener = listener;
178 let listener = loop {
179 if let Some(l) = listener.take() {
180 break l;
181 }
182 tokio::time::sleep(retry_interval()).await;
183 listener = bind(&ctx).await;
184 };
185 ctx.router().status.listening = true;
186 let addr = listener
187 .local_addr()
188 .map_or_else(|_| "?".to_string(), |a| a.to_string());
189 log(&ctx, &format!("router listening on {addr}"));
190 events::emit(
191 &ctx,
192 events::ROUTER_STARTED,
193 None,
194 None,
195 json!({ "listen": addr }),
196 );
197 loop {
198 match listener.accept().await {
199 Ok((stream, _)) => {
200 tokio::spawn(connection(ctx.clone(), stream));
201 }
202 Err(_) => tokio::time::sleep(Duration::from_millis(100)).await,
204 }
205 }
206 })
207}
208
209async fn connection(ctx: Arc<Ctx>, mut client: TcpStream) {
211 let _ = client.set_nodelay(true);
212 let first = match tokio::time::timeout(STARTUP_TIMEOUT, first_message(&mut client)).await {
213 Ok(Ok(m)) => m,
214 _ => return,
215 };
216 match first {
217 FirstMessage::Ssl | FirstMessage::GssEnc => {}
218 FirstMessage::DirectTls => log(
219 &ctx,
220 "router: closed a direct TLS connection (no TLS through the router; use sslmode=disable or prefer)",
221 ),
222 FirstMessage::Cancel { pid, key } => {
223 let _ = cancel(&ctx, pid, key).await;
224 }
225 FirstMessage::Startup {
226 major,
227 minor,
228 params,
229 } => {
230 let _ = startup(ctx, client, major, minor, params).await;
231 }
232 }
233}
234
235async fn first_message(client: &mut TcpStream) -> io::Result<FirstMessage> {
237 let mut first = wire::read_first(client).await?;
238 for _ in 0..2 {
239 if !matches!(first, FirstMessage::Ssl | FirstMessage::GssEnc) {
240 break;
241 }
242 client.write_all(b"N").await?;
243 first = wire::read_first(client).await?;
244 }
245 Ok(first)
246}
247
248async fn cancel(ctx: &Ctx, pid: i32, key: Vec<u8>) -> io::Result<()> {
251 let Some(id) = ctx.router().cancel.get(&(pid, key.clone())).cloned() else {
252 return Ok(());
253 };
254 let Some(port) = ctx.store().get_clone(&id).ok().flatten().map(|r| r.port) else {
255 return Ok(());
256 };
257 let mut server =
258 tokio::time::timeout(CONNECT_TIMEOUT, TcpStream::connect(("127.0.0.1", port))).await??;
259 server.write_all(&wire::cancel_bytes(pid, &key)).await?;
260 let _ = tokio::time::timeout(CONNECT_TIMEOUT, server.read(&mut [0u8; 1])).await;
262 Ok(())
263}
264
265fn lookup(ctx: &Ctx, db: &str) -> crate::error::Result<Option<CloneRow>> {
267 let rows = ctx.store().list_clones()?;
268 Ok(rows
269 .iter()
270 .find(|c| c.name.as_deref() == Some(db))
271 .or_else(|| rows.iter().find(|c| c.id == db))
272 .cloned())
273}
274
275async fn refuse(
276 client: &mut TcpStream,
277 sqlstate: &str,
278 msg: &str,
279 hint: Option<&str>,
280) -> io::Result<()> {
281 client
282 .write_all(&wire::error_response(sqlstate, msg, hint))
283 .await
284}
285
286struct Tracked {
289 ctx: Arc<Ctx>,
290 conn: u64,
291 clone: String,
292 key: Option<(i32, Vec<u8>)>,
293}
294
295impl Tracked {
296 fn begin(ctx: &Arc<Ctx>, clone: &str) -> (Tracked, Arc<Notify>) {
297 let close = Arc::new(Notify::new());
298 let mut r = ctx.router();
299 r.next_conn += 1;
300 let conn = r.next_conn;
301 r.closers.insert(conn, (clone.to_string(), close.clone()));
302 *r.conns.entry(clone.to_string()).or_default() += 1;
303 r.last_active.insert(clone.to_string(), now());
304 drop(r);
305 let tracked = Tracked {
306 ctx: ctx.clone(),
307 conn,
308 clone: clone.to_string(),
309 key: None,
310 };
311 (tracked, close)
312 }
313
314 fn record_key(&mut self, key: (i32, Vec<u8>)) {
315 let mut r = self.ctx.router();
316 if let Some(old) = self.key.take() {
317 r.cancel.remove(&old);
318 }
319 r.cancel.insert(key.clone(), self.clone.clone());
320 self.key = Some(key);
321 }
322}
323
324impl Drop for Tracked {
325 fn drop(&mut self) {
326 let mut r = self.ctx.router();
327 r.closers.remove(&self.conn);
328 if let Some(n) = r.conns.get_mut(&self.clone) {
329 *n = n.saturating_sub(1);
330 if *n == 0 {
331 r.conns.remove(&self.clone);
332 }
333 }
334 r.last_active.insert(self.clone.clone(), now());
335 if let Some(k) = self.key.take() {
336 r.cancel.remove(&k);
337 }
338 }
339}
340
341async fn startup(
342 ctx: Arc<Ctx>,
343 mut client: TcpStream,
344 major: u16,
345 minor: u16,
346 mut params: Vec<(String, String)>,
347) -> io::Result<()> {
348 if major != 3 {
349 let msg = format!("unsupported frontend protocol {major}.{minor}: the router speaks 3.x");
350 return refuse(&mut client, "0A000", &msg, None).await;
351 }
352 let param = |name: &str| {
353 params
354 .iter()
355 .find(|(k, _)| k == name)
356 .map(|(_, v)| v.clone())
357 };
358 let db = param("database")
360 .filter(|d| !d.is_empty())
361 .or_else(|| param("user"))
362 .unwrap_or_default();
363 let row = match lookup(&ctx, &db) {
364 Ok(Some(row)) => row,
365 Ok(None) => {
366 let hint = format!(
367 "connect as postgres://postgres:<password>@127.0.0.1:{}/<clone name or id>",
368 ctx.config().router_port
369 );
370 let msg = format!("no clone named \"{db}\" (the database name picks the clone)");
371 return refuse(&mut client, "3D000", &msg, Some(&hint)).await;
372 }
373 Err(e) => return refuse(&mut client, "58000", &e.message, None).await,
374 };
375 let mut tries = 0;
378 let (row, mut tracked, close) = loop {
379 tries += 1;
380 let row = match clones::ensure_running(&ctx, &row.id).await {
381 Ok(row) => row,
382 Err(e) => return refuse(&mut client, "57P03", &e.message, e.hint.as_deref()).await,
383 };
384 let (tracked, close) = Tracked::begin(&ctx, &row.id);
385 let same = ctx
386 .store()
387 .get_clone(&row.id)
388 .ok()
389 .flatten()
390 .is_some_and(|r| r.status == CloneStatus::Ready && r.port == row.port);
391 if same {
392 break (row, tracked, close);
393 }
394 if tries >= 3 {
395 drop(tracked);
398 let label = row.name.as_deref().unwrap_or(&row.id);
399 let msg = format!("clone {label} is changing state; retry");
400 return refuse(&mut client, "57P03", &msg, None).await;
401 }
402 };
403 match params.iter_mut().find(|(k, _)| k == "database") {
404 Some((_, v)) => *v = row.database.clone(),
405 None => params.push(("database".into(), row.database.clone())),
406 }
407 let label = row.name.as_deref().unwrap_or(&row.id).to_string();
408 let server =
409 match tokio::time::timeout(CONNECT_TIMEOUT, TcpStream::connect(("127.0.0.1", row.port)))
410 .await
411 {
412 Ok(Ok(s)) => s,
413 _ => {
414 let msg = format!("clone {label} does not answer on its port {}", row.port);
415 return refuse(&mut client, "57P03", &msg, None).await;
416 }
417 };
418 let _ = server.set_nodelay(true);
419
420 let (mut cr, mut cw) = client.into_split();
421 let (mut sr, mut sw) = server.into_split();
422 sw.write_all(&wire::startup_bytes(major, minor, ¶ms))
423 .await?;
424 let up = async {
425 let _ = tokio::io::copy(&mut cr, &mut sw).await;
426 let _ = sw.shutdown().await;
427 };
428 let down = async {
431 loop {
432 let (tag, body) = wire::read_message(&mut sr).await?;
433 if tag == b'K' {
434 if let Some(key) = wire::parse_backend_key(&body) {
435 tracked.record_key(key);
436 }
437 }
438 cw.write_all(&wire::message(tag, &body)).await?;
439 if tag == b'Z' {
440 break;
441 }
442 }
443 tokio::io::copy(&mut sr, &mut cw).await?;
444 io::Result::Ok(())
445 };
446 tokio::pin!(up, down);
447 tokio::select! {
450 _ = close.notified() => {}
451 _ = &mut down => {}
452 _ = &mut up => {
453 tokio::select! {
454 _ = close.notified() => {}
455 _ = &mut down => {}
456 }
457 }
458 }
459 Ok(())
460}