├── .gitignore ├── .dockerignore ├── rinha-app ├── Cargo.toml └── src │ └── main.rs ├── rinha-load-balancer-tcp ├── Cargo.toml └── src │ └── main.rs ├── espora-db ├── src │ ├── lock.rs │ ├── builder.rs │ ├── page.rs │ ├── lib.rs │ └── tokio.rs └── Cargo.toml ├── rinha-load-balancer ├── Cargo.toml └── src │ └── main.rs ├── axum-unix-socket ├── Cargo.toml └── src │ └── lib.rs ├── README.md ├── rinha-espora-server ├── Cargo.toml └── src │ ├── ring_buffer.rs │ └── main.rs ├── rinha-espora-embedded ├── Cargo.toml └── src │ └── main.rs ├── Cargo.toml ├── Dockerfile ├── docker-compose-embedded.yml ├── docker-compose-prod-embedded.yml ├── .github └── workflows │ └── release.yml ├── docker-compose.yml ├── docker-compose-prod.yml ├── src └── lib.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.espora 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .github/ 3 | target/ 4 | README.md 5 | Dockerfile 6 | docker-compose* 7 | -------------------------------------------------------------------------------- /rinha-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rinha-app" 3 | version.workspace = true 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1.36.0", features = ["full"] } 8 | -------------------------------------------------------------------------------- /rinha-load-balancer-tcp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rinha-load-balancer-tcp" 3 | version.workspace = true 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1.36.0", features = ["full"] } 8 | -------------------------------------------------------------------------------- /espora-db/src/lock.rs: -------------------------------------------------------------------------------- 1 | use std::os::fd::RawFd; 2 | 3 | pub struct LockHandle { 4 | pub(crate) fd: RawFd, 5 | } 6 | 7 | impl Drop for LockHandle { 8 | fn drop(&mut self) { 9 | unsafe { libc::flock(self.fd, libc::LOCK_UN) }; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rinha-load-balancer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rinha-load-balancer" 3 | version.workspace = true 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = "0.7.4" 8 | hyper-util = { version = "0.1.3", features = ["client-legacy", "http1", "http2"] } 9 | tokio = { version = "1.36.0", features = ["full"] } 10 | -------------------------------------------------------------------------------- /axum-unix-socket/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "axum-unix-socket" 3 | edition = "2021" 4 | version.workspace = true 5 | 6 | [dependencies] 7 | axum = "0.7.4" 8 | hyper = { version = "1", features = ["full"] } 9 | hyper-util = { version = "0.1.3", features = ["tokio", "server-auto", "http1"] } 10 | tokio = { version = "1.36.0", features = ["full"] } 11 | tower = { version = "0.4", features = ["util"] } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rinha de Backend 2024 2 | 3 | Stack: Rust, Rust e Rust 4 | 5 | Da app, ao load balancer ao banco de dados, tudo foi implementado em Rust em lives no Youtube: 6 | 7 | - [Dia 01 - Implementação da API em memória](https://www.youtube.com/watch?v=wbaw3bBMBag) 8 | - [Dia 02 - Load balancer](https://www.youtube.com/watch?v=hbUuXZMPggM) 9 | - [Dia 03 - Banco de dados](https://www.youtube.com/watch?v=vI5vdnPvoE4) 10 | - [Dia 04 - Integrando tudo](https://www.youtube.com/watch?v=sCWggMruZXg) 11 | -------------------------------------------------------------------------------- /rinha-espora-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rinha-espora-server" 3 | version.workspace = true 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = { version = "0.7.4", features = ["http2"] } 8 | axum-unix-socket = { path = "../axum-unix-socket" } 9 | espora-db = { path = "../espora-db" } 10 | humantime = "2.1.0" 11 | rinha = { path = "../" } 12 | serde = { version = "1.0.196", features = ["derive"] } 13 | serde_json = "1.0.113" 14 | tokio = { version = "1.36.0", features = ["full"] } 15 | -------------------------------------------------------------------------------- /rinha-espora-embedded/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rinha-espora-embedded" 3 | version.workspace = true 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = { version = "0.7.4", features = ["http2"] } 8 | axum-unix-socket = { path = "../axum-unix-socket" } 9 | espora-db = { path = "../espora-db", features = ["tokio"] } 10 | futures = "0.3.30" 11 | humantime = "2.1.0" 12 | rinha = { path = "../" } 13 | serde = { version = "1.0.196", features = ["derive"] } 14 | serde_json = "1.0.113" 15 | tokio = { version = "1.36.0", features = ["full"] } 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "axum-unix-socket", 4 | "espora-db", 5 | "rinha-app", 6 | "rinha-espora-embedded", 7 | "rinha-espora-server", 8 | "rinha-load-balancer", 9 | "rinha-load-balancer-tcp", 10 | ] 11 | 12 | [package] 13 | name = "rinha" 14 | version.workspace = true 15 | edition = "2021" 16 | 17 | [workspace.package] 18 | version = "1.2.0" 19 | 20 | [profile.release] 21 | codegen-units = 1 22 | lto = true 23 | panic = "abort" 24 | 25 | [dependencies] 26 | serde = { version = "1.0.196", features = ["derive"] } 27 | time = { version = "0.3.34", features = ["formatting", "macros", "serde", "parsing"] } 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1-slim-buster AS build 2 | 3 | RUN mkdir /app 4 | COPY . /app 5 | WORKDIR /app 6 | ENV RUSTFLAGS "-C target-feature=+crt-static" 7 | RUN cargo build \ 8 | --release \ 9 | --all \ 10 | --target x86_64-unknown-linux-gnu 11 | 12 | FROM scratch 13 | 14 | COPY --from=build /app/target/x86_64-unknown-linux-gnu/release/rinha-app /app/ 15 | COPY --from=build /app/target/x86_64-unknown-linux-gnu/release/rinha-espora-embedded /app/ 16 | COPY --from=build /app/target/x86_64-unknown-linux-gnu/release/rinha-espora-server /app/ 17 | COPY --from=build /app/target/x86_64-unknown-linux-gnu/release/rinha-load-balancer /app/ 18 | COPY --from=build /app/target/x86_64-unknown-linux-gnu/release/rinha-load-balancer-tcp /app/ 19 | -------------------------------------------------------------------------------- /espora-db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "espora-db" 3 | version = "1.0.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-stream = { version = "0.3.5", optional = true } 8 | bitcode = { version = "0.5.1", features = ["serde"] } 9 | futures = { version = "0.3.30", optional = true, default-features = false, features = ["std"] } 10 | libc = { version = "0.2.153", default-features = false } 11 | serde = { version = "1.0.196", features = ["derive"] } 12 | tokio = { version = "1.36.0", optional = true, features = ["fs", "io-std", "io-util", "rt", "sync"] } 13 | 14 | [features] 15 | tokio = ["async-stream", "futures", "dep:tokio"] 16 | 17 | [dev-dependencies] 18 | tempfile = "3.10.1" 19 | tokio = { version = "1.36.0", features = ["full"] } 20 | -------------------------------------------------------------------------------- /rinha-app/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use tokio::{ 4 | fs, io, 5 | net::{UnixListener, UnixStream}, 6 | }; 7 | 8 | #[tokio::main] 9 | async fn main() -> io::Result<()> { 10 | let unix_socket = env::var("UNIX_SOCKET") 11 | .ok() 12 | .unwrap_or(String::from("./rinha-app.socket")); 13 | 14 | let db = env::var("DB").unwrap_or(String::from("./rinha-espora-server.socket")); 15 | let db: &'static str = Box::leak(db.into_boxed_str()); 16 | 17 | fs::remove_file(&unix_socket).await.ok(); 18 | let listener = UnixListener::bind(&unix_socket)?; 19 | 20 | println!("App ({}) ready {unix_socket}", env!("CARGO_PKG_VERSION")); 21 | 22 | while let Ok((mut downstream, _)) = listener.accept().await { 23 | tokio::spawn(async move { 24 | let mut upstream = UnixStream::connect(db).await.unwrap(); 25 | io::copy_bidirectional(&mut downstream, &mut upstream) 26 | .await 27 | .unwrap(); 28 | }); 29 | } 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /docker-compose-embedded.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | api01: &api 5 | build: 6 | context: . 7 | command: /app/rinha-espora-embedded 8 | hostname: api1 9 | network_mode: host 10 | environment: 11 | UNIX_SOCKET: /app/unix-sockets/rinha-app1.socket 12 | DB: "/db" 13 | volumes: 14 | - db:/db 15 | - sockets:/app/unix-sockets 16 | deploy: 17 | resources: 18 | limits: 19 | cpus: "0.6" 20 | memory: "200MB" 21 | 22 | api02: 23 | <<: *api 24 | hostname: api2 25 | environment: 26 | UNIX_SOCKET: /app/unix-sockets/rinha-app2.socket 27 | DB: "/db" 28 | 29 | lb: 30 | build: 31 | context: . 32 | command: /app/rinha-load-balancer-tcp 33 | depends_on: 34 | - api01 35 | - api02 36 | environment: 37 | PORT: 9999 38 | UPSTREAMS: "/app/unix-sockets/rinha-app1.socket,/app/unix-sockets/rinha-app2.socket" 39 | network_mode: host 40 | deploy: 41 | resources: 42 | limits: 43 | cpus: "0.3" 44 | memory: "150MB" 45 | volumes: 46 | - sockets:/app/unix-sockets 47 | 48 | volumes: 49 | db: 50 | name: db 51 | sockets: 52 | name: unix-sockets 53 | -------------------------------------------------------------------------------- /docker-compose-prod-embedded.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | api01: &api 5 | image: rnavarro/rinha-backend-2024:1.2 6 | command: /app/rinha-espora-embedded 7 | hostname: api1 8 | network_mode: host 9 | environment: 10 | UNIX_SOCKET: /app/unix-sockets/rinha-app1.socket 11 | DB: "/db" 12 | volumes: 13 | - db:/db 14 | - sockets:/app/unix-sockets 15 | deploy: 16 | resources: 17 | limits: 18 | cpus: "0.6" 19 | memory: "200MB" 20 | 21 | api02: 22 | <<: *api 23 | hostname: api2 24 | environment: 25 | UNIX_SOCKET: /app/unix-sockets/rinha-app2.socket 26 | DB: "/db" 27 | 28 | lb: 29 | image: rnavarro/rinha-backend-2024:1.2 30 | command: /app/rinha-load-balancer-tcp 31 | depends_on: 32 | - api01 33 | - api02 34 | environment: 35 | PORT: 9999 36 | UPSTREAMS: "/app/unix-sockets/rinha-app1.socket,/app/unix-sockets/rinha-app2.socket" 37 | network_mode: host 38 | deploy: 39 | resources: 40 | limits: 41 | cpus: "0.3" 42 | memory: "150MB" 43 | volumes: 44 | - sockets:/app/unix-sockets 45 | 46 | volumes: 47 | db: 48 | name: db 49 | sockets: 50 | name: unix-sockets 51 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | jobs: 7 | release: 8 | name: Publish to Dockerhub 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Get release version 14 | id: get_version 15 | uses: battila7/get-version-action@v2 16 | 17 | - run: docker login -u ${{ secrets.DOCKERHUB_USER }} -p ${{ secrets.DOCKERHUB_PASS }} 18 | 19 | - run: docker build -t rinha . 20 | - run: docker tag rinha rnavarro/rinha-backend-2024:latest 21 | - run: docker tag rinha rnavarro/rinha-backend-2024:${{ steps.get_version.outputs.major }} 22 | - run: docker tag rinha rnavarro/rinha-backend-2024:${{ steps.get_version.outputs.major }}.${{ steps.get_version.outputs.minor }} 23 | - run: docker tag rinha rnavarro/rinha-backend-2024:${{ steps.get_version.outputs.version-without-v }} 24 | 25 | - run: docker push rnavarro/rinha-backend-2024:latest 26 | - run: docker push rnavarro/rinha-backend-2024:${{ steps.get_version.outputs.major }} 27 | - run: docker push rnavarro/rinha-backend-2024:${{ steps.get_version.outputs.major }}.${{ steps.get_version.outputs.minor }} 28 | - run: docker push rnavarro/rinha-backend-2024:${{ steps.get_version.outputs.version-without-v }} 29 | -------------------------------------------------------------------------------- /axum-unix-socket/src/lib.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::Request, response::Response}; 2 | use hyper::body::Incoming; 3 | use hyper_util::{ 4 | rt::{TokioExecutor, TokioIo}, 5 | server, 6 | }; 7 | use std::{convert::Infallible, io, path::Path}; 8 | use tokio::{fs, net::UnixListener}; 9 | use tower::Service; 10 | 11 | pub async fn serve(path: impl AsRef, app: S) -> io::Result<()> 12 | where 13 | S: Service, Response = Response, Error = Infallible> + Clone + Send + 'static, 14 | S::Future: Send, 15 | { 16 | let path = path.as_ref(); 17 | 18 | fs::remove_file(&path).await.ok(); 19 | 20 | let listener = UnixListener::bind(path)?; 21 | 22 | while let Ok((socket, _addr)) = listener.accept().await { 23 | let service = app.clone(); 24 | 25 | tokio::spawn(async move { 26 | let socket = TokioIo::new(socket); 27 | 28 | let hyper_service = hyper::service::service_fn(move |request: Request| { 29 | service.clone().call(request) 30 | }); 31 | 32 | if let Err(err) = server::conn::auto::Builder::new(TokioExecutor::new()) 33 | .serve_connection_with_upgrades(socket, hyper_service) 34 | .await 35 | { 36 | eprintln!("failed to serve connection: {err:#}"); 37 | } 38 | }); 39 | } 40 | 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /rinha-load-balancer-tcp/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use tokio::{ 4 | io, 5 | net::{TcpListener, UnixStream}, 6 | }; 7 | 8 | #[tokio::main] 9 | async fn main() -> io::Result<()> { 10 | let port = env::var("PORT") 11 | .ok() 12 | .and_then(|port| port.parse::().ok()) 13 | .unwrap_or(9999); 14 | 15 | let addrs = env::var("UPSTREAMS") 16 | .ok() 17 | .map(|upstream| { 18 | upstream 19 | .split(',') 20 | .map(|addr| addr.trim().to_owned()) 21 | .collect::>() 22 | }) 23 | .unwrap_or(vec![ 24 | String::from("./rinha-app1.socket"), 25 | String::from("./rinha-app2.socket"), 26 | ]) 27 | .into_iter() 28 | .map(|addr| Box::leak(addr.into_boxed_str()) as &'static str) 29 | .collect::>(); 30 | 31 | let listener = TcpListener::bind(("0.0.0.0", port)).await.unwrap(); 32 | let mut counter = 0; 33 | 34 | println!("TCP lb ({}) ready 9999", env!("CARGO_PKG_VERSION")); 35 | while let Ok((mut downstream, _)) = listener.accept().await { 36 | downstream.set_nodelay(true)?; 37 | counter += 1; 38 | let addr = addrs[counter % addrs.len()]; 39 | tokio::spawn(async move { 40 | let mut upstream = UnixStream::connect(addr).await.unwrap(); 41 | io::copy_bidirectional(&mut downstream, &mut upstream) 42 | .await 43 | .unwrap(); 44 | }); 45 | } 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /espora-db/src/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::Path, time::Duration}; 2 | 3 | use serde::{de::DeserializeOwned, Serialize}; 4 | 5 | use crate::Db; 6 | 7 | #[derive(Debug)] 8 | pub struct Builder { 9 | sync_writes: Option, 10 | } 11 | 12 | impl Default for Builder { 13 | fn default() -> Self { 14 | Builder { 15 | sync_writes: Some(Duration::from_secs(0)), 16 | } 17 | } 18 | } 19 | 20 | impl Builder { 21 | pub fn sync_writes(self, sync_writes: bool) -> Self { 22 | Self { 23 | sync_writes: if sync_writes { 24 | Some(Duration::from_secs(0)) 25 | } else { 26 | None 27 | }, 28 | } 29 | } 30 | 31 | pub fn sync_write_interval(self, interval: Duration) -> Self { 32 | Self { 33 | sync_writes: Some(interval), 34 | } 35 | } 36 | 37 | pub fn build( 38 | self, 39 | path: impl AsRef, 40 | ) -> io::Result> { 41 | let mut db = Db::from_path(path)?; 42 | db.sync_writes = self.sync_writes; 43 | Ok(db) 44 | } 45 | 46 | #[cfg(feature = "tokio")] 47 | pub async fn build_tokio( 48 | self, 49 | path: impl AsRef, 50 | ) -> io::Result> { 51 | let mut db = crate::tokio::Db::from_path(path).await?; 52 | db.sync_writes = self.sync_writes; 53 | Ok(db) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /rinha-espora-server/src/ring_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{vec_deque, VecDeque}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub struct RingBuffer(VecDeque); 7 | 8 | impl Default for RingBuffer { 9 | fn default() -> Self { 10 | Self::new() 11 | } 12 | } 13 | 14 | impl RingBuffer { 15 | pub fn new() -> Self { 16 | Self(VecDeque::with_capacity(SIZE)) 17 | } 18 | 19 | pub fn push_front(&mut self, item: T) { 20 | if self.0.len() == self.0.capacity() { 21 | self.0.pop_back(); 22 | self.0.push_front(item); 23 | } else { 24 | self.0.push_front(item); 25 | } 26 | } 27 | 28 | pub fn push_back(&mut self, item: T) { 29 | if self.0.len() == self.0.capacity() { 30 | self.0.pop_front(); 31 | self.0.push_back(item); 32 | } else { 33 | self.0.push_back(item); 34 | } 35 | } 36 | } 37 | 38 | impl IntoIterator for RingBuffer { 39 | type Item = T; 40 | type IntoIter = vec_deque::IntoIter; 41 | 42 | fn into_iter(self) -> Self::IntoIter { 43 | self.0.into_iter() 44 | } 45 | } 46 | 47 | impl FromIterator for RingBuffer { 48 | fn from_iter>(iter: T) -> Self { 49 | let mut ring_buffer = Self::new(); 50 | for item in iter.into_iter() { 51 | ring_buffer.push_back(item); 52 | } 53 | ring_buffer 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | api01: &api 5 | build: 6 | context: . 7 | command: /app/rinha-app 8 | hostname: api1 9 | network_mode: host 10 | depends_on: 11 | - db 12 | environment: 13 | UNIX_SOCKET: /app/unix-sockets/rinha-app1.socket 14 | DB: /app/unix-sockets/rinha-espora_server.socket 15 | deploy: 16 | resources: 17 | limits: 18 | cpus: "0.3" 19 | memory: "100MB" 20 | volumes: 21 | - sockets:/app/unix-sockets 22 | 23 | api02: 24 | <<: *api 25 | hostname: api2 26 | environment: 27 | UNIX_SOCKET: /app/unix-sockets/rinha-app2.socket 28 | DB: /app/unix-sockets/rinha-espora_server.socket 29 | 30 | db: 31 | build: 32 | context: . 33 | command: /app/rinha-espora-server 34 | network_mode: host 35 | environment: 36 | UNIX_SOCKET: /app/unix-sockets/rinha-espora_server.socket 37 | deploy: 38 | resources: 39 | limits: 40 | cpus: "0.5" 41 | memory: "250MB" 42 | volumes: 43 | - sockets:/app/unix-sockets 44 | 45 | lb: 46 | build: 47 | context: . 48 | command: /app/rinha-load-balancer-tcp 49 | depends_on: 50 | - api01 51 | - api02 52 | environment: 53 | PORT: 9999 54 | UPSTREAMS: "/app/unix-sockets/rinha-app1.socket,/app/unix-sockets/rinha-app2.socket" 55 | network_mode: host 56 | deploy: 57 | resources: 58 | limits: 59 | cpus: "0.4" 60 | memory: "100MB" 61 | volumes: 62 | - sockets:/app/unix-sockets 63 | 64 | volumes: 65 | sockets: 66 | name: unix-sockets 67 | -------------------------------------------------------------------------------- /docker-compose-prod.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | api01: &api 5 | image: rnavarro/rinha-backend-2024:1.2 6 | command: /app/rinha-app 7 | hostname: api1 8 | network_mode: host 9 | depends_on: 10 | - db 11 | environment: 12 | UNIX_SOCKET: /app/unix-sockets/rinha-app1.socket 13 | DB: /app/unix-sockets/rinha-espora-server.socket 14 | deploy: 15 | resources: 16 | limits: 17 | cpus: "0.3" 18 | memory: "100MB" 19 | volumes: 20 | - sockets:/app/unix-sockets 21 | 22 | api02: 23 | <<: *api 24 | hostname: api2 25 | environment: 26 | UNIX_SOCKET: /app/unix-sockets/rinha-app2.socket 27 | DB: /app/unix-sockets/rinha-espora-server.socket 28 | volumes: 29 | - sockets:/app/unix-sockets 30 | 31 | db: 32 | image: rnavarro/rinha-backend-2024:1.2 33 | command: /app/rinha-espora-server 34 | network_mode: host 35 | environment: 36 | UNIX_SOCKET: /app/unix-sockets/rinha-espora-server.socket 37 | deploy: 38 | resources: 39 | limits: 40 | cpus: "0.5" 41 | memory: "250MB" 42 | volumes: 43 | - sockets:/app/unix-sockets 44 | 45 | lb: 46 | image: rnavarro/rinha-backend-2024:1.2 47 | command: /app/rinha-load-balancer-tcp 48 | depends_on: 49 | - api01 50 | - api02 51 | environment: 52 | PORT: 9999 53 | UPSTREAMS: "/app/unix-sockets/rinha-app1.socket,/app/unix-sockets/rinha-app2.socket" 54 | network_mode: host 55 | deploy: 56 | resources: 57 | limits: 58 | cpus: "0.4" 59 | memory: "100MB" 60 | volumes: 61 | - sockets:/app/unix-sockets 62 | 63 | volumes: 64 | sockets: 65 | name: unix-sockets 66 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::TryFrom, fmt::Display}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use time::{format_description::well_known::Rfc3339, OffsetDateTime}; 5 | 6 | #[derive(Debug, Clone, Serialize, Deserialize)] 7 | #[serde(try_from = "String")] 8 | pub struct Description(String); 9 | 10 | impl TryFrom for Description { 11 | type Error = &'static str; 12 | 13 | fn try_from(value: String) -> Result { 14 | if value.is_empty() || value.len() > 10 { 15 | Err("Descricao inválida") 16 | } else { 17 | Ok(Self(value)) 18 | } 19 | } 20 | } 21 | 22 | #[derive(Debug, Clone, Serialize, Deserialize)] 23 | pub enum TransactionType { 24 | #[serde(rename = "c")] 25 | Credit, 26 | #[serde(rename = "d")] 27 | Debit, 28 | } 29 | 30 | #[derive(Debug, Clone, Serialize, Deserialize)] 31 | pub struct Transaction { 32 | #[serde(rename = "valor")] 33 | pub value: i64, 34 | #[serde(rename = "tipo")] 35 | pub kind: TransactionType, 36 | #[serde(rename = "descricao")] 37 | pub description: Description, 38 | #[serde(rename = "realizada_em", default)] 39 | pub created_at: DateTime, 40 | } 41 | 42 | #[derive(Debug, Clone, Serialize, Deserialize)] 43 | pub struct DateTime(#[serde(with = "time::serde::rfc3339")] OffsetDateTime); 44 | 45 | impl Default for DateTime { 46 | fn default() -> Self { 47 | Self(OffsetDateTime::now_utc()) 48 | } 49 | } 50 | 51 | impl Display for DateTime { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | write!(f, "{}", self.0.format(&Rfc3339).unwrap()) 54 | } 55 | } 56 | 57 | impl DateTime { 58 | pub fn now() -> DateTime { 59 | Default::default() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rinha-load-balancer/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | hash::{DefaultHasher, Hash, Hasher}, 4 | str::FromStr, 5 | sync::{ 6 | atomic::{AtomicUsize, Ordering}, 7 | Arc, 8 | }, 9 | time::Duration, 10 | }; 11 | 12 | use axum::{ 13 | body::Body, 14 | extract::{Request, State}, 15 | handler::Handler, 16 | http::{ 17 | uri::{Authority, Scheme}, 18 | StatusCode, Uri, 19 | }, 20 | response::IntoResponse, 21 | }; 22 | use hyper_util::{ 23 | client::legacy::{connect::HttpConnector, Client}, 24 | rt::TokioExecutor, 25 | }; 26 | use tokio::net::TcpListener; 27 | 28 | #[derive(Clone)] 29 | struct AppState { 30 | load_balancer: Arc, 31 | http_client: Client, 32 | } 33 | 34 | struct RoundRobin { 35 | addrs: Vec, 36 | req_counter: Arc, 37 | } 38 | 39 | trait LoadBalancer { 40 | fn next_server(&self, req: &Request) -> String; 41 | } 42 | 43 | impl LoadBalancer for RoundRobin { 44 | fn next_server(&self, _req: &Request) -> String { 45 | let count = self.req_counter.fetch_add(1, Ordering::Relaxed); 46 | self.addrs[count % self.addrs.len()].clone() 47 | } 48 | } 49 | 50 | struct RinhaAccountBalancer { 51 | addrs: Vec, 52 | } 53 | 54 | impl LoadBalancer for RinhaAccountBalancer { 55 | fn next_server(&self, req: &Request) -> String { 56 | let path = req.uri().path(); 57 | let hash = { 58 | let mut hasher = DefaultHasher::new(); 59 | path.hash(&mut hasher); 60 | hasher.finish() as usize 61 | }; 62 | self.addrs[hash % self.addrs.len()].to_string() 63 | } 64 | } 65 | 66 | #[tokio::main] 67 | async fn main() { 68 | let port = env::var("PORT") 69 | .ok() 70 | .and_then(|port| port.parse::().ok()) 71 | .unwrap_or(9999); 72 | 73 | let addrs = env::var("UPSTREAMS") 74 | .ok() 75 | .map(|upstream| { 76 | upstream 77 | .split(',') 78 | .map(|addr| addr.trim().to_owned()) 79 | .collect::>() 80 | }) 81 | .unwrap_or(vec![ 82 | String::from("0.0.0.0:9997"), 83 | String::from("0.0.0.0:9998"), 84 | ]); 85 | 86 | let listener = TcpListener::bind(("0.0.0.0", port)).await.unwrap(); 87 | 88 | let client = { 89 | let mut connector = HttpConnector::new(); 90 | connector.set_keepalive(Some(Duration::from_secs(60))); 91 | connector.set_nodelay(true); 92 | Client::builder(TokioExecutor::new()) 93 | .http2_only(true) 94 | .build::<_, Body>(connector) 95 | }; 96 | 97 | #[allow(unused)] 98 | let round_robin = RoundRobin { 99 | addrs: addrs.clone(), 100 | req_counter: Arc::new(AtomicUsize::new(0)), 101 | }; 102 | 103 | #[allow(unused)] 104 | let fixed_load_balancer = RinhaAccountBalancer { 105 | addrs: addrs.clone(), 106 | }; 107 | 108 | let app_state = AppState { 109 | load_balancer: Arc::new(round_robin), 110 | http_client: client, 111 | }; 112 | 113 | let app = proxy.with_state(app_state); 114 | 115 | println!("HTTP lb ({}) ready 9999", env!("CARGO_PKG_VERSION")); 116 | 117 | axum::serve(listener, app).await.unwrap(); 118 | } 119 | 120 | async fn proxy( 121 | State(AppState { 122 | load_balancer, 123 | http_client, 124 | }): State, 125 | mut req: Request, 126 | ) -> impl IntoResponse { 127 | let addr = load_balancer.next_server(&req); 128 | 129 | *req.uri_mut() = { 130 | let uri = req.uri(); 131 | let mut parts = uri.clone().into_parts(); 132 | parts.authority = Authority::from_str(addr.as_str()).ok(); 133 | parts.scheme = Some(Scheme::HTTP); 134 | Uri::from_parts(parts).unwrap() 135 | }; 136 | 137 | match http_client.request(req).await { 138 | Ok(res) => Ok(res), 139 | Err(_) => Err(StatusCode::BAD_GATEWAY), 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /rinha-espora-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, env, error::Error, path::Path as FilePath, sync::Arc, time::Duration, 3 | }; 4 | 5 | use axum::{ 6 | extract::{Path, State}, 7 | http::StatusCode, 8 | response::IntoResponse, 9 | routing::{get, post}, 10 | Json, Router, 11 | }; 12 | use espora_db::{Db, Error as DbError}; 13 | use ring_buffer::RingBuffer; 14 | use rinha::{DateTime, Transaction, TransactionType}; 15 | use serde_json::json; 16 | use tokio::sync::RwLock; 17 | 18 | mod ring_buffer; 19 | 20 | type Balance = i64; 21 | 22 | struct Account { 23 | balance: Balance, 24 | limit: i64, 25 | transactions: RingBuffer, 26 | db: Db<(Balance, Transaction), 128>, 27 | } 28 | 29 | impl Account { 30 | pub fn with_db( 31 | path: impl AsRef, 32 | fsync_interval: Duration, 33 | limit: i64, 34 | ) -> Result> { 35 | let mut db = Db::<(i64, Transaction), 128>::builder() 36 | .sync_write_interval(fsync_interval) 37 | .build(path)?; 38 | 39 | let transactions = db 40 | .rows_reverse() 41 | .take(10) 42 | .collect::, DbError>>()?; 43 | 44 | let balance = transactions 45 | .first() 46 | .map(|(balance, _)| *balance) 47 | .unwrap_or_default(); 48 | 49 | Ok(Account { 50 | limit, 51 | balance, 52 | transactions: transactions 53 | .into_iter() 54 | .map(|(_, transaction)| transaction) 55 | .collect(), 56 | db, 57 | }) 58 | } 59 | 60 | pub fn transact(&mut self, transaction: Transaction) -> Result<(), &'static str> { 61 | let balance = match transaction.kind { 62 | TransactionType::Credit => self.balance + transaction.value, 63 | TransactionType::Debit => { 64 | if self.balance + self.limit >= transaction.value { 65 | self.balance - transaction.value 66 | } else { 67 | return Err("Não tem limite o suficiente"); 68 | } 69 | } 70 | }; 71 | self.db 72 | .insert((balance, transaction.clone())) 73 | .map_err(|_| "Erro ao persistir")?; 74 | self.balance = balance; 75 | self.transactions.push_front(transaction); 76 | Ok(()) 77 | } 78 | } 79 | 80 | type AppState = Arc>>; 81 | 82 | #[tokio::main] 83 | async fn main() { 84 | let unix_socket = env::var("UNIX_SOCKET") 85 | .ok() 86 | .unwrap_or(String::from("./rinha-espora-server.socket")); 87 | 88 | let fsync_interval = env::var("ESPORA_FSYNC_INTERVAL") 89 | .ok() 90 | .and_then(|interval| humantime::parse_duration(&interval).ok()) 91 | .unwrap_or(Duration::from_millis(10)); 92 | 93 | #[rustfmt::skip] 94 | let accounts = HashMap::from_iter([ 95 | (1, RwLock::new(Account::with_db("account-1.espora", fsync_interval, 100_000).unwrap())), 96 | (2, RwLock::new(Account::with_db("account-2.espora", fsync_interval, 80_000).unwrap())), 97 | (3, RwLock::new(Account::with_db("account-3.espora", fsync_interval, 1_000_000).unwrap())), 98 | (4, RwLock::new(Account::with_db("account-4.espora", fsync_interval, 10_000_000).unwrap())), 99 | (5, RwLock::new(Account::with_db("account-5.espora", fsync_interval, 500_000).unwrap())), 100 | ]); 101 | 102 | let app = Router::new() 103 | .route("/clientes/:id/transacoes", post(create_transaction)) 104 | .route("/clientes/:id/extrato", get(view_account)) 105 | .with_state(Arc::new(accounts)); 106 | 107 | println!("DB ({}) ready {unix_socket}", env!("CARGO_PKG_VERSION")); 108 | 109 | axum_unix_socket::serve(unix_socket, app).await.unwrap(); 110 | } 111 | 112 | async fn create_transaction( 113 | Path(account_id): Path, 114 | State(accounts): State, 115 | Json(transaction): Json, 116 | ) -> impl IntoResponse { 117 | match accounts.get(&account_id) { 118 | Some(account) => { 119 | let mut account = account.write().await; 120 | match account.transact(transaction) { 121 | Ok(()) => Ok(Json(json!({ 122 | "limite": account.limit, 123 | "saldo": account.balance, 124 | }))), 125 | Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), 126 | } 127 | } 128 | None => Err(StatusCode::NOT_FOUND), 129 | } 130 | } 131 | 132 | async fn view_account( 133 | Path(account_id): Path, 134 | State(accounts): State, 135 | ) -> impl IntoResponse { 136 | match accounts.get(&account_id) { 137 | Some(account) => { 138 | let account = account.read().await; 139 | Ok(Json(json!({ 140 | "saldo": { 141 | "total": account.balance, 142 | "data_extrato": DateTime::now(), 143 | "limite": account.limit, 144 | }, 145 | "ultimas_transacoes": account.transactions, 146 | }))) 147 | } 148 | None => Err(StatusCode::NOT_FOUND), 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /espora-db/src/page.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Cursor, Seek, Write}, 3 | iter, 4 | }; 5 | 6 | use serde::Serialize; 7 | 8 | use crate::DbResult; 9 | 10 | pub const PAGE_SIZE: usize = 4096; 11 | 12 | #[derive(Debug)] 13 | pub struct Page { 14 | data: Vec, 15 | free: usize, 16 | } 17 | 18 | impl Page { 19 | pub fn new() -> Self { 20 | Self { 21 | data: Vec::with_capacity(PAGE_SIZE), 22 | free: PAGE_SIZE, 23 | } 24 | } 25 | 26 | pub fn from_bytes(data: Vec) -> Self { 27 | let free = { 28 | let mut cursor = 0; 29 | let last_page_offset = iter::from_fn(|| { 30 | let offset = cursor * ROW_SIZE; 31 | if offset + ROW_SIZE > data.len() { 32 | return None; 33 | } 34 | cursor += 1; 35 | 36 | if data[offset..offset + 8] != [0; 8] { 37 | Some(offset + ROW_SIZE) 38 | } else { 39 | None 40 | } 41 | }) 42 | .last() 43 | .unwrap_or(0); 44 | PAGE_SIZE - last_page_offset 45 | }; 46 | 47 | Self { data, free } 48 | } 49 | 50 | pub fn insert(&mut self, row: S) -> DbResult<()> { 51 | let serialized = bitcode::serialize(&row)?; 52 | let size = serialized.len() as u64; 53 | let size = size.to_be_bytes(); 54 | 55 | let mut cursor = Cursor::new(&mut self.data); 56 | cursor.seek(std::io::SeekFrom::Start((PAGE_SIZE - self.free) as u64))?; 57 | 58 | self.free -= cursor.write(&size)?; 59 | self.free -= cursor.write(&serialized)?; 60 | self.free -= cursor.write(&vec![0; ROW_SIZE - (serialized.len() + size.len())])?; 61 | 62 | Ok(()) 63 | } 64 | 65 | pub fn rows(&self) -> impl Iterator { 66 | let mut cursor = 0; 67 | iter::from_fn(move || { 68 | let offset = cursor * ROW_SIZE; 69 | if offset + ROW_SIZE > self.data.len() { 70 | return None; 71 | } 72 | 73 | let row = &self.data[offset..offset + ROW_SIZE]; 74 | let size = { 75 | let mut buf = [0; 8]; 76 | buf.copy_from_slice(&row[0..8]); 77 | u64::from_be_bytes(buf) as usize 78 | }; 79 | 80 | if size == 0 { 81 | return None; 82 | } 83 | 84 | cursor += 1; 85 | Some(&row[8..8 + size]) 86 | }) 87 | } 88 | 89 | pub fn len(&self) -> usize { 90 | self.data.len() 91 | } 92 | 93 | pub fn available_rows(&self) -> usize { 94 | (PAGE_SIZE - self.data.len()) / ROW_SIZE 95 | } 96 | } 97 | 98 | impl AsRef<[u8]> for Page { 99 | fn as_ref(&self) -> &[u8] { 100 | &self.data 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | 108 | #[test] 109 | fn test_insert_into_page() { 110 | let mut page = Page::<1024>::new(); 111 | assert_eq!(4, page.available_rows()); 112 | page.insert(String::from("Rinha")).unwrap(); 113 | assert_eq!(3, page.available_rows()); 114 | page.insert(String::from("de")).unwrap(); 115 | assert_eq!(2, page.available_rows()); 116 | page.insert(2024 as u64).unwrap(); 117 | assert_eq!(1, page.available_rows()); 118 | 119 | let mut rows = page.rows(); 120 | assert_eq!( 121 | "Rinha", 122 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 123 | ); 124 | assert_eq!( 125 | "de", 126 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 127 | ); 128 | assert_eq!( 129 | 2024, 130 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 131 | ); 132 | assert!(rows.next().is_none()); 133 | } 134 | 135 | #[test] 136 | fn test_initialize() { 137 | let page = Page::<1024>::new(); 138 | assert_eq!(0, page.len()); 139 | assert_eq!(PAGE_SIZE, page.free); 140 | } 141 | 142 | #[test] 143 | fn test_from_empty_bytes() { 144 | let page = Page::<1024>::from_bytes(vec![]); 145 | assert_eq!(0, page.len()); 146 | assert_eq!(PAGE_SIZE, page.free); 147 | } 148 | 149 | #[test] 150 | fn test_from_bytes() { 151 | let mut page = Page::<1024>::from_bytes(vec![]); 152 | page.insert(1).unwrap(); 153 | page.insert(2).unwrap(); 154 | 155 | let new_page = Page::<1024>::from_bytes(page.as_ref().to_vec()); 156 | assert_eq!(page.len(), new_page.len()); 157 | assert_eq!(page.available_rows(), new_page.available_rows()); 158 | assert_eq!(page.free, new_page.free); 159 | } 160 | 161 | #[test] 162 | fn test_update_existing_page() { 163 | let mut page = Page::<1024>::from_bytes(vec![]); 164 | page.insert("Rinha").unwrap(); 165 | page.insert("de").unwrap(); 166 | 167 | let mut page = Page::<1024>::from_bytes(page.as_ref().to_vec()); 168 | page.insert("Backend").unwrap(); 169 | page.insert("2024").unwrap(); 170 | 171 | let mut rows = page.rows(); 172 | assert_eq!( 173 | "Rinha", 174 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 175 | ); 176 | assert_eq!( 177 | "de", 178 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 179 | ); 180 | assert_eq!( 181 | "Backend", 182 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 183 | ); 184 | assert_eq!( 185 | "2024", 186 | bitcode::deserialize::(&rows.next().unwrap()).unwrap() 187 | ); 188 | assert!(rows.next().is_none()); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /rinha-espora-embedded/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | env, 4 | path::{Path as FilePath, PathBuf}, 5 | sync::Arc, 6 | time::Duration, 7 | }; 8 | 9 | use axum::{ 10 | extract::{Path, State}, 11 | http::StatusCode, 12 | response::IntoResponse, 13 | routing::{get, post}, 14 | Json, Router, 15 | }; 16 | use espora_db::{tokio::Db, Error as DbError}; 17 | use futures::{StreamExt, TryStreamExt}; 18 | use rinha::{DateTime, Transaction, TransactionType}; 19 | use serde_json::json; 20 | use tokio::sync::Mutex; 21 | 22 | type Balance = i64; 23 | 24 | struct Account { 25 | limit: i64, 26 | db: Db<(Balance, Transaction), 128>, 27 | } 28 | 29 | impl Account { 30 | pub async fn with_db( 31 | path: impl AsRef, 32 | fsync_interval: Duration, 33 | limit: i64, 34 | ) -> Result { 35 | let db = Db::<(i64, Transaction), 128>::builder() 36 | .sync_write_interval(fsync_interval) 37 | .build_tokio(&path) 38 | .await?; 39 | 40 | Ok(Account { limit, db }) 41 | } 42 | 43 | pub async fn transact(&mut self, transaction: Transaction) -> Result { 44 | let lock = self 45 | .db 46 | .lock_writes() 47 | .await 48 | .map_err(|_| "Falha ao conseguir o lock do db")?; 49 | 50 | let current_balance = self 51 | .db 52 | .rows_reverse() 53 | .take(1) 54 | .try_collect::>() 55 | .await 56 | .map_err(|_| "Falha ao ler do db")? 57 | .first() 58 | .map(|(balance, _)| *balance) 59 | .unwrap_or(0); 60 | 61 | let balance = match transaction.kind { 62 | TransactionType::Credit => current_balance + transaction.value, 63 | TransactionType::Debit => { 64 | if current_balance + self.limit >= transaction.value { 65 | current_balance - transaction.value 66 | } else { 67 | return Err("Não tem limite o suficiente"); 68 | } 69 | } 70 | }; 71 | 72 | self.db 73 | .insert((balance, transaction.clone())) 74 | .await 75 | .map_err(|_| "Erro ao persistir no db")?; 76 | 77 | drop(lock); 78 | 79 | Ok(balance) 80 | } 81 | 82 | pub async fn last_transactions( 83 | &mut self, 84 | limit: usize, 85 | ) -> Result, DbError> { 86 | self.db 87 | .rows_reverse() 88 | .take(limit) 89 | .try_collect::>() 90 | .await 91 | } 92 | } 93 | 94 | type AppState = Arc>>; 95 | 96 | #[tokio::main] 97 | async fn main() { 98 | let unix_socket = env::var("UNIX_SOCKET") 99 | .ok() 100 | .unwrap_or(String::from("./rinha-espora-app.socket")); 101 | 102 | let db = env::var("DB") 103 | .ok() 104 | .map(PathBuf::from) 105 | .unwrap_or(PathBuf::from("./")); 106 | 107 | let fsync_interval = env::var("ESPORA_FSYNC_INTERVAL") 108 | .ok() 109 | .and_then(|interval| humantime::parse_duration(&interval).ok()) 110 | .unwrap_or(Duration::from_millis(10)); 111 | 112 | #[rustfmt::skip] 113 | let accounts = HashMap::from_iter([ 114 | (1, Mutex::new(Account::with_db(db.join("account-1.espora"), fsync_interval, 100_000).await.unwrap())), 115 | (2, Mutex::new(Account::with_db(db.join("account-2.espora"), fsync_interval, 80_000).await.unwrap())), 116 | (3, Mutex::new(Account::with_db(db.join("account-3.espora"), fsync_interval, 1_000_000).await.unwrap())), 117 | (4, Mutex::new(Account::with_db(db.join("account-4.espora"), fsync_interval, 10_000_000).await.unwrap())), 118 | (5, Mutex::new(Account::with_db(db.join("account-5.espora"), fsync_interval, 500_000).await.unwrap())), 119 | ]); 120 | 121 | let app = Router::new() 122 | .route("/clientes/:id/transacoes", post(create_transaction)) 123 | .route("/clientes/:id/extrato", get(view_account)) 124 | .with_state(Arc::new(accounts)); 125 | 126 | println!("App ({}) ready {unix_socket}", env!("CARGO_PKG_VERSION")); 127 | 128 | axum_unix_socket::serve(unix_socket, app).await.unwrap(); 129 | } 130 | 131 | async fn create_transaction( 132 | Path(account_id): Path, 133 | State(accounts): State, 134 | Json(transaction): Json, 135 | ) -> impl IntoResponse { 136 | match accounts.get(&account_id) { 137 | Some(account) => { 138 | let mut account = account.lock().await; 139 | match account.transact(transaction).await { 140 | Ok(balance) => Ok(Json(json!({ 141 | "limite": account.limit, 142 | "saldo": balance, 143 | }))), 144 | Err(_) => Err(StatusCode::UNPROCESSABLE_ENTITY), 145 | } 146 | } 147 | None => Err(StatusCode::NOT_FOUND), 148 | } 149 | } 150 | 151 | async fn view_account( 152 | Path(account_id): Path, 153 | State(accounts): State, 154 | ) -> impl IntoResponse { 155 | match accounts.get(&account_id) { 156 | Some(account) => { 157 | let mut account = account.lock().await; 158 | 159 | let transactions = account.last_transactions(10).await.unwrap_or_default(); 160 | 161 | let balance = transactions 162 | .first() 163 | .map(|(balance, _)| *balance) 164 | .unwrap_or_default(); 165 | 166 | let transactions = transactions 167 | .into_iter() 168 | .map(|(_, txn)| txn) 169 | .collect::>(); 170 | 171 | Ok(Json(json!({ 172 | "saldo": { 173 | "total": balance, 174 | "data_extrato": DateTime::now(), 175 | "limite": account.limit, 176 | }, 177 | "ultimas_transacoes": transactions, 178 | }))) 179 | } 180 | None => Err(StatusCode::NOT_FOUND), 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /espora-db/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error, fmt, 3 | fs::{File, OpenOptions}, 4 | io::{self, Read, Seek, Write}, 5 | iter, 6 | marker::PhantomData, 7 | os::fd::AsRawFd, 8 | path::Path, 9 | time::{Duration, Instant}, 10 | }; 11 | 12 | use lock::LockHandle; 13 | use serde::{de::DeserializeOwned, Serialize}; 14 | 15 | use crate::{ 16 | builder::Builder, 17 | page::{Page, PAGE_SIZE}, 18 | }; 19 | 20 | pub mod builder; 21 | mod lock; 22 | mod page; 23 | #[cfg(feature = "tokio")] 24 | pub mod tokio; 25 | 26 | #[derive(Debug)] 27 | pub enum Error { 28 | Io(io::Error), 29 | Serialization(Box), 30 | } 31 | impl fmt::Display for Error { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | match self { 34 | Self::Io(err) => write!(f, "{err}"), 35 | Self::Serialization(err) => write!(f, "{err}"), 36 | } 37 | } 38 | } 39 | 40 | impl std::error::Error for Error {} 41 | 42 | impl From for Error { 43 | fn from(err: io::Error) -> Self { 44 | Error::Io(err) 45 | } 46 | } 47 | 48 | impl From for Error { 49 | fn from(err: bitcode::Error) -> Self { 50 | Error::Serialization(Box::new(err)) 51 | } 52 | } 53 | 54 | pub(crate) type DbResult = Result; 55 | 56 | pub struct Db { 57 | current_page: Page, 58 | reader: File, 59 | writer: File, 60 | last_sync: Instant, 61 | pub(crate) sync_writes: Option, 62 | data: PhantomData, 63 | } 64 | 65 | impl Db { 66 | pub fn builder() -> Builder { 67 | Builder::default() 68 | } 69 | 70 | pub fn from_path(path: impl AsRef) -> io::Result { 71 | let mut file = OpenOptions::new() 72 | .read(true) 73 | .write(true) 74 | .create(true) 75 | .open(&path)?; 76 | 77 | let current_page = if file.seek(io::SeekFrom::End(-(PAGE_SIZE as i64))).is_ok() { 78 | let mut buf = vec![0; PAGE_SIZE]; 79 | file.read_exact(&mut buf)?; 80 | file.seek(io::SeekFrom::End(-(PAGE_SIZE as i64)))?; 81 | Page::from_bytes(buf) 82 | } else { 83 | file.seek(io::SeekFrom::End(0))?; 84 | Page::new() 85 | }; 86 | 87 | Ok(Self { 88 | current_page, 89 | reader: File::open(&path)?, 90 | writer: file, 91 | last_sync: Instant::now(), 92 | sync_writes: Some(Duration::from_secs(0)), 93 | data: PhantomData, 94 | }) 95 | } 96 | 97 | pub fn insert(&mut self, row: T) -> DbResult<()> { 98 | self.current_page.insert(row)?; 99 | 100 | self.writer.write_all( 101 | &[ 102 | self.current_page.as_ref(), 103 | &vec![0; PAGE_SIZE - self.current_page.len()], 104 | ] 105 | .concat(), 106 | )?; 107 | 108 | match self.sync_writes { 109 | Some(interval) if self.last_sync.elapsed() > interval => { 110 | self.writer.sync_data()?; 111 | self.last_sync = Instant::now(); 112 | } 113 | _ => {} 114 | } 115 | 116 | if self.current_page.available_rows() == 0 { 117 | self.current_page = Page::new(); 118 | } else { 119 | self.writer.seek(io::SeekFrom::End(-(PAGE_SIZE as i64)))?; 120 | } 121 | 122 | Ok(()) 123 | } 124 | 125 | pub fn lock_writes(&mut self) -> DbResult { 126 | let fd = self.writer.as_raw_fd(); 127 | match unsafe { libc::flock(fd, libc::LOCK_EX) } { 128 | 0 => Ok(LockHandle { fd }), 129 | _ => Err(io::Error::new(io::ErrorKind::Other, "couldn't acquire lock").into()), 130 | } 131 | } 132 | 133 | fn pages(&mut self) -> impl Iterator> + '_ { 134 | let mut cursor = 0; 135 | iter::from_fn(move || { 136 | let offset = (cursor * PAGE_SIZE) as u64; 137 | 138 | if self.reader.seek(io::SeekFrom::Start(offset)).is_err() { 139 | return None; 140 | } 141 | 142 | let mut buf = vec![0; PAGE_SIZE]; 143 | cursor += 1; 144 | match self.reader.read_exact(&mut buf) { 145 | Ok(()) => Some(Page::from_bytes(buf)), 146 | Err(_) => None, 147 | } 148 | }) 149 | } 150 | 151 | fn pages_reverse(&mut self) -> impl Iterator> + '_ { 152 | let mut cursor = 1; 153 | iter::from_fn(move || { 154 | let offset = (cursor * PAGE_SIZE) as i64; 155 | 156 | if self.reader.seek(io::SeekFrom::End(-offset)).is_err() { 157 | return None; 158 | } 159 | 160 | let mut buf = vec![0; PAGE_SIZE]; 161 | cursor += 1; 162 | match self.reader.read_exact(&mut buf) { 163 | Ok(()) => Some(Page::from_bytes(buf)), 164 | Err(_) => None, 165 | } 166 | }) 167 | } 168 | 169 | pub fn rows(&mut self) -> impl Iterator> + '_ { 170 | self.pages().flat_map(|page| { 171 | page.rows() 172 | .map(|row| bitcode::deserialize(row).map_err(|err| err.into())) 173 | .collect::>() 174 | }) 175 | } 176 | 177 | pub fn rows_reverse(&mut self) -> impl Iterator> + '_ { 178 | self.pages_reverse().flat_map(|page| { 179 | page.rows() 180 | .map(|row| bitcode::deserialize(row).map_err(|err| err.into())) 181 | .collect::>() 182 | .into_iter() 183 | .rev() 184 | }) 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | use tempfile::tempdir; 191 | 192 | use super::*; 193 | 194 | #[test] 195 | fn test_db_rows() { 196 | let tmp = tempdir().unwrap(); 197 | let mut db = Db::::from_path(tmp.path().join("test.espora")).unwrap(); 198 | 199 | db.insert(1).unwrap(); 200 | db.insert(2).unwrap(); 201 | db.insert(3).unwrap(); 202 | db.insert(4).unwrap(); 203 | db.insert(5).unwrap(); 204 | 205 | let rows = db.rows().collect::>>().unwrap(); 206 | assert_eq!(vec![1, 2, 3, 4, 5], rows); 207 | } 208 | 209 | #[test] 210 | fn test_db_rows_reverse() { 211 | let tmp = tempdir().unwrap(); 212 | let mut db = Db::::from_path(tmp.path().join("test.espora")).unwrap(); 213 | 214 | db.insert(1).unwrap(); 215 | db.insert(2).unwrap(); 216 | db.insert(3).unwrap(); 217 | db.insert(4).unwrap(); 218 | db.insert(5).unwrap(); 219 | 220 | let rows = db.rows_reverse().collect::>>().unwrap(); 221 | assert_eq!(vec![5, 4, 3, 2, 1], rows); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /espora-db/src/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | marker::PhantomData, 3 | os::fd::AsRawFd, 4 | path::Path, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use async_stream::stream; 9 | use futures::{stream, Stream, StreamExt}; 10 | use serde::{de::DeserializeOwned, Serialize}; 11 | use tokio::{ 12 | fs::{File, OpenOptions}, 13 | io::{self, AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, 14 | sync::oneshot, 15 | task, 16 | }; 17 | 18 | use crate::{ 19 | builder::Builder, 20 | lock::LockHandle, 21 | page::{Page, PAGE_SIZE}, 22 | DbResult, 23 | }; 24 | 25 | pub struct Db { 26 | current_page: Page, 27 | reader: File, 28 | writer: File, 29 | last_sync: Instant, 30 | pub(crate) sync_writes: Option, 31 | data: PhantomData, 32 | } 33 | 34 | impl Db { 35 | pub fn builder() -> Builder { 36 | Builder::default() 37 | } 38 | 39 | pub async fn from_path(path: impl AsRef) -> io::Result { 40 | let mut file = OpenOptions::new() 41 | .read(true) 42 | .write(true) 43 | .create(true) 44 | .open(&path) 45 | .await?; 46 | 47 | let current_page = if file 48 | .seek(io::SeekFrom::End(-(PAGE_SIZE as i64))) 49 | .await 50 | .is_ok() 51 | { 52 | let mut buf = vec![0; PAGE_SIZE]; 53 | file.read_exact(&mut buf).await?; 54 | file.seek(io::SeekFrom::End(-(PAGE_SIZE as i64))).await?; 55 | Page::from_bytes(buf) 56 | } else { 57 | file.seek(io::SeekFrom::End(0)).await?; 58 | Page::new() 59 | }; 60 | 61 | Ok(Self { 62 | current_page, 63 | reader: File::open(&path).await?, 64 | writer: file, 65 | last_sync: Instant::now(), 66 | sync_writes: Some(Duration::from_secs(0)), 67 | data: PhantomData, 68 | }) 69 | } 70 | 71 | pub async fn insert(&mut self, row: T) -> DbResult<()> { 72 | self.current_page.insert(row)?; 73 | 74 | self.writer 75 | .write_all( 76 | &[ 77 | self.current_page.as_ref(), 78 | &vec![0; PAGE_SIZE - self.current_page.len()], 79 | ] 80 | .concat(), 81 | ) 82 | .await?; 83 | 84 | match self.sync_writes { 85 | Some(interval) if self.last_sync.elapsed() > interval => { 86 | self.writer.sync_data().await?; 87 | self.last_sync = Instant::now(); 88 | } 89 | _ => {} 90 | } 91 | 92 | if self.current_page.available_rows() == 0 { 93 | self.current_page = Page::new(); 94 | } else { 95 | self.writer 96 | .seek(io::SeekFrom::End(-(PAGE_SIZE as i64))) 97 | .await?; 98 | } 99 | 100 | Ok(()) 101 | } 102 | 103 | pub async fn lock_writes(&mut self) -> DbResult { 104 | let (tx, rx) = oneshot::channel(); 105 | let fd = self.writer.as_raw_fd(); 106 | task::spawn_blocking(move || match unsafe { libc::flock(fd, libc::LOCK_EX) } { 107 | 0 => tx.send(Ok(LockHandle { fd })), 108 | _ => tx.send(Err(io::Error::new( 109 | io::ErrorKind::Other, 110 | "couldn't acquire lock", 111 | ))), 112 | }); 113 | Ok(rx.await.unwrap()?) 114 | } 115 | 116 | fn pages(&mut self) -> impl Stream> + '_ { 117 | let mut cursor = 0; 118 | stream! { 119 | loop { 120 | let offset = (cursor * PAGE_SIZE) as u64; 121 | 122 | if self.reader.seek(io::SeekFrom::Start(offset)).await.is_err() { 123 | break; 124 | } 125 | 126 | let mut buf = vec![0; PAGE_SIZE]; 127 | cursor += 1; 128 | match self.reader.read_exact(&mut buf).await { 129 | Ok(n) if n > 0 => yield Page::::from_bytes(buf), 130 | _ => break, 131 | } 132 | } 133 | } 134 | } 135 | 136 | fn pages_reverse(&mut self) -> impl Stream> + '_ { 137 | let mut cursor = 1; 138 | stream! { 139 | loop { 140 | let offset = (cursor * PAGE_SIZE) as i64; 141 | 142 | if self.reader.seek(io::SeekFrom::End(-offset)).await.is_err() { 143 | break; 144 | } 145 | 146 | let mut buf = vec![0; PAGE_SIZE]; 147 | cursor += 1; 148 | match self.reader.read_exact(&mut buf).await { 149 | Ok(n) if n > 0 => yield Page::::from_bytes(buf), 150 | _ => break, 151 | } 152 | } 153 | } 154 | } 155 | 156 | pub fn rows(&mut self) -> impl Stream> + '_ { 157 | self.pages().flat_map(|page| { 158 | stream::iter( 159 | page.rows() 160 | .map(|row| bitcode::deserialize(row).map_err(|err| err.into())) 161 | .collect::>(), 162 | ) 163 | }) 164 | } 165 | 166 | pub fn rows_reverse(&mut self) -> impl Stream> + '_ { 167 | self.pages_reverse().flat_map(|page| { 168 | stream::iter( 169 | page.rows() 170 | .map(|row| bitcode::deserialize(row).map_err(|err| err.into())) 171 | .collect::>() 172 | .into_iter() 173 | .rev(), 174 | ) 175 | }) 176 | } 177 | } 178 | 179 | #[cfg(test)] 180 | mod tests { 181 | use futures::TryStreamExt; 182 | use tempfile::tempdir; 183 | 184 | use super::*; 185 | 186 | #[tokio::test] 187 | async fn test_db_rows() { 188 | let tmp = tempdir().unwrap(); 189 | let mut db = Db::::from_path(tmp.path().join("test.espora")) 190 | .await 191 | .unwrap(); 192 | 193 | db.insert(1).await.unwrap(); 194 | db.insert(2).await.unwrap(); 195 | db.insert(3).await.unwrap(); 196 | db.insert(4).await.unwrap(); 197 | db.insert(5).await.unwrap(); 198 | 199 | let rows = db.rows().try_collect::>().await.unwrap(); 200 | assert_eq!(vec![1, 2, 3, 4, 5], rows); 201 | } 202 | 203 | #[tokio::test] 204 | async fn test_db_rows_reverse() { 205 | let tmp = tempdir().unwrap(); 206 | let mut db = Db::::from_path(tmp.path().join("test.espora")) 207 | .await 208 | .unwrap(); 209 | 210 | db.insert(1).await.unwrap(); 211 | db.insert(2).await.unwrap(); 212 | db.insert(3).await.unwrap(); 213 | db.insert(4).await.unwrap(); 214 | db.insert(5).await.unwrap(); 215 | 216 | let rows = db.rows_reverse().try_collect::>().await.unwrap(); 217 | assert_eq!(vec![5, 4, 3, 2, 1], rows); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "async-stream" 22 | version = "0.3.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 25 | dependencies = [ 26 | "async-stream-impl", 27 | "futures-core", 28 | "pin-project-lite", 29 | ] 30 | 31 | [[package]] 32 | name = "async-stream-impl" 33 | version = "0.3.5" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 36 | dependencies = [ 37 | "proc-macro2", 38 | "quote", 39 | "syn", 40 | ] 41 | 42 | [[package]] 43 | name = "async-trait" 44 | version = "0.1.77" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" 47 | dependencies = [ 48 | "proc-macro2", 49 | "quote", 50 | "syn", 51 | ] 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 58 | 59 | [[package]] 60 | name = "axum" 61 | version = "0.7.4" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" 64 | dependencies = [ 65 | "async-trait", 66 | "axum-core", 67 | "bytes", 68 | "futures-util", 69 | "http", 70 | "http-body", 71 | "http-body-util", 72 | "hyper", 73 | "hyper-util", 74 | "itoa", 75 | "matchit", 76 | "memchr", 77 | "mime", 78 | "percent-encoding", 79 | "pin-project-lite", 80 | "rustversion", 81 | "serde", 82 | "serde_json", 83 | "serde_path_to_error", 84 | "serde_urlencoded", 85 | "sync_wrapper", 86 | "tokio", 87 | "tower", 88 | "tower-layer", 89 | "tower-service", 90 | "tracing", 91 | ] 92 | 93 | [[package]] 94 | name = "axum-core" 95 | version = "0.4.3" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" 98 | dependencies = [ 99 | "async-trait", 100 | "bytes", 101 | "futures-util", 102 | "http", 103 | "http-body", 104 | "http-body-util", 105 | "mime", 106 | "pin-project-lite", 107 | "rustversion", 108 | "sync_wrapper", 109 | "tower-layer", 110 | "tower-service", 111 | "tracing", 112 | ] 113 | 114 | [[package]] 115 | name = "axum-unix-socket" 116 | version = "1.2.0" 117 | dependencies = [ 118 | "axum", 119 | "hyper", 120 | "hyper-util", 121 | "tokio", 122 | "tower", 123 | ] 124 | 125 | [[package]] 126 | name = "backtrace" 127 | version = "0.3.69" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 130 | dependencies = [ 131 | "addr2line", 132 | "cc", 133 | "cfg-if", 134 | "libc", 135 | "miniz_oxide", 136 | "object", 137 | "rustc-demangle", 138 | ] 139 | 140 | [[package]] 141 | name = "bitcode" 142 | version = "0.5.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "e8a733ab290c6e6b2859bba3e68fd1a4ca3eee0577f21ea46049a3529e500627" 145 | dependencies = [ 146 | "bitcode_derive", 147 | "bytemuck", 148 | "from_bytes_or_zeroed", 149 | "residua-zigzag", 150 | "serde", 151 | "simdutf8", 152 | ] 153 | 154 | [[package]] 155 | name = "bitcode_derive" 156 | version = "0.5.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "4c25ffc293cbf807499083d016ce3bc0459aaa7fd624c1cc563718a2440cb38a" 159 | dependencies = [ 160 | "packagemerge", 161 | "proc-macro2", 162 | "quote", 163 | "syn", 164 | ] 165 | 166 | [[package]] 167 | name = "bitflags" 168 | version = "1.3.2" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 171 | 172 | [[package]] 173 | name = "bitflags" 174 | version = "2.4.2" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 177 | 178 | [[package]] 179 | name = "bytemuck" 180 | version = "1.14.3" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" 183 | 184 | [[package]] 185 | name = "bytes" 186 | version = "1.5.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 189 | 190 | [[package]] 191 | name = "cc" 192 | version = "1.0.83" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 195 | dependencies = [ 196 | "libc", 197 | ] 198 | 199 | [[package]] 200 | name = "cfg-if" 201 | version = "1.0.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 204 | 205 | [[package]] 206 | name = "deranged" 207 | version = "0.3.11" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 210 | dependencies = [ 211 | "powerfmt", 212 | "serde", 213 | ] 214 | 215 | [[package]] 216 | name = "equivalent" 217 | version = "1.0.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 220 | 221 | [[package]] 222 | name = "errno" 223 | version = "0.3.8" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 226 | dependencies = [ 227 | "libc", 228 | "windows-sys 0.52.0", 229 | ] 230 | 231 | [[package]] 232 | name = "espora-db" 233 | version = "1.0.1" 234 | dependencies = [ 235 | "async-stream", 236 | "bitcode", 237 | "futures", 238 | "libc", 239 | "serde", 240 | "tempfile", 241 | "tokio", 242 | ] 243 | 244 | [[package]] 245 | name = "fastrand" 246 | version = "2.0.1" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 249 | 250 | [[package]] 251 | name = "fnv" 252 | version = "1.0.7" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 255 | 256 | [[package]] 257 | name = "form_urlencoded" 258 | version = "1.2.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 261 | dependencies = [ 262 | "percent-encoding", 263 | ] 264 | 265 | [[package]] 266 | name = "from_bytes_or_zeroed" 267 | version = "0.1.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "3d25934a78435889223e575c7b0fc36a290c5a312e7a7ae901f10587792e142a" 270 | 271 | [[package]] 272 | name = "futures" 273 | version = "0.3.30" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 276 | dependencies = [ 277 | "futures-channel", 278 | "futures-core", 279 | "futures-executor", 280 | "futures-io", 281 | "futures-sink", 282 | "futures-task", 283 | "futures-util", 284 | ] 285 | 286 | [[package]] 287 | name = "futures-channel" 288 | version = "0.3.30" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 291 | dependencies = [ 292 | "futures-core", 293 | "futures-sink", 294 | ] 295 | 296 | [[package]] 297 | name = "futures-core" 298 | version = "0.3.30" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 301 | 302 | [[package]] 303 | name = "futures-executor" 304 | version = "0.3.30" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 307 | dependencies = [ 308 | "futures-core", 309 | "futures-task", 310 | "futures-util", 311 | ] 312 | 313 | [[package]] 314 | name = "futures-io" 315 | version = "0.3.30" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 318 | 319 | [[package]] 320 | name = "futures-macro" 321 | version = "0.3.30" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 324 | dependencies = [ 325 | "proc-macro2", 326 | "quote", 327 | "syn", 328 | ] 329 | 330 | [[package]] 331 | name = "futures-sink" 332 | version = "0.3.30" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 335 | 336 | [[package]] 337 | name = "futures-task" 338 | version = "0.3.30" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 341 | 342 | [[package]] 343 | name = "futures-util" 344 | version = "0.3.30" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 347 | dependencies = [ 348 | "futures-channel", 349 | "futures-core", 350 | "futures-io", 351 | "futures-macro", 352 | "futures-sink", 353 | "futures-task", 354 | "memchr", 355 | "pin-project-lite", 356 | "pin-utils", 357 | "slab", 358 | ] 359 | 360 | [[package]] 361 | name = "gimli" 362 | version = "0.28.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 365 | 366 | [[package]] 367 | name = "h2" 368 | version = "0.4.2" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" 371 | dependencies = [ 372 | "bytes", 373 | "fnv", 374 | "futures-core", 375 | "futures-sink", 376 | "futures-util", 377 | "http", 378 | "indexmap", 379 | "slab", 380 | "tokio", 381 | "tokio-util", 382 | "tracing", 383 | ] 384 | 385 | [[package]] 386 | name = "hashbrown" 387 | version = "0.14.3" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 390 | 391 | [[package]] 392 | name = "hermit-abi" 393 | version = "0.3.6" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" 396 | 397 | [[package]] 398 | name = "http" 399 | version = "1.0.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" 402 | dependencies = [ 403 | "bytes", 404 | "fnv", 405 | "itoa", 406 | ] 407 | 408 | [[package]] 409 | name = "http-body" 410 | version = "1.0.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 413 | dependencies = [ 414 | "bytes", 415 | "http", 416 | ] 417 | 418 | [[package]] 419 | name = "http-body-util" 420 | version = "0.1.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" 423 | dependencies = [ 424 | "bytes", 425 | "futures-util", 426 | "http", 427 | "http-body", 428 | "pin-project-lite", 429 | ] 430 | 431 | [[package]] 432 | name = "httparse" 433 | version = "1.8.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 436 | 437 | [[package]] 438 | name = "httpdate" 439 | version = "1.0.3" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 442 | 443 | [[package]] 444 | name = "humantime" 445 | version = "2.1.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 448 | 449 | [[package]] 450 | name = "hyper" 451 | version = "1.1.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" 454 | dependencies = [ 455 | "bytes", 456 | "futures-channel", 457 | "futures-util", 458 | "h2", 459 | "http", 460 | "http-body", 461 | "httparse", 462 | "httpdate", 463 | "itoa", 464 | "pin-project-lite", 465 | "tokio", 466 | "want", 467 | ] 468 | 469 | [[package]] 470 | name = "hyper-util" 471 | version = "0.1.3" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 474 | dependencies = [ 475 | "bytes", 476 | "futures-channel", 477 | "futures-util", 478 | "http", 479 | "http-body", 480 | "hyper", 481 | "pin-project-lite", 482 | "socket2", 483 | "tokio", 484 | "tower", 485 | "tower-service", 486 | "tracing", 487 | ] 488 | 489 | [[package]] 490 | name = "indexmap" 491 | version = "2.2.3" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" 494 | dependencies = [ 495 | "equivalent", 496 | "hashbrown", 497 | ] 498 | 499 | [[package]] 500 | name = "itertools" 501 | version = "0.4.19" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" 504 | 505 | [[package]] 506 | name = "itoa" 507 | version = "1.0.10" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 510 | 511 | [[package]] 512 | name = "libc" 513 | version = "0.2.153" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 516 | 517 | [[package]] 518 | name = "linux-raw-sys" 519 | version = "0.4.13" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 522 | 523 | [[package]] 524 | name = "lock_api" 525 | version = "0.4.11" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 528 | dependencies = [ 529 | "autocfg", 530 | "scopeguard", 531 | ] 532 | 533 | [[package]] 534 | name = "log" 535 | version = "0.4.20" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 538 | 539 | [[package]] 540 | name = "matchit" 541 | version = "0.7.3" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 544 | 545 | [[package]] 546 | name = "memchr" 547 | version = "2.7.1" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 550 | 551 | [[package]] 552 | name = "mime" 553 | version = "0.3.17" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 556 | 557 | [[package]] 558 | name = "miniz_oxide" 559 | version = "0.7.2" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 562 | dependencies = [ 563 | "adler", 564 | ] 565 | 566 | [[package]] 567 | name = "mio" 568 | version = "0.8.10" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" 571 | dependencies = [ 572 | "libc", 573 | "wasi", 574 | "windows-sys 0.48.0", 575 | ] 576 | 577 | [[package]] 578 | name = "num-conv" 579 | version = "0.1.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 582 | 583 | [[package]] 584 | name = "num_cpus" 585 | version = "1.16.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 588 | dependencies = [ 589 | "hermit-abi", 590 | "libc", 591 | ] 592 | 593 | [[package]] 594 | name = "object" 595 | version = "0.32.2" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 598 | dependencies = [ 599 | "memchr", 600 | ] 601 | 602 | [[package]] 603 | name = "once_cell" 604 | version = "1.19.0" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 607 | 608 | [[package]] 609 | name = "packagemerge" 610 | version = "0.1.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "0efcf6ee55f8f7a24333bc8d1dd0e541a6cedf903dbc07ae6479d7f8ff32ed08" 613 | dependencies = [ 614 | "itertools", 615 | ] 616 | 617 | [[package]] 618 | name = "parking_lot" 619 | version = "0.12.1" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 622 | dependencies = [ 623 | "lock_api", 624 | "parking_lot_core", 625 | ] 626 | 627 | [[package]] 628 | name = "parking_lot_core" 629 | version = "0.9.9" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 632 | dependencies = [ 633 | "cfg-if", 634 | "libc", 635 | "redox_syscall", 636 | "smallvec", 637 | "windows-targets 0.48.5", 638 | ] 639 | 640 | [[package]] 641 | name = "percent-encoding" 642 | version = "2.3.1" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 645 | 646 | [[package]] 647 | name = "pin-project" 648 | version = "1.1.4" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" 651 | dependencies = [ 652 | "pin-project-internal", 653 | ] 654 | 655 | [[package]] 656 | name = "pin-project-internal" 657 | version = "1.1.4" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" 660 | dependencies = [ 661 | "proc-macro2", 662 | "quote", 663 | "syn", 664 | ] 665 | 666 | [[package]] 667 | name = "pin-project-lite" 668 | version = "0.2.13" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 671 | 672 | [[package]] 673 | name = "pin-utils" 674 | version = "0.1.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 677 | 678 | [[package]] 679 | name = "powerfmt" 680 | version = "0.2.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 683 | 684 | [[package]] 685 | name = "proc-macro2" 686 | version = "1.0.78" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 689 | dependencies = [ 690 | "unicode-ident", 691 | ] 692 | 693 | [[package]] 694 | name = "quote" 695 | version = "1.0.35" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 698 | dependencies = [ 699 | "proc-macro2", 700 | ] 701 | 702 | [[package]] 703 | name = "redox_syscall" 704 | version = "0.4.1" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 707 | dependencies = [ 708 | "bitflags 1.3.2", 709 | ] 710 | 711 | [[package]] 712 | name = "residua-zigzag" 713 | version = "0.1.0" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "2b37805477eee599a61753230f511ae94d737f69b536e468e294723ad5f1b75f" 716 | 717 | [[package]] 718 | name = "rinha" 719 | version = "1.2.0" 720 | dependencies = [ 721 | "serde", 722 | "time", 723 | ] 724 | 725 | [[package]] 726 | name = "rinha-app" 727 | version = "1.2.0" 728 | dependencies = [ 729 | "tokio", 730 | ] 731 | 732 | [[package]] 733 | name = "rinha-espora-embedded" 734 | version = "1.2.0" 735 | dependencies = [ 736 | "axum", 737 | "axum-unix-socket", 738 | "espora-db", 739 | "futures", 740 | "humantime", 741 | "rinha", 742 | "serde", 743 | "serde_json", 744 | "tokio", 745 | ] 746 | 747 | [[package]] 748 | name = "rinha-espora-server" 749 | version = "1.2.0" 750 | dependencies = [ 751 | "axum", 752 | "axum-unix-socket", 753 | "espora-db", 754 | "humantime", 755 | "rinha", 756 | "serde", 757 | "serde_json", 758 | "tokio", 759 | ] 760 | 761 | [[package]] 762 | name = "rinha-load-balancer" 763 | version = "1.2.0" 764 | dependencies = [ 765 | "axum", 766 | "hyper-util", 767 | "tokio", 768 | ] 769 | 770 | [[package]] 771 | name = "rinha-load-balancer-tcp" 772 | version = "1.2.0" 773 | dependencies = [ 774 | "tokio", 775 | ] 776 | 777 | [[package]] 778 | name = "rustc-demangle" 779 | version = "0.1.23" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 782 | 783 | [[package]] 784 | name = "rustix" 785 | version = "0.38.31" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" 788 | dependencies = [ 789 | "bitflags 2.4.2", 790 | "errno", 791 | "libc", 792 | "linux-raw-sys", 793 | "windows-sys 0.52.0", 794 | ] 795 | 796 | [[package]] 797 | name = "rustversion" 798 | version = "1.0.14" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 801 | 802 | [[package]] 803 | name = "ryu" 804 | version = "1.0.16" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 807 | 808 | [[package]] 809 | name = "scopeguard" 810 | version = "1.2.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 813 | 814 | [[package]] 815 | name = "serde" 816 | version = "1.0.196" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 819 | dependencies = [ 820 | "serde_derive", 821 | ] 822 | 823 | [[package]] 824 | name = "serde_derive" 825 | version = "1.0.196" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 828 | dependencies = [ 829 | "proc-macro2", 830 | "quote", 831 | "syn", 832 | ] 833 | 834 | [[package]] 835 | name = "serde_json" 836 | version = "1.0.113" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" 839 | dependencies = [ 840 | "itoa", 841 | "ryu", 842 | "serde", 843 | ] 844 | 845 | [[package]] 846 | name = "serde_path_to_error" 847 | version = "0.1.15" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" 850 | dependencies = [ 851 | "itoa", 852 | "serde", 853 | ] 854 | 855 | [[package]] 856 | name = "serde_urlencoded" 857 | version = "0.7.1" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 860 | dependencies = [ 861 | "form_urlencoded", 862 | "itoa", 863 | "ryu", 864 | "serde", 865 | ] 866 | 867 | [[package]] 868 | name = "signal-hook-registry" 869 | version = "1.4.1" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 872 | dependencies = [ 873 | "libc", 874 | ] 875 | 876 | [[package]] 877 | name = "simdutf8" 878 | version = "0.1.4" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" 881 | 882 | [[package]] 883 | name = "slab" 884 | version = "0.4.9" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 887 | dependencies = [ 888 | "autocfg", 889 | ] 890 | 891 | [[package]] 892 | name = "smallvec" 893 | version = "1.13.1" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 896 | 897 | [[package]] 898 | name = "socket2" 899 | version = "0.5.5" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 902 | dependencies = [ 903 | "libc", 904 | "windows-sys 0.48.0", 905 | ] 906 | 907 | [[package]] 908 | name = "syn" 909 | version = "2.0.48" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 912 | dependencies = [ 913 | "proc-macro2", 914 | "quote", 915 | "unicode-ident", 916 | ] 917 | 918 | [[package]] 919 | name = "sync_wrapper" 920 | version = "0.1.2" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 923 | 924 | [[package]] 925 | name = "tempfile" 926 | version = "3.10.1" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 929 | dependencies = [ 930 | "cfg-if", 931 | "fastrand", 932 | "rustix", 933 | "windows-sys 0.52.0", 934 | ] 935 | 936 | [[package]] 937 | name = "time" 938 | version = "0.3.34" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" 941 | dependencies = [ 942 | "deranged", 943 | "itoa", 944 | "num-conv", 945 | "powerfmt", 946 | "serde", 947 | "time-core", 948 | "time-macros", 949 | ] 950 | 951 | [[package]] 952 | name = "time-core" 953 | version = "0.1.2" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 956 | 957 | [[package]] 958 | name = "time-macros" 959 | version = "0.2.17" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" 962 | dependencies = [ 963 | "num-conv", 964 | "time-core", 965 | ] 966 | 967 | [[package]] 968 | name = "tokio" 969 | version = "1.36.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" 972 | dependencies = [ 973 | "backtrace", 974 | "bytes", 975 | "libc", 976 | "mio", 977 | "num_cpus", 978 | "parking_lot", 979 | "pin-project-lite", 980 | "signal-hook-registry", 981 | "socket2", 982 | "tokio-macros", 983 | "windows-sys 0.48.0", 984 | ] 985 | 986 | [[package]] 987 | name = "tokio-macros" 988 | version = "2.2.0" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 991 | dependencies = [ 992 | "proc-macro2", 993 | "quote", 994 | "syn", 995 | ] 996 | 997 | [[package]] 998 | name = "tokio-util" 999 | version = "0.7.10" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1002 | dependencies = [ 1003 | "bytes", 1004 | "futures-core", 1005 | "futures-sink", 1006 | "pin-project-lite", 1007 | "tokio", 1008 | "tracing", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "tower" 1013 | version = "0.4.13" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1016 | dependencies = [ 1017 | "futures-core", 1018 | "futures-util", 1019 | "pin-project", 1020 | "pin-project-lite", 1021 | "tokio", 1022 | "tower-layer", 1023 | "tower-service", 1024 | "tracing", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "tower-layer" 1029 | version = "0.3.2" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1032 | 1033 | [[package]] 1034 | name = "tower-service" 1035 | version = "0.3.2" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1038 | 1039 | [[package]] 1040 | name = "tracing" 1041 | version = "0.1.40" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1044 | dependencies = [ 1045 | "log", 1046 | "pin-project-lite", 1047 | "tracing-core", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "tracing-core" 1052 | version = "0.1.32" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1055 | dependencies = [ 1056 | "once_cell", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "try-lock" 1061 | version = "0.2.5" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1064 | 1065 | [[package]] 1066 | name = "unicode-ident" 1067 | version = "1.0.12" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1070 | 1071 | [[package]] 1072 | name = "want" 1073 | version = "0.3.1" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1076 | dependencies = [ 1077 | "try-lock", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "wasi" 1082 | version = "0.11.0+wasi-snapshot-preview1" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1085 | 1086 | [[package]] 1087 | name = "windows-sys" 1088 | version = "0.48.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1091 | dependencies = [ 1092 | "windows-targets 0.48.5", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "windows-sys" 1097 | version = "0.52.0" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1100 | dependencies = [ 1101 | "windows-targets 0.52.4", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "windows-targets" 1106 | version = "0.48.5" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1109 | dependencies = [ 1110 | "windows_aarch64_gnullvm 0.48.5", 1111 | "windows_aarch64_msvc 0.48.5", 1112 | "windows_i686_gnu 0.48.5", 1113 | "windows_i686_msvc 0.48.5", 1114 | "windows_x86_64_gnu 0.48.5", 1115 | "windows_x86_64_gnullvm 0.48.5", 1116 | "windows_x86_64_msvc 0.48.5", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "windows-targets" 1121 | version = "0.52.4" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 1124 | dependencies = [ 1125 | "windows_aarch64_gnullvm 0.52.4", 1126 | "windows_aarch64_msvc 0.52.4", 1127 | "windows_i686_gnu 0.52.4", 1128 | "windows_i686_msvc 0.52.4", 1129 | "windows_x86_64_gnu 0.52.4", 1130 | "windows_x86_64_gnullvm 0.52.4", 1131 | "windows_x86_64_msvc 0.52.4", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "windows_aarch64_gnullvm" 1136 | version = "0.48.5" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1139 | 1140 | [[package]] 1141 | name = "windows_aarch64_gnullvm" 1142 | version = "0.52.4" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 1145 | 1146 | [[package]] 1147 | name = "windows_aarch64_msvc" 1148 | version = "0.48.5" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1151 | 1152 | [[package]] 1153 | name = "windows_aarch64_msvc" 1154 | version = "0.52.4" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 1157 | 1158 | [[package]] 1159 | name = "windows_i686_gnu" 1160 | version = "0.48.5" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1163 | 1164 | [[package]] 1165 | name = "windows_i686_gnu" 1166 | version = "0.52.4" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 1169 | 1170 | [[package]] 1171 | name = "windows_i686_msvc" 1172 | version = "0.48.5" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1175 | 1176 | [[package]] 1177 | name = "windows_i686_msvc" 1178 | version = "0.52.4" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 1181 | 1182 | [[package]] 1183 | name = "windows_x86_64_gnu" 1184 | version = "0.48.5" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1187 | 1188 | [[package]] 1189 | name = "windows_x86_64_gnu" 1190 | version = "0.52.4" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 1193 | 1194 | [[package]] 1195 | name = "windows_x86_64_gnullvm" 1196 | version = "0.48.5" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1199 | 1200 | [[package]] 1201 | name = "windows_x86_64_gnullvm" 1202 | version = "0.52.4" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 1205 | 1206 | [[package]] 1207 | name = "windows_x86_64_msvc" 1208 | version = "0.48.5" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1211 | 1212 | [[package]] 1213 | name = "windows_x86_64_msvc" 1214 | version = "0.52.4" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 1217 | --------------------------------------------------------------------------------