use callback based pruning
This commit is contained in:
parent
74966678bd
commit
9904ba2cfc
8 changed files with 136 additions and 223 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
/target
|
/target
|
||||||
.env
|
.env
|
||||||
cache
|
cache
|
||||||
|
flamegraph*.svg
|
||||||
|
perf.data*
|
152
Cargo.lock
generated
152
Cargo.lock
generated
|
@ -28,7 +28,7 @@ dependencies = [
|
||||||
"actix-tls",
|
"actix-tls",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"ahash 0.7.2",
|
"ahash 0.7.2",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"brotli2",
|
"brotli2",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -54,7 +54,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"sha-1 0.9.4",
|
"sha-1",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"time 0.2.26",
|
"time 0.2.26",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -268,15 +268,6 @@ version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
@ -298,34 +289,13 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-buffer"
|
|
||||||
version = "0.7.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
|
||||||
dependencies = [
|
|
||||||
"block-padding",
|
|
||||||
"byte-tools",
|
|
||||||
"byteorder",
|
|
||||||
"generic-array 0.12.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.4",
|
"generic-array",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-padding"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
|
||||||
dependencies = [
|
|
||||||
"byte-tools",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -354,18 +324,6 @@ version = "3.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
|
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byte-tools"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -511,22 +469,13 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "digest"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array 0.12.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.4",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -556,12 +505,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fake-simd"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.20"
|
version = "1.0.20"
|
||||||
|
@ -682,15 +625,6 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.12.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.4"
|
version = "0.14.4"
|
||||||
|
@ -758,12 +692,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
@ -968,7 +896,7 @@ version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -986,9 +914,9 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"simple_logger",
|
"simple_logger",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
"ssri",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
@ -1099,12 +1027,6 @@ version = "1.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "opaque-debug"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -1324,7 +1246,7 @@ version = "0.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124"
|
checksum = "2296f2fac53979e8ccbc4a1136b25dcefd37be9ed7e4a1f6b05a6029c84ff124"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -1384,7 +1306,7 @@ version = "0.19.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
"sct",
|
"sct",
|
||||||
|
@ -1471,29 +1393,17 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha-1"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer 0.7.3",
|
|
||||||
"digest 0.8.1",
|
|
||||||
"fake-simd",
|
|
||||||
"opaque-debug 0.2.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
|
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer 0.9.0",
|
"block-buffer",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpuid-bool",
|
"cpuid-bool",
|
||||||
"digest 0.9.0",
|
"digest",
|
||||||
"opaque-debug 0.3.0",
|
"opaque-debug",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1502,18 +1412,6 @@ version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
|
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha2"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer 0.7.3",
|
|
||||||
"digest 0.8.1",
|
|
||||||
"fake-simd",
|
|
||||||
"opaque-debug 0.2.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -1575,22 +1473,6 @@ version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ssri"
|
|
||||||
version = "5.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e875e58f12c33f832d09cff94b079aee84a48d8188d7a7132b3434358afb70c9"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.10.1",
|
|
||||||
"digest 0.8.1",
|
|
||||||
"hex",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"sha-1 0.8.2",
|
|
||||||
"sha2",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "standback"
|
name = "standback"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
@ -1820,6 +1702,18 @@ dependencies = [
|
||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-stream"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.6.6"
|
version = "0.6.6"
|
||||||
|
|
|
@ -29,9 +29,9 @@ serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
simple_logger = "1"
|
simple_logger = "1"
|
||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
ssri = "5"
|
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tokio = { version = "1", features = [ "full", "parking_lot" ] }
|
tokio = { version = "1", features = [ "full", "parking_lot" ] }
|
||||||
|
tokio-stream = { version = "0.1", features = [ "sync" ] }
|
||||||
tokio-util = { version = "0.6", features = [ "codec" ] }
|
tokio-util = { version = "0.6", features = [ "codec" ] }
|
||||||
url = { version = "2", features = [ "serde" ] }
|
url = { version = "2", features = [ "serde" ] }
|
||||||
|
|
||||||
|
|
131
src/cache/fs.rs
vendored
131
src/cache/fs.rs
vendored
|
@ -1,19 +1,19 @@
|
||||||
use actix_web::error::PayloadError;
|
use actix_web::error::PayloadError;
|
||||||
|
use bytes::Buf;
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use log::debug;
|
use log::{debug, error};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::time::Duration;
|
|
||||||
use tokio::fs::{create_dir_all, remove_file, File};
|
use tokio::fs::{create_dir_all, remove_file, File};
|
||||||
use tokio::io::{AsyncRead, AsyncWriteExt, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncWriteExt, ReadBuf};
|
||||||
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
use tokio::sync::watch::{channel, Receiver};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tokio::time::Interval;
|
use tokio_stream::wrappers::WatchStream;
|
||||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||||
|
|
||||||
use super::{BoxedImageStream, CacheStream, CacheStreamItem};
|
use super::{BoxedImageStream, CacheStream, CacheStreamItem};
|
||||||
|
@ -34,17 +34,17 @@ use super::{BoxedImageStream, CacheStream, CacheStreamItem};
|
||||||
/// We effectively use `WRITING_STATUS` as a status relay to ensure concurrent
|
/// We effectively use `WRITING_STATUS` as a status relay to ensure concurrent
|
||||||
/// reads to the file while it's being written to will wait for writing to be
|
/// reads to the file while it's being written to will wait for writing to be
|
||||||
/// completed.
|
/// completed.
|
||||||
static WRITING_STATUS: Lazy<RwLock<HashMap<PathBuf, Arc<CacheStatus>>>> =
|
static WRITING_STATUS: Lazy<RwLock<HashMap<PathBuf, Receiver<WritingStatus>>>> =
|
||||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
/// Tries to read from the file, returning a byte stream if it exists
|
/// Tries to read from the file, returning a byte stream if it exists
|
||||||
pub async fn read_file(path: &Path) -> Option<Result<CacheStream, std::io::Error>> {
|
pub async fn read_file(path: &Path) -> Option<Result<CacheStream, std::io::Error>> {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
let status = WRITING_STATUS.read().await.get(path).map(Arc::clone);
|
let status = WRITING_STATUS.read().await.get(path).map(Clone::clone);
|
||||||
|
|
||||||
if let Some(status) = status {
|
if let Some(status) = status {
|
||||||
Some(
|
Some(
|
||||||
ConcurrentFsStream::new(path, status)
|
ConcurrentFsStream::new(path, WatchStream::new(status))
|
||||||
.await
|
.await
|
||||||
.map(CacheStream::Concurrent),
|
.map(CacheStream::Concurrent),
|
||||||
)
|
)
|
||||||
|
@ -65,27 +65,38 @@ pub async fn read_file(path: &Path) -> Option<Result<CacheStream, std::io::Error
|
||||||
pub async fn write_file(
|
pub async fn write_file(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
mut byte_stream: BoxedImageStream,
|
mut byte_stream: BoxedImageStream,
|
||||||
|
notifier: UnboundedSender<u64>,
|
||||||
) -> Result<CacheStream, std::io::Error> {
|
) -> Result<CacheStream, std::io::Error> {
|
||||||
let done_writing_flag = Arc::new(CacheStatus::new());
|
let (tx, rx) = channel(WritingStatus::NotDone);
|
||||||
|
|
||||||
let mut file = {
|
let mut file = {
|
||||||
let mut write_lock = WRITING_STATUS.write().await;
|
let mut write_lock = WRITING_STATUS.write().await;
|
||||||
let parent = path.parent().unwrap();
|
let parent = path.parent().unwrap();
|
||||||
create_dir_all(parent).await?;
|
create_dir_all(parent).await?;
|
||||||
let file = File::create(path).await?; // we need to make sure the file exists and is truncated.
|
let file = File::create(path).await?; // we need to make sure the file exists and is truncated.
|
||||||
write_lock.insert(path.to_path_buf(), Arc::clone(&done_writing_flag));
|
write_lock.insert(path.to_path_buf(), rx.clone());
|
||||||
file
|
file
|
||||||
};
|
};
|
||||||
|
|
||||||
let write_flag = Arc::clone(&done_writing_flag);
|
|
||||||
// need owned variant because async lifetime
|
// need owned variant because async lifetime
|
||||||
let path_buf = path.to_path_buf();
|
let path_buf = path.to_path_buf();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let path_buf = path_buf; // moves path buf into async
|
let path_buf = path_buf; // moves path buf into async
|
||||||
let mut errored = false;
|
let mut errored = false;
|
||||||
|
let mut bytes_written: u64 = 0;
|
||||||
while let Some(bytes) = byte_stream.next().await {
|
while let Some(bytes) = byte_stream.next().await {
|
||||||
if let Ok(bytes) = bytes {
|
if let Ok(mut bytes) = bytes {
|
||||||
file.write_all(&bytes).await?
|
loop {
|
||||||
|
match file.write(&bytes).await? {
|
||||||
|
0 => break,
|
||||||
|
n => {
|
||||||
|
bytes.advance(n);
|
||||||
|
// We don't care if we don't have receivers
|
||||||
|
bytes_written += n as u64;
|
||||||
|
let _ = tx.send(WritingStatus::NotDone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errored = true;
|
errored = true;
|
||||||
break;
|
break;
|
||||||
|
@ -105,35 +116,45 @@ pub async fn write_file(
|
||||||
let mut write_lock = WRITING_STATUS.write().await;
|
let mut write_lock = WRITING_STATUS.write().await;
|
||||||
// This needs to be written atomically with the write lock, else
|
// This needs to be written atomically with the write lock, else
|
||||||
// it's possible we have an inconsistent state
|
// it's possible we have an inconsistent state
|
||||||
|
//
|
||||||
|
// We don't really care if we have no receivers
|
||||||
if errored {
|
if errored {
|
||||||
write_flag.store(WritingStatus::Error);
|
let _ = tx.send(WritingStatus::Error);
|
||||||
} else {
|
} else {
|
||||||
write_flag.store(WritingStatus::Done);
|
let _ = tx.send(WritingStatus::Done);
|
||||||
}
|
}
|
||||||
write_lock.remove(&path_buf);
|
write_lock.remove(&path_buf);
|
||||||
|
|
||||||
|
// notify
|
||||||
|
if let Err(e) = notifier.send(bytes_written) {
|
||||||
|
error!(
|
||||||
|
"Failed to notify cache of new entry size: {}. Cache no longer can prune FS!",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// We don't ever check this, so the return value doesn't matter
|
// We don't ever check this, so the return value doesn't matter
|
||||||
Ok::<_, std::io::Error>(())
|
Ok::<_, std::io::Error>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(CacheStream::Concurrent(
|
Ok(CacheStream::Concurrent(
|
||||||
ConcurrentFsStream::new(path, done_writing_flag).await?,
|
ConcurrentFsStream::new(path, WatchStream::new(rx)).await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ConcurrentFsStream {
|
pub struct ConcurrentFsStream {
|
||||||
file: Pin<Box<File>>,
|
file: Pin<Box<File>>,
|
||||||
sleep: Pin<Box<Interval>>,
|
receiver: Pin<Box<WatchStream<WritingStatus>>>,
|
||||||
is_file_done_writing: Arc<CacheStatus>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConcurrentFsStream {
|
impl ConcurrentFsStream {
|
||||||
async fn new(path: &Path, is_done: Arc<CacheStatus>) -> Result<Self, std::io::Error> {
|
async fn new(
|
||||||
|
path: &Path,
|
||||||
|
receiver: WatchStream<WritingStatus>,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
file: Box::pin(File::open(path).await?),
|
file: Box::pin(File::open(path).await?),
|
||||||
// 0.5ms
|
receiver: Box::pin(receiver),
|
||||||
sleep: Box::pin(tokio::time::interval(Duration::from_micros(250))),
|
|
||||||
is_file_done_writing: is_done,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,30 +175,29 @@ impl Stream for ConcurrentFsStream {
|
||||||
type Item = CacheStreamItem;
|
type Item = CacheStreamItem;
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
let status = self.is_file_done_writing.load();
|
match self.receiver.as_mut().poll_next_unpin(cx) {
|
||||||
|
Poll::Ready(status) => {
|
||||||
let mut bytes = [0; 1460].to_vec();
|
let mut bytes = [0; 1460].to_vec();
|
||||||
let mut buffer = ReadBuf::new(&mut bytes);
|
let mut buffer = ReadBuf::new(&mut bytes);
|
||||||
let polled_result = self.file.as_mut().poll_read(cx, &mut buffer);
|
let polled_result = self.file.as_mut().poll_read(cx, &mut buffer);
|
||||||
let filled = buffer.filled().len();
|
let filled = buffer.filled().len();
|
||||||
match (status, filled) {
|
match (status, filled) {
|
||||||
// Prematurely reached EOF, schedule a poll in the future
|
(Some(WritingStatus::NotDone), 0) => Poll::Pending,
|
||||||
(WritingStatus::NotDone, 0) => {
|
// We got an error, abort the read.
|
||||||
let _ = self.sleep.as_mut().poll_tick(cx);
|
(Some(WritingStatus::Error), _) => Poll::Ready(Some(Err(UpstreamError))),
|
||||||
Poll::Pending
|
_ => {
|
||||||
}
|
bytes.truncate(filled);
|
||||||
// We got an error, abort the read.
|
polled_result.map(|_| {
|
||||||
(WritingStatus::Error, _) => Poll::Ready(Some(Err(UpstreamError))),
|
if bytes.is_empty() {
|
||||||
_ => {
|
None
|
||||||
bytes.truncate(filled);
|
} else {
|
||||||
polled_result.map(|_| {
|
Some(Ok(bytes.into()))
|
||||||
if bytes.is_empty() {
|
}
|
||||||
None
|
})
|
||||||
} else {
|
|
||||||
Some(Ok(bytes.into()))
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,26 +209,7 @@ impl From<UpstreamError> for actix_web::Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CacheStatus(AtomicU8);
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
||||||
impl CacheStatus {
|
|
||||||
#[inline]
|
|
||||||
const fn new() -> Self {
|
|
||||||
Self(AtomicU8::new(WritingStatus::NotDone as u8))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn store(&self, status: WritingStatus) {
|
|
||||||
self.0.store(status as u8, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn load(&self) -> WritingStatus {
|
|
||||||
self.0.load(Ordering::Acquire).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum WritingStatus {
|
enum WritingStatus {
|
||||||
NotDone = 0,
|
NotDone = 0,
|
||||||
Done,
|
Done,
|
||||||
|
|
3
src/cache/generational.rs
vendored
3
src/cache/generational.rs
vendored
|
@ -239,4 +239,7 @@ impl Cache for GenerationalCache {
|
||||||
|
|
||||||
self.get(&key).await.unwrap()
|
self.get(&key).await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noop
|
||||||
|
async fn increase_usage(&mut self, _amt: u64) {}
|
||||||
}
|
}
|
||||||
|
|
41
src/cache/low_mem.rs
vendored
41
src/cache/low_mem.rs
vendored
|
@ -1,10 +1,11 @@
|
||||||
//! Low memory caching stuff
|
//! Low memory caching stuff
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::warn;
|
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use super::{BoxedImageStream, Cache, CacheError, CacheKey, CacheStream, ImageMetadata};
|
use super::{BoxedImageStream, Cache, CacheError, CacheKey, CacheStream, ImageMetadata};
|
||||||
|
|
||||||
|
@ -13,16 +14,37 @@ pub struct LowMemCache {
|
||||||
disk_path: PathBuf,
|
disk_path: PathBuf,
|
||||||
disk_max_size: u64,
|
disk_max_size: u64,
|
||||||
disk_cur_size: u64,
|
disk_cur_size: u64,
|
||||||
|
master_sender: UnboundedSender<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LowMemCache {
|
impl LowMemCache {
|
||||||
pub fn new(disk_max_size: u64, disk_path: PathBuf) -> Self {
|
pub fn new(disk_max_size: u64, disk_path: PathBuf) -> Arc<RwLock<Box<dyn Cache>>> {
|
||||||
Self {
|
let (tx, mut rx) = unbounded_channel();
|
||||||
|
let new_self: Arc<RwLock<Box<dyn Cache>>> = Arc::new(RwLock::new(Box::new(Self {
|
||||||
on_disk: LruCache::unbounded(),
|
on_disk: LruCache::unbounded(),
|
||||||
disk_path,
|
disk_path,
|
||||||
disk_max_size,
|
disk_max_size,
|
||||||
disk_cur_size: 0,
|
disk_cur_size: 0,
|
||||||
}
|
master_sender: tx,
|
||||||
|
})));
|
||||||
|
|
||||||
|
let new_self_0 = Arc::clone(&new_self);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let new_size = match rx.recv().await {
|
||||||
|
Some(v) => v,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
new_self_0.write().await.increase_usage(new_size).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new_self.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prune(&mut self) {
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,13 +69,16 @@ impl Cache for LowMemCache {
|
||||||
) -> Result<(CacheStream, &ImageMetadata), CacheError> {
|
) -> Result<(CacheStream, &ImageMetadata), CacheError> {
|
||||||
let path = self.disk_path.clone().join(PathBuf::from(key.clone()));
|
let path = self.disk_path.clone().join(PathBuf::from(key.clone()));
|
||||||
self.on_disk.put(key.clone(), metadata);
|
self.on_disk.put(key.clone(), metadata);
|
||||||
super::fs::write_file(&path, image)
|
super::fs::write_file(&path, image, self.master_sender.clone())
|
||||||
.await
|
.await
|
||||||
.map(move |stream| (stream, self.on_disk.get(&key).unwrap()))
|
.map(move |stream| (stream, self.on_disk.get(&key).unwrap()))
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prune(&mut self) {
|
async fn increase_usage(&mut self, amt: u64) {
|
||||||
warn!("Trimming has not been implemented yet. Cache is unbounded!");
|
self.disk_cur_size += amt;
|
||||||
|
if self.disk_cur_size > self.disk_max_size {
|
||||||
|
self.prune().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
5
src/cache/mod.rs
vendored
5
src/cache/mod.rs
vendored
|
@ -10,7 +10,6 @@ use bytes::Bytes;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use fs::ConcurrentFsStream;
|
use fs::ConcurrentFsStream;
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use log::debug;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use fs::UpstreamError;
|
pub use fs::UpstreamError;
|
||||||
|
@ -159,9 +158,7 @@ pub trait Cache: Send + Sync {
|
||||||
metadata: ImageMetadata,
|
metadata: ImageMetadata,
|
||||||
) -> Result<(CacheStream, &ImageMetadata), CacheError>;
|
) -> Result<(CacheStream, &ImageMetadata), CacheError>;
|
||||||
|
|
||||||
async fn prune(&mut self) {
|
async fn increase_usage(&mut self, amt: u64);
|
||||||
debug!("Would trim but cache does not implement trimming!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CacheStream {
|
pub enum CacheStream {
|
||||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -120,27 +120,18 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let cache: Box<dyn Cache> = if low_mem_mode {
|
let cache: Arc<TokioRwLock<Box<dyn Cache>>> = if low_mem_mode {
|
||||||
Box::new(LowMemCache::new(disk_quota, cache_path.clone()))
|
LowMemCache::new(disk_quota, cache_path.clone())
|
||||||
} else {
|
} else {
|
||||||
Box::new(GenerationalCache::new(
|
Arc::new(TokioRwLock::new(Box::new(GenerationalCache::new(
|
||||||
memory_max_size,
|
memory_max_size,
|
||||||
disk_quota,
|
disk_quota,
|
||||||
cache_path.clone(),
|
cache_path.clone(),
|
||||||
))
|
))))
|
||||||
};
|
};
|
||||||
let cache = Arc::new(TokioRwLock::new(cache));
|
let cache = Arc::clone(&cache);
|
||||||
let cache1 = Arc::clone(&cache);
|
let cache1 = Arc::clone(&cache);
|
||||||
|
|
||||||
// Spawn periodic cache trimming
|
|
||||||
spawn(async move {
|
|
||||||
let mut interval = time::interval(Duration::from_secs(3 * 60));
|
|
||||||
loop {
|
|
||||||
interval.tick().await;
|
|
||||||
cache.write().await.prune().await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start HTTPS server
|
// Start HTTPS server
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
Loading…
Reference in a new issue