Skip to content

Commit 426c635

Browse files
committed
downloads_counter: prevent deadlocks with multiple crates.io instances
1 parent 4019c27 commit 426c635

File tree

1 file changed

+22
-1
lines changed

1 file changed

+22
-1
lines changed

src/downloads_counter.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,31 @@ impl DownloadsCounter {
126126
counted_downloads += count;
127127
counted_versions += 1;
128128

129-
to_insert.push((version_id.eq(*key), downloads.eq(count as i32)));
129+
to_insert.push((*key, count));
130130
}
131131

132132
if !to_insert.is_empty() {
133+
// The rows we're about to insert need to be sorted to avoid deadlocks when multiple
134+
// instances of crates.io are running at the same time.
135+
//
136+
// In PostgreSQL a transaction modifying a row locks that row until the transaction is
137+
// committed. Multiple transactions inserting rows into a table could end up
138+
// deadlocking each other though: PostgreSQL will detect that deadlock, abort one of
139+
// the transactions and allow the other one to continue. We don't want that to happen,
140+
// as we'd lose the downloads from the aborted transaction.
141+
//
142+
// Ensuring the rows are inserted in a consistent order (in our case by sorting them by
143+
// the version ID) will prevent deadlocks from occuring. For more information:
144+
//
145+
// https://www.postgresql.org/docs/11/explicit-locking.html#LOCKING-DEADLOCKS
146+
//
147+
to_insert.sort_by_key(|(key, _)| *key);
148+
149+
let to_insert = to_insert
150+
.into_iter()
151+
.map(|(key, count)| (version_id.eq(key), downloads.eq(count as i32)))
152+
.collect::<Vec<_>>();
153+
133154
diesel::insert_into(version_downloads)
134155
.values(&to_insert)
135156
.on_conflict((version_id, date))

0 commit comments

Comments
 (0)