Skip to content

Commit 2d5b20b

Browse files
committed
Add cli interface to sandbox limit overrides
1 parent c927eac commit 2d5b20b

File tree

6 files changed

+257
-39
lines changed

6 files changed

+257
-39
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ chrono = { version = "0.4.11", default-features = false, features = ["clock", "s
107107

108108
# Transitive dependencies we don't use directly but need to have specific versions of
109109
thread_local = "1.1.3"
110+
humantime = "2.1.0"
110111

111112
[dependencies.postgres]
112113
version = "0.19"

src/bin/cratesfyi.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::sync::Arc;
77
use anyhow::{anyhow, Context as _, Error, Result};
88
use clap::{Parser, Subcommand, ValueEnum};
99
use docs_rs::cdn::CdnBackend;
10-
use docs_rs::db::{self, add_path_into_database, Pool, PoolClient};
10+
use docs_rs::db::{self, add_path_into_database, Overrides, Pool, PoolClient};
1111
use docs_rs::repositories::RepositoryStatsUpdater;
1212
use docs_rs::utils::{
1313
get_config, queue_builder, remove_crate_priority, set_crate_priority, ConfigName,
@@ -16,6 +16,7 @@ use docs_rs::{
1616
start_web_server, BuildQueue, Config, Context, Index, Metrics, PackageKind, RustwideBuilder,
1717
Storage,
1818
};
19+
use humantime::Duration;
1920
use once_cell::sync::OnceCell;
2021
use tokio::runtime::{Builder, Runtime};
2122
use tracing_log::LogTracer;
@@ -417,6 +418,12 @@ enum DatabaseSubcommand {
417418
command: BlacklistSubcommand,
418419
},
419420

421+
/// Limit overrides operations
422+
Limits {
423+
#[command(subcommand)]
424+
command: LimitsSubcommand,
425+
},
426+
420427
/// Compares the database with the index and resolves inconsistencies
421428
#[cfg(feature = "consistency_check")]
422429
Synchronize {
@@ -473,6 +480,8 @@ impl DatabaseSubcommand {
473480
.context("failed to delete the crate")?,
474481
Self::Blacklist { command } => command.handle_args(ctx)?,
475482

483+
Self::Limits { command } => command.handle_args(ctx)?,
484+
476485
#[cfg(feature = "consistency_check")]
477486
Self::Synchronize { dry_run } => {
478487
docs_rs::utils::consistency::run_check(&ctx, dry_run)?;
@@ -482,6 +491,72 @@ impl DatabaseSubcommand {
482491
}
483492
}
484493

494+
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
495+
enum LimitsSubcommand {
496+
/// Get sandbox limit overrides for a crate
497+
Get { crate_name: String },
498+
499+
/// List sandbox limit overrides for all crates
500+
List,
501+
502+
/// Set sandbox limits overrides for a crate
503+
Set {
504+
crate_name: String,
505+
#[arg(long)]
506+
memory: Option<usize>,
507+
#[arg(long)]
508+
targets: Option<usize>,
509+
#[arg(long)]
510+
timeout: Option<Duration>,
511+
},
512+
513+
/// Remove sandbox limits overrides for a crate
514+
Remove { crate_name: String },
515+
}
516+
517+
impl LimitsSubcommand {
518+
fn handle_args(self, ctx: BinContext) -> Result<()> {
519+
let conn = &mut *ctx.conn()?;
520+
match self {
521+
Self::Get { crate_name } => {
522+
let overrides = Overrides::for_crate(conn, &crate_name)?;
523+
println!("sandbox limit overrides for {crate_name} = {overrides:?}");
524+
}
525+
526+
Self::List => {
527+
for (crate_name, overrides) in Overrides::all(conn)? {
528+
println!("sandbox limit overrides for {crate_name} = {overrides:?}");
529+
}
530+
}
531+
532+
Self::Set {
533+
crate_name,
534+
memory,
535+
targets,
536+
timeout,
537+
} => {
538+
let overrides = Overrides::for_crate(conn, &crate_name)?;
539+
println!("previous sandbox limit overrides for {crate_name} = {overrides:?}");
540+
let overrides = Overrides {
541+
memory,
542+
targets,
543+
timeout: timeout.map(Into::into),
544+
};
545+
Overrides::save(conn, &crate_name, overrides)?;
546+
let overrides = Overrides::for_crate(conn, &crate_name)?;
547+
println!("new sandbox limit overrides for {crate_name} = {overrides:?}");
548+
}
549+
550+
Self::Remove { crate_name } => {
551+
let overrides = Overrides::for_crate(conn, &crate_name)?;
552+
println!("previous overrides for {crate_name} = {overrides:?}");
553+
Overrides::remove(conn, &crate_name)?;
554+
}
555+
}
556+
Ok(())
557+
}
558+
}
559+
485560
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
486561
enum BlacklistSubcommand {
487562
/// List all crates on the blacklist

src/db/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
//! Database operations
22
3-
pub use self::add_package::update_crate_data_in_database;
43
pub(crate) use self::add_package::{
54
add_build_into_database, add_doc_coverage, add_package_into_database,
65
};
7-
pub use self::delete::{delete_crate, delete_version};
8-
pub use self::file::{add_path_into_database, add_path_into_remote_archive};
9-
pub use self::migrate::migrate;
10-
pub use self::pool::{Pool, PoolClient, PoolError};
6+
pub use self::{
7+
add_package::update_crate_data_in_database,
8+
delete::{delete_crate, delete_version},
9+
file::{add_path_into_database, add_path_into_remote_archive},
10+
migrate::migrate,
11+
overrides::Overrides,
12+
pool::{Pool, PoolClient, PoolError},
13+
};
1114

1215
mod add_package;
1316
pub mod blacklist;
1417
pub mod delete;
1518
pub(crate) mod file;
1619
mod migrate;
20+
mod overrides;
1721
mod pool;
1822
pub(crate) mod types;

src/db/overrides.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use crate::error::Result;
2+
use postgres::Client;
3+
use std::time::Duration;
4+
5+
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
6+
pub struct Overrides {
7+
pub memory: Option<usize>,
8+
pub targets: Option<usize>,
9+
pub timeout: Option<Duration>,
10+
}
11+
12+
impl Overrides {
13+
pub fn all(conn: &mut Client) -> Result<Vec<(String, Self)>> {
14+
Ok(conn
15+
.query("SELECT * FROM sandbox_overrides", &[])?
16+
.into_iter()
17+
.map(|row| (row.get("crate_name"), Self::from_row(row)))
18+
.collect())
19+
}
20+
21+
pub fn for_crate(conn: &mut Client, krate: &str) -> Result<Option<Self>> {
22+
Ok(conn
23+
.query_opt(
24+
"SELECT * FROM sandbox_overrides WHERE crate_name = $1",
25+
&[&krate],
26+
)?
27+
.map(Self::from_row))
28+
}
29+
30+
fn from_row(row: postgres::Row) -> Self {
31+
Self {
32+
memory: row
33+
.get::<_, Option<i64>>("max_memory_bytes")
34+
.map(|i| i as usize),
35+
targets: row.get::<_, Option<i32>>("max_targets").map(|i| i as usize),
36+
timeout: row
37+
.get::<_, Option<i32>>("timeout_seconds")
38+
.map(|i| Duration::from_secs(i as u64)),
39+
}
40+
}
41+
42+
pub fn save(conn: &mut Client, krate: &str, overrides: Self) -> Result<()> {
43+
if overrides.timeout.is_some() && overrides.targets.is_none() {
44+
tracing::warn!("setting `Overrides::timeout` implies a default `Overrides::targets = 1`, prefer setting this explicitly");
45+
}
46+
conn.execute(
47+
"
48+
INSERT INTO sandbox_overrides (
49+
crate_name, max_memory_bytes, max_targets, timeout_seconds
50+
)
51+
VALUES ($1, $2, $3, $4)
52+
ON CONFLICT (crate_name) DO UPDATE
53+
SET
54+
max_memory_bytes = $2,
55+
max_targets = $3,
56+
timeout_seconds = $4
57+
",
58+
&[
59+
&krate,
60+
&overrides.memory.map(|i| i as i64),
61+
&overrides.targets.map(|i| i as i32),
62+
&overrides.timeout.map(|d| d.as_secs() as i32),
63+
],
64+
)?;
65+
Ok(())
66+
}
67+
68+
pub fn remove(conn: &mut Client, krate: &str) -> Result<()> {
69+
conn.execute(
70+
"DELETE FROM sandbox_overrides WHERE crate_name = $1",
71+
&[&krate],
72+
)?;
73+
Ok(())
74+
}
75+
}
76+
77+
#[cfg(test)]
78+
mod test {
79+
use crate::{db::Overrides, test::*};
80+
use std::time::Duration;
81+
82+
#[test]
83+
fn retrieve_overrides() {
84+
wrapper(|env| {
85+
let db = env.db();
86+
87+
let krate = "hexponent";
88+
89+
// no overrides
90+
let actual = Overrides::for_crate(&mut db.conn(), krate)?;
91+
assert_eq!(actual, None);
92+
93+
// add partial overrides
94+
let expected = Overrides {
95+
targets: Some(1),
96+
..Overrides::default()
97+
};
98+
Overrides::save(&mut db.conn(), krate, expected)?;
99+
let actual = Overrides::for_crate(&mut db.conn(), krate)?;
100+
assert_eq!(actual, Some(expected));
101+
102+
// overwrite with full overrides
103+
let expected = Overrides {
104+
memory: Some(100_000),
105+
targets: Some(1),
106+
timeout: Some(Duration::from_secs(300)),
107+
};
108+
Overrides::save(&mut db.conn(), krate, expected)?;
109+
let actual = Overrides::for_crate(&mut db.conn(), krate)?;
110+
assert_eq!(actual, Some(expected));
111+
112+
// overwrite with partial overrides
113+
let expected = Overrides {
114+
memory: Some(1),
115+
..Overrides::default()
116+
};
117+
Overrides::save(&mut db.conn(), krate, expected)?;
118+
let actual = Overrides::for_crate(&mut db.conn(), krate)?;
119+
assert_eq!(actual, Some(expected));
120+
121+
// remove overrides
122+
Overrides::remove(&mut db.conn(), krate)?;
123+
let actual = Overrides::for_crate(&mut db.conn(), krate)?;
124+
assert_eq!(actual, None);
125+
126+
Ok(())
127+
});
128+
}
129+
}

src/docbuilder/limits.rs

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::db::Overrides;
12
use crate::error::Result;
23
use postgres::Client;
34
use serde::Serialize;
@@ -26,29 +27,18 @@ impl Default for Limits {
2627

2728
impl Limits {
2829
pub(crate) fn for_crate(conn: &mut Client, name: &str) -> Result<Self> {
29-
let mut limits = Self::default();
30-
31-
let res = conn.query(
32-
"SELECT * FROM sandbox_overrides WHERE crate_name = $1;",
33-
&[&name],
34-
)?;
35-
if !res.is_empty() {
36-
let row = &res[0];
37-
if let Some(memory) = row.get::<_, Option<i64>>("max_memory_bytes") {
38-
limits.memory = memory as usize;
39-
}
40-
let timeout = row.get::<_, Option<i32>>("timeout_seconds");
41-
if let Some(timeout) = timeout {
42-
limits.timeout = Duration::from_secs(timeout as u64);
43-
}
44-
if let Some(targets) = row.get::<_, Option<i32>>("max_targets") {
45-
limits.targets = targets as usize;
46-
} else if timeout.is_some() {
47-
limits.targets = 1;
48-
}
49-
}
50-
51-
Ok(limits)
30+
let default = Self::default();
31+
let overrides = Overrides::for_crate(conn, name)?.unwrap_or_default();
32+
Ok(Self {
33+
memory: overrides.memory.unwrap_or(default.memory),
34+
targets: overrides
35+
.targets
36+
.or(overrides.timeout.map(|_| 1))
37+
.unwrap_or(default.targets),
38+
timeout: overrides.timeout.unwrap_or(default.timeout),
39+
networking: default.networking,
40+
max_log_size: default.max_log_size,
41+
})
5242
}
5343

5444
pub(crate) fn memory(&self) -> usize {
@@ -87,9 +77,13 @@ mod test {
8777
let hexponent = Limits::for_crate(&mut db.conn(), krate)?;
8878
assert_eq!(hexponent, Limits::default());
8979

90-
db.conn().query(
91-
"INSERT INTO sandbox_overrides (crate_name, max_targets) VALUES ($1, 15)",
92-
&[&krate],
80+
Overrides::save(
81+
&mut db.conn(),
82+
krate,
83+
Overrides {
84+
targets: Some(15),
85+
..Overrides::default()
86+
},
9387
)?;
9488
// limits work if crate has limits set
9589
let hexponent = Limits::for_crate(&mut db.conn(), krate)?;
@@ -109,10 +103,14 @@ mod test {
109103
targets: 1,
110104
..Limits::default()
111105
};
112-
db.conn().query(
113-
"INSERT INTO sandbox_overrides (crate_name, max_memory_bytes, timeout_seconds, max_targets)
114-
VALUES ($1, $2, $3, $4)",
115-
&[&krate, &(limits.memory as i64), &(limits.timeout.as_secs() as i32), &(limits.targets as i32)]
106+
Overrides::save(
107+
&mut db.conn(),
108+
krate,
109+
Overrides {
110+
memory: Some(limits.memory),
111+
targets: Some(limits.targets),
112+
timeout: Some(limits.timeout),
113+
},
116114
)?;
117115
assert_eq!(limits, Limits::for_crate(&mut db.conn(), krate)?);
118116
Ok(())
@@ -124,9 +122,13 @@ mod test {
124122
wrapper(|env| {
125123
let db = env.db();
126124
let krate = "hexponent";
127-
db.conn().query(
128-
"INSERT INTO sandbox_overrides (crate_name, timeout_seconds) VALUES ($1, 20*60);",
129-
&[&krate],
125+
Overrides::save(
126+
&mut db.conn(),
127+
krate,
128+
Overrides {
129+
timeout: Some(Duration::from_secs(20 * 60)),
130+
..Overrides::default()
131+
},
130132
)?;
131133
let limits = Limits::for_crate(&mut db.conn(), krate)?;
132134
assert_eq!(limits.targets, 1);

0 commit comments

Comments
 (0)