Skip to content

Commit caa96be

Browse files
committed
refactor: optimize shard lock hold
1 parent b65504f commit caa96be

File tree

3 files changed

+41
-107
lines changed

3 files changed

+41
-107
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ deepsize = "0.2.0"
4343
derive_more = "0"
4444
fern = "0"
4545
futures = "0"
46+
hashbrown = "0"
4647
hyper = "1"
4748
lazy_static = "1"
4849
log = { version = "0", features = ["release_max_level_info"] }

src/core/torrent/repositories.rs

+39-107
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use std::collections::{HashSet, VecDeque};
1+
use std::collections::VecDeque;
22
use std::iter;
33
use std::mem::size_of;
4-
use std::sync::{Arc, Mutex, MutexGuard};
4+
use std::sync::{Arc, Mutex};
55

66
use dashmap::{DashMap, Map, SharedValue};
77

@@ -11,15 +11,15 @@ use crate::shared::bit_torrent::info_hash::InfoHash;
1111
use crate::shared::mem_size::{MemSize, POINTER_SIZE};
1212

1313
// todo: Make this a config option. Through env?
14-
const MAX_MEMORY_LIMIT: Option<usize> = Some(8_000); // 8GB
14+
const MAX_MEMORY_LIMIT: Option<usize> = Some(8_000_000_000); // 8GB
1515

1616
const INFO_HASH_SIZE: usize = size_of::<InfoHash>();
1717

1818
/// Total memory impact of adding a new empty torrent ([torrent::Entry]) to a map.
1919
const TORRENT_INSERTION_SIZE_COST: usize = 216;
2020

2121
/// Total memory impact of adding a new peer ([peer::Peer]) to a map.
22-
const PEER_INSERTION_SIZE_COST: usize = 132;
22+
const _PEER_INSERTION_SIZE_COST: usize = 132;
2323

2424
pub trait Repository {
2525
fn new() -> Self;
@@ -345,35 +345,7 @@ impl MemSize for RepositoryDashmap {
345345
}
346346

347347
impl RepositoryDashmap {
348-
/// Removes all torrents with no peers and returns the amount of memory freed in bytes.
349-
fn _clean_empty_torrents_in_shard(&self, shard_idx: usize) -> usize {
350-
let mut to_be_removed_torrents: HashSet<InfoHash> = HashSet::new();
351-
let mut memory_freed: usize = 0;
352-
353-
let mut shard = unsafe { self.torrents._yield_write_shard(shard_idx) };
354-
355-
for (info_hash, torrent) in shard.iter_mut() {
356-
// todo: Get the max peer timeout from config.
357-
torrent.get_mut().remove_inactive_peers(900);
358-
359-
if torrent.get().peers.is_empty() {
360-
to_be_removed_torrents.insert(info_hash.to_owned());
361-
memory_freed += (2 * POINTER_SIZE) + INFO_HASH_SIZE + torrent.get().get_mem_size();
362-
}
363-
}
364-
365-
let mut priority_list = unsafe { self.shard_priority_list.get_unchecked(shard_idx) }.lock().unwrap();
366-
367-
for info_hash in &to_be_removed_torrents {
368-
shard.remove(info_hash);
369-
}
370-
371-
priority_list.retain(|v| !to_be_removed_torrents.contains(v));
372-
373-
memory_freed
374-
}
375-
376-
fn _get_index_of_torrent_on_shard_priority_list(&self, shard_idx: usize, info_hash: &InfoHash) -> Option<usize> {
348+
fn get_index_of_torrent_on_shard_priority_list(&self, shard_idx: usize, info_hash: &InfoHash) -> Option<usize> {
377349
let priority_list = unsafe { self.shard_priority_list.get_unchecked(shard_idx) }.lock().unwrap();
378350

379351
let mut index = None;
@@ -387,53 +359,8 @@ impl RepositoryDashmap {
387359
index
388360
}
389361

390-
unsafe fn _yield_shard_lock(&self, shard_idx: usize) -> MutexGuard<'_, ()> {
391-
self.shard_locks.get_unchecked(shard_idx).lock().unwrap()
392-
}
393-
394-
fn check_do_free_memory_on_shard(&self, shard_idx: usize, amount: usize) {
395-
let mem_size_shard = self.get_shard_mem_size(shard_idx);
396-
let maybe_max_memory_available = MAX_MEMORY_LIMIT.map(|v| v / self.torrents._shard_count() - mem_size_shard);
397-
let memory_shortage = maybe_max_memory_available.map(|v| amount.saturating_sub(v)).unwrap_or(0);
398-
399-
if memory_shortage > 0 {
400-
self.free_memory_on_shard(shard_idx, memory_shortage);
401-
}
402-
}
403-
404-
fn free_memory_on_shard(&self, shard_idx: usize, amount: usize) {
405-
let mut amount_freed: usize = 0;
406-
407-
// Free memory from inactive torrents first.
408-
amount_freed += self._clean_empty_torrents_in_shard(shard_idx);
409-
410-
let mut shard = unsafe { self.torrents._yield_write_shard(shard_idx) };
411-
let mut priority_list = unsafe { self.shard_priority_list.get_unchecked(shard_idx) }.lock().unwrap();
412-
413-
while amount_freed < amount && !priority_list.is_empty() {
414-
// Can safely unwrap as we check if the priority list is not empty
415-
let torrent_hash_to_be_removed = priority_list.pop_back().unwrap();
416-
417-
if let Some(torrent) = shard.remove(&torrent_hash_to_be_removed) {
418-
amount_freed += torrent.get().get_mem_size();
419-
}
420-
}
421-
}
422-
423-
fn get_shard_mem_size(&self, shard_idx: usize) -> usize {
424-
let shard = unsafe { self.torrents._yield_read_shard(shard_idx) };
425-
426-
let mut mem_size_shard: usize = 0;
427-
428-
for torrent in shard.values() {
429-
mem_size_shard += (2 * POINTER_SIZE) + INFO_HASH_SIZE + torrent.get().get_mem_size();
430-
}
431-
432-
mem_size_shard
433-
}
434-
435-
fn shift_torrent_to_front_on_shard_priority_list(&self, shard_idx: usize, info_hash: &InfoHash) {
436-
let maybe_index = self._get_index_of_torrent_on_shard_priority_list(shard_idx, info_hash);
362+
fn addshift_torrent_to_front_on_shard_priority_list(&self, shard_idx: usize, info_hash: &InfoHash) {
363+
let maybe_index = self.get_index_of_torrent_on_shard_priority_list(shard_idx, info_hash);
437364

438365
let mut priority_list = self.shard_priority_list.get(shard_idx).unwrap().lock().unwrap();
439366

@@ -443,16 +370,6 @@ impl RepositoryDashmap {
443370

444371
priority_list.push_front(info_hash.to_owned());
445372
}
446-
447-
fn insert_torrent_into_shard(&self, shard_idx: usize, info_hash: &InfoHash) -> Option<Entry> {
448-
let mut shard = unsafe { self.torrents._yield_write_shard(shard_idx) };
449-
450-
self.shift_torrent_to_front_on_shard_priority_list(shard_idx, info_hash);
451-
452-
shard
453-
.insert(info_hash.to_owned(), SharedValue::new(Entry::default()))
454-
.map(|v| v.into_inner())
455-
}
456373
}
457374

458375
impl Repository for RepositoryDashmap {
@@ -476,32 +393,47 @@ impl Repository for RepositoryDashmap {
476393
fn upsert_torrent_with_peer_and_get_stats(&self, info_hash: &InfoHash, peer: &peer::Peer) -> (SwarmStats, bool) {
477394
let hash = self.torrents.hash_usize(&info_hash);
478395
let shard_idx = self.torrents.determine_shard(hash);
396+
let mut shard = unsafe { self.torrents._yield_write_shard(shard_idx) };
479397

480-
let _shard_lock = unsafe { self._yield_shard_lock(shard_idx) };
398+
let mut torrent = shard.remove(info_hash).map(|v| v.into_inner()).unwrap_or_default();
481399

482-
if !self.torrents.contains_key(info_hash) {
483-
self.check_do_free_memory_on_shard(shard_idx, TORRENT_INSERTION_SIZE_COST);
484-
self.insert_torrent_into_shard(shard_idx, info_hash);
485-
} else {
486-
self.shift_torrent_to_front_on_shard_priority_list(shard_idx, info_hash);
400+
let stats_updated = torrent.insert_or_update_peer(peer);
401+
let stats = torrent.get_stats();
402+
403+
let mut mem_size_shard: usize = 0;
404+
405+
for torrent in shard.values() {
406+
mem_size_shard += (2 * POINTER_SIZE) + INFO_HASH_SIZE + torrent.get().get_mem_size();
487407
}
488408

489-
// todo: Reserve the freed memory above.
409+
let maybe_max_memory_available = MAX_MEMORY_LIMIT.map(|v| v / self.torrents._shard_count() - mem_size_shard);
490410

491-
let peer_exists = self.torrents.get(info_hash).unwrap().peers.contains_key(&peer.peer_id);
411+
let memory_shortage = maybe_max_memory_available
412+
.map(|v| TORRENT_INSERTION_SIZE_COST.saturating_sub(v))
413+
.unwrap_or(0);
492414

493-
if !peer_exists {
494-
self.check_do_free_memory_on_shard(shard_idx, PEER_INSERTION_SIZE_COST);
415+
if memory_shortage > 0 {
416+
let mut amount_freed: usize = 0;
417+
418+
let mut priority_list = unsafe { self.shard_priority_list.get_unchecked(shard_idx) }.lock().unwrap();
419+
420+
while amount_freed < memory_shortage && !priority_list.is_empty() {
421+
// Can safely unwrap as we check if the priority list is not empty
422+
let torrent_hash_to_be_removed = priority_list.pop_back().unwrap();
423+
424+
if let Some(torrent) = shard.remove(&torrent_hash_to_be_removed) {
425+
amount_freed += torrent.get().get_mem_size();
426+
}
427+
}
495428
}
496429

497-
// todo: Will unwrap to none if the max repo size / shard amount is lower than the size of a torrent + 1 peer.
498-
// todo: Should assert that the above condition is never the case.
499-
let mut torrent = self.torrents.get_mut(info_hash).unwrap();
430+
self.addshift_torrent_to_front_on_shard_priority_list(shard_idx, info_hash);
500431

501-
let stats_updated = torrent.insert_or_update_peer(peer);
502-
let stats = torrent.get_stats();
432+
shard
433+
.insert(info_hash.to_owned(), SharedValue::new(torrent))
434+
.map(|v| v.into_inner());
503435

504-
drop(_shard_lock);
436+
drop(shard);
505437

506438
(
507439
SwarmStats {
@@ -592,7 +524,7 @@ pub mod tests {
592524
let hash = torrent_repository.torrents.hash_usize(&info_hash_2);
593525
let shard_idx = torrent_repository.torrents.determine_shard(hash);
594526

595-
let maybe_priority_idx = torrent_repository._get_index_of_torrent_on_shard_priority_list(shard_idx, &info_hash_2);
527+
let maybe_priority_idx = torrent_repository.get_index_of_torrent_on_shard_priority_list(shard_idx, &info_hash_2);
596528

597529
assert_eq!(maybe_priority_idx, Some(0))
598530
}

0 commit comments

Comments
 (0)