Skip to content

Commit 095cba4

Browse files
committed
controllers/krate/publish: Always upsert the default_value
The value is determined by comparing the existing `default_value` to the published version. This could potentially result in upserting an outdated version. A subsequent background job is necessary to ensure correctness and eventual consistency.
1 parent c42f7b6 commit 095cba4

File tree

1 file changed

+38
-16
lines changed

1 file changed

+38
-16
lines changed

src/controllers/krate/publish.rs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use tokio::runtime::Handle;
2323
use url::Url;
2424

2525
use crate::models::{
26-
insert_version_owner_action, Category, Crate, DependencyKind, Keyword, NewCrate, NewVersion,
27-
Rights, VersionAction,
26+
default_versions::Version as DefaultVersion, insert_version_owner_action, Category, Crate,
27+
DependencyKind, Keyword, NewCrate, NewVersion, Rights, VersionAction,
2828
};
2929

3030
use crate::licenses::parse_license_expr;
@@ -64,7 +64,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
6464

6565
Crate::validate_crate_name("crate", &metadata.name).map_err(bad_request)?;
6666

67-
let version = match semver::Version::parse(&metadata.vers) {
67+
let semver = match semver::Version::parse(&metadata.vers) {
6868
Ok(parsed) => parsed,
6969
Err(_) => {
7070
return Err(bad_request(format_args!(
@@ -75,7 +75,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
7575
};
7676

7777
// Convert the version back to a string to deal with any inconsistencies
78-
let version_string = version.to_string();
78+
let version_string = semver.to_string();
7979

8080
let request_log = req.request_log();
8181
request_log.add("crate_name", &*metadata.name);
@@ -385,16 +385,40 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
385385
// Link this new version to all dependencies
386386
add_dependencies(conn, &deps, version.id)?;
387387

388-
// Insert the default version if it doesn't already exist. Compared
389-
// to only using a background job, this prevents us from getting
390-
// into a situation where a crate exists in the `crates` table but
391-
// doesn't have a default version in the `default_versions` table.
392-
let inserted_default_versions = diesel::insert_into(default_versions::table)
388+
let existing_default_version = default_versions::table
389+
.inner_join(versions::table)
390+
.filter(default_versions::crate_id.eq(krate.id))
391+
.select(DefaultVersion::as_select())
392+
.first(conn)
393+
.optional()?;
394+
395+
let published_default_version = DefaultVersion {
396+
id: version.id,
397+
num: semver,
398+
yanked: false,
399+
};
400+
401+
// Upsert the `default_value` determined by the existing `default_value` and the
402+
// published version. Note that this could potentially write an outdated version
403+
// (although this should not happen regularly), as we might be comparing to an
404+
// outdated value.
405+
//
406+
// Compared to only using a background job, this prevents us from getting into a
407+
// situation where a crate exists in the `crates` table but doesn't have a default
408+
// version in the `default_versions` table.
409+
let default_version = if let Some(existing_default_version) = existing_default_version {
410+
std::cmp::max(existing_default_version, published_default_version)
411+
} else {
412+
published_default_version
413+
};
414+
diesel::insert_into(default_versions::table)
393415
.values((
394416
default_versions::crate_id.eq(krate.id),
395-
default_versions::version_id.eq(version.id),
417+
default_versions::version_id.eq(default_version.id),
396418
))
397-
.on_conflict_do_nothing()
419+
.on_conflict(default_versions::crate_id)
420+
.do_update()
421+
.set(default_versions::version_id.eq(default_version.id))
398422
.execute(conn)?;
399423

400424
// Update all keywords for this crate
@@ -446,11 +470,9 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
446470

447471
SendPublishNotificationsJob::new(version.id).enqueue(conn)?;
448472

449-
// If this is a new version for an existing crate it is sufficient
450-
// to update the default version asynchronously in a background job.
451-
if inserted_default_versions == 0 {
452-
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
453-
}
473+
// Update the default version asynchronously in a background job
474+
// to ensure correctness and eventual consistency.
475+
UpdateDefaultVersion::new(krate.id).enqueue(conn)?;
454476

455477
// Experiment: check new crates for potential typosquatting.
456478
if existing_crate.is_none() {

0 commit comments

Comments
 (0)