mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-10-05 01:12:06 +00:00
refactor(db): add DbPool::with_conn_or for the default-and-log tails
Ten call sites across link_cache, state and queue repeated the same match/if-let over a with_conn result with their own log line and default. with_conn_or takes the level, the operation name and the default; each site keeps its exact message and the same Ok/Err behaviour.
This commit is contained in:
@@ -82,6 +82,26 @@ impl DbPool {
|
|||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
&self.inner.path
|
&self.inner.path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [`with_conn`] for the many callers that answer a failed statement with
|
||||||
|
/// a default plus one log line: `what` names the operation and `level`
|
||||||
|
/// says how bad it is (`Error` when the failure loses work the caller
|
||||||
|
/// expected, `Warn` when the user is still served).
|
||||||
|
///
|
||||||
|
/// [`with_conn`]: DbPool::with_conn
|
||||||
|
pub async fn with_conn_or<T, F>(&self, level: log::Level, what: &str, default: T, f: F) -> T
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
F: FnOnce(&mut Connection) -> rusqlite::Result<T> + Send + 'static,
|
||||||
|
{
|
||||||
|
match self.with_conn(f).await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(e) => {
|
||||||
|
log::log!(level, "{what}: {e}");
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PoolInner {
|
impl PoolInner {
|
||||||
|
|||||||
@@ -74,9 +74,12 @@ impl LinkCache {
|
|||||||
pub async fn get(&self, key: &str, ttl: Duration) -> Option<CachedPost> {
|
pub async fn get(&self, key: &str, ttl: Duration) -> Option<CachedPost> {
|
||||||
let key = key.to_string();
|
let key = key.to_string();
|
||||||
let ttl = ttl.as_secs_f64();
|
let ttl = ttl.as_secs_f64();
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(move |conn| {
|
log::Level::Warn,
|
||||||
|
"link cache read failed",
|
||||||
|
None,
|
||||||
|
move |conn| {
|
||||||
let Some((payload, created_at)) = conn
|
let Some((payload, created_at)) = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT payload, created_at FROM link_cache WHERE url = ?1",
|
"SELECT payload, created_at FROM link_cache WHERE url = ?1",
|
||||||
@@ -100,89 +103,81 @@ impl LinkCache {
|
|||||||
Err(rusqlite::Error::ToSqlConversionFailure(Box::new(e)))
|
Err(rusqlite::Error::ToSqlConversionFailure(Box::new(e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.await;
|
)
|
||||||
match result {
|
.await
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("link cache read failed: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn put(&self, key: &str, post: &CachedPost) {
|
pub async fn put(&self, key: &str, post: &CachedPost) {
|
||||||
let key = key.to_string();
|
let key = key.to_string();
|
||||||
let payload = serde_json::to_string(post).expect("cached post serializes");
|
let payload = serde_json::to_string(post).expect("cached post serializes");
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(move |conn| {
|
log::Level::Warn,
|
||||||
|
"link cache write failed",
|
||||||
|
(),
|
||||||
|
move |conn| {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO link_cache (url, payload, created_at) VALUES (?1, ?2, ?3)",
|
"INSERT OR REPLACE INTO link_cache (url, payload, created_at) VALUES (?1, ?2, ?3)",
|
||||||
params![key, payload, now_f64()],
|
params![key, payload, now_f64()],
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
|
||||||
log::warn!("link cache write failed: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drops an entry (e.g. a cached file id that turned out invalid).
|
/// Drops an entry (e.g. a cached file id that turned out invalid).
|
||||||
pub async fn remove(&self, key: &str) {
|
pub async fn remove(&self, key: &str) {
|
||||||
let key = key.to_string();
|
let key = key.to_string();
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(move |conn| {
|
log::Level::Warn,
|
||||||
|
"link cache delete failed",
|
||||||
|
(),
|
||||||
|
move |conn| {
|
||||||
conn.execute("DELETE FROM link_cache WHERE url = ?1", params![key])?;
|
conn.execute("DELETE FROM link_cache WHERE url = ?1", params![key])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
|
||||||
log::warn!("link cache delete failed: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes expired entries; returns how many were deleted.
|
/// Removes expired entries; returns how many were deleted.
|
||||||
pub async fn prune(&self, ttl: Duration) -> usize {
|
pub async fn prune(&self, ttl: Duration) -> usize {
|
||||||
let cutoff = now_f64() - ttl.as_secs_f64();
|
let cutoff = now_f64() - ttl.as_secs_f64();
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(move |conn| {
|
log::Level::Warn,
|
||||||
|
"link cache prune failed",
|
||||||
|
0,
|
||||||
|
move |conn| {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"DELETE FROM link_cache WHERE created_at < ?1",
|
"DELETE FROM link_cache WHERE created_at < ?1",
|
||||||
params![cutoff],
|
params![cutoff],
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
.await;
|
)
|
||||||
match result {
|
.await
|
||||||
Ok(n) => n,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("link cache prune failed: {e}");
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes one entry (by normalized cache key) or the whole cache when
|
/// Deletes one entry (by normalized cache key) or the whole cache when
|
||||||
/// `key` is `None`. Returns how many rows were removed.
|
/// `key` is `None`. Returns how many rows were removed.
|
||||||
pub async fn clear(&self, key: Option<&str>) -> usize {
|
pub async fn clear(&self, key: Option<&str>) -> usize {
|
||||||
let key = key.map(str::to_string);
|
let key = key.map(str::to_string);
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(move |conn| match &key {
|
log::Level::Warn,
|
||||||
Some(key) => conn.execute("DELETE FROM link_cache WHERE url = ?1", params![key]),
|
"link cache clear failed",
|
||||||
|
0,
|
||||||
|
move |conn| match &key {
|
||||||
|
Some(key) => {
|
||||||
|
conn.execute("DELETE FROM link_cache WHERE url = ?1", params![key])
|
||||||
|
}
|
||||||
None => conn.execute("DELETE FROM link_cache", []),
|
None => conn.execute("DELETE FROM link_cache", []),
|
||||||
})
|
},
|
||||||
.await;
|
)
|
||||||
match result {
|
.await
|
||||||
Ok(n) => n,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("link cache clear failed: {e}");
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -239,23 +239,20 @@ impl PersistentTaskQueue {
|
|||||||
/// `in_progress`). The startup repair reads these before the workers start:
|
/// `in_progress`). The startup repair reads these before the workers start:
|
||||||
/// with no worker running, no row can be leased while it writes.
|
/// with no worker running, no row can be leased while it writes.
|
||||||
pub async fn runnable_rows(&self) -> Vec<(String, String)> {
|
pub async fn runnable_rows(&self) -> Vec<(String, String)> {
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(|conn| {
|
log::Level::Error,
|
||||||
|
"queue row scan failed",
|
||||||
|
Vec::new(),
|
||||||
|
|conn| {
|
||||||
let mut stmt = conn.prepare(
|
let mut stmt = conn.prepare(
|
||||||
"SELECT id, payload FROM tasks WHERE status IN ('pending', 'in_progress') ORDER BY run_after",
|
"SELECT id, payload FROM tasks WHERE status IN ('pending', 'in_progress') ORDER BY run_after",
|
||||||
)?;
|
)?;
|
||||||
let rows = stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?;
|
let rows = stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?;
|
||||||
rows.collect::<rusqlite::Result<Vec<(String, String)>>>()
|
rows.collect::<rusqlite::Result<Vec<(String, String)>>>()
|
||||||
})
|
},
|
||||||
.await;
|
)
|
||||||
match result {
|
.await
|
||||||
Ok(rows) => rows,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("queue row scan failed: {e}");
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces a runnable row's payload and restarts its attempt budget: the
|
/// Replaces a runnable row's payload and restarts its attempt budget: the
|
||||||
@@ -291,9 +288,12 @@ impl PersistentTaskQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pending_backlog(&self) -> Option<(i64, f64)> {
|
pub async fn pending_backlog(&self) -> Option<(i64, f64)> {
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(|conn| {
|
log::Level::Error,
|
||||||
|
"queue backlog query failed",
|
||||||
|
None,
|
||||||
|
|conn| {
|
||||||
let (count, oldest) = conn.query_row(
|
let (count, oldest) = conn.query_row(
|
||||||
"SELECT COUNT(*), MIN(run_after) FROM tasks WHERE status='pending'",
|
"SELECT COUNT(*), MIN(run_after) FROM tasks WHERE status='pending'",
|
||||||
[],
|
[],
|
||||||
@@ -304,15 +304,9 @@ impl PersistentTaskQueue {
|
|||||||
Some(oldest) if count > 0 => Some((count, oldest)),
|
Some(oldest) if count > 0 => Some((count, oldest)),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
})
|
},
|
||||||
.await;
|
)
|
||||||
match result {
|
.await
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("queue backlog query failed: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,23 +449,20 @@ impl QueueWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn earliest_run_after(&self) -> Option<f64> {
|
async fn earliest_run_after(&self) -> Option<f64> {
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(|conn| {
|
log::Level::Error,
|
||||||
|
"queue timing query failed",
|
||||||
|
None,
|
||||||
|
|conn| {
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
"SELECT MIN(run_after) FROM tasks WHERE status='pending'",
|
"SELECT MIN(run_after) FROM tasks WHERE status='pending'",
|
||||||
[],
|
[],
|
||||||
|row| row.get::<_, Option<f64>>(0),
|
|row| row.get::<_, Option<f64>>(0),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
.await;
|
)
|
||||||
match result {
|
.await
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("queue timing query failed: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes one leased row, keeping the lease alive while the handler
|
/// Processes one leased row, keeping the lease alive while the handler
|
||||||
|
|||||||
@@ -61,22 +61,24 @@ impl ChatStore {
|
|||||||
let chat_key = chat_id.to_string();
|
let chat_key = chat_id.to_string();
|
||||||
let payload = self
|
let payload = self
|
||||||
.pool
|
.pool
|
||||||
.with_conn(move |conn| {
|
.with_conn_or(
|
||||||
// Concurrent handler tasks (batch-forwards) may write chat_state
|
log::Level::Warn,
|
||||||
// while this read runs; the shared busy timeout handles the
|
"chat_state read failed",
|
||||||
// write-lock collision instead of failing the query.
|
None,
|
||||||
|
move |conn| {
|
||||||
|
// Concurrent handler tasks (batch-forwards) may write
|
||||||
|
// chat_state while this read runs; the shared busy timeout
|
||||||
|
// handles the write-lock collision instead of failing the
|
||||||
|
// query.
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
"SELECT payload FROM chat_state WHERE chat_id = ?1",
|
"SELECT payload FROM chat_state WHERE chat_id = ?1",
|
||||||
params![chat_key],
|
params![chat_key],
|
||||||
|row| row.get::<_, String>(0),
|
|row| row.get::<_, String>(0),
|
||||||
)
|
)
|
||||||
.optional()
|
.optional()
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
log::warn!("chat_state read failed: {e}");
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let data: ChatData = serde_json::from_str(&payload).unwrap_or_default();
|
let data: ChatData = serde_json::from_str(&payload).unwrap_or_default();
|
||||||
self.cache.lock().insert(chat_id, data.clone());
|
self.cache.lock().insert(chat_id, data.clone());
|
||||||
@@ -88,19 +90,20 @@ impl ChatStore {
|
|||||||
self.cache.lock().insert(chat_id, data.clone());
|
self.cache.lock().insert(chat_id, data.clone());
|
||||||
let payload = serde_json::to_string(data).expect("chat state serializes");
|
let payload = serde_json::to_string(data).expect("chat state serializes");
|
||||||
let chat_id = chat_id.to_string();
|
let chat_id = chat_id.to_string();
|
||||||
let result = self
|
self.pool
|
||||||
.pool
|
.with_conn_or(
|
||||||
.with_conn(move |conn| {
|
log::Level::Warn,
|
||||||
|
"chat_state write failed",
|
||||||
|
(),
|
||||||
|
move |conn| {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO chat_state (chat_id, payload) VALUES (?1, ?2)",
|
"INSERT OR REPLACE INTO chat_state (chat_id, payload) VALUES (?1, ?2)",
|
||||||
params![chat_id, payload],
|
params![chat_id, payload],
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
if let Err(e) = result {
|
|
||||||
log::warn!("chat_state write failed: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The per-chat async lock serializing get→mutate→set cycles.
|
/// The per-chat async lock serializing get→mutate→set cycles.
|
||||||
|
|||||||
Reference in New Issue
Block a user