Conversation
📝 WalkthroughWalkthroughRelease v4.2.10: introduce TorrentCounts and aggregated scrape handling, add shared hex utilities, centralize env parsing, replace unwraps/panics in startup and DB init with logged exit(1), and bump packaging/version metadata. ChangesCore Functionality and Refactoring
Version Bump to 4.2.10
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Cargo.toml (1)
104-104:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winCritical: Bundle version was not updated.
The
package.metadata.bundle.versionat line 104 still shows"4.2.9"and does not have a change marker, indicating it was not updated in this PR. This creates a version mismatch with the mainpackage.version = "4.2.10"at line 8.Both versions should be kept in sync for consistency.
🔧 Proposed fix
-version = "4.2.9" +version = "4.2.10"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Cargo.toml` at line 104, The package.metadata.bundle.version ("4.2.9") in Cargo.toml is out of sync with package.version ("4.2.10"); update the package.metadata.bundle.version value to match the main package.version so both read "4.2.10", ensuring the bundle version and package.version stay consistent (look for the keys package.version and package.metadata.bundle.version in Cargo.toml and change the bundle.version entry to the new version).src/database/impls/database_connector_sqlite.rs (1)
71-75:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid logging full DB DSN/path in connection errors
config.database.pathcan contain credentials; logging it leaks secrets. Log redacted endpoint/context instead.Suggested fix
- error!( - "{} Unable to connect to SQLite on DSL {}", - LOG_PREFIX, - config.database.clone().path - ); + error!("{LOG_PREFIX} Unable to connect to SQLite");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/database/impls/database_connector_sqlite.rs` around lines 71 - 75, The error message in the SQLite connector logs the full DSN/path (config.database.path) which may contain secrets; change the error! call that currently references LOG_PREFIX and config.database.clone().path to log a redacted endpoint instead—implement or call a small redaction helper (e.g., redact_db_path or mask_credentials) and pass redact_db_path(&config.database.path) or simply log a non-secret context string like "sqlite://<REDACTED_PATH>" along with LOG_PREFIX and any safe fields (database type or host if applicable) in the DatabaseConnectorSqlite connection error path so credentials are not emitted.
🧹 Nitpick comments (4)
README.md (1)
569-570: ⚡ Quick winImprove changelog specificity.
The changelog entry "Various audit findings and performance fixes" is too vague for users to understand what changed in this release. Given that the AI summary mentions specific improvements (scrape response refactoring, hex encoding utilities, environment variable parsing, error handling), consider adding more detail.
For example:
#### v4.2.10 * Refactored scrape responses to use aggregated TorrentCounts * Extracted hex encoding utilities into reusable helpers * Centralized environment variable parsing * Improved error handling across server initialization and database operations * Various performance optimizationsThis helps users and maintainers understand the scope of changes and aids in debugging if issues arise.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 569 - 570, Update the v4.2.10 changelog entry (the "#### v4.2.10" section) to replace the vague line "Various audit findings and performance fixes" with specific bullet points describing the notable changes: mention the refactor of scrape responses to use aggregated TorrentCounts, extraction of hex encoding utilities into reusable helpers, centralization of environment variable parsing, improvements to error handling across server initialization and database operations, and a general note about performance optimizations.src/tracker/impls/torrent_tracker_torrents_updates.rs (1)
111-116: ⚡ Quick winAvoid cloning the full batch on the persistence hot path.
torrents_to_save.clone()duplicates everyTorrentEntryright beforesave_torrents, which can eat a lot of CPU and memory on large batches and undercuts the gain from draining instead of cloning the queue. Precompute the small cache/delete payloads you need later, then movetorrents_to_saveintosave_torrents(or changesave_torrentsto borrow).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tracker/impls/torrent_tracker_torrents_updates.rs` around lines 111 - 116, The code clones the entire BTreeMap into save_torrents by calling torrents_to_save.clone(), which is expensive; instead, avoid duplicating TorrentEntry by either (A) constructing only the minimal payloads needed for persistence (e.g., separate small cache/delete lists) from mapping and pass those to save_torrents, or (B) change the signature of save_torrents to accept torrents_to_save by value (moving it) or by reference/borrow (&BTreeMap<InfoHash,(TorrentEntry,UpdatesAction)>) so you can move torrents_to_save into the call without cloning; locate the creation of torrents_to_save (mapping.iter().map(...).collect()) and the save_torrents call and replace the clone with a move or pass a smaller precomputed payload as appropriate, keeping symbols mapping, torrents_to_save, save_torrents, TorrentEntry, UpdatesAction, is_persistent and torrent_tracker in mind.src/websocket/websocket.rs (1)
587-591: ⚡ Quick winInconsistent bind error handling compared to HTTP/API servers.
The
websocket_master_servicestill uses.unwrap()on bind operations (lines 588, 605), while the HTTP and API server handlers were updated to use.unwrap_or_else()with proper error logging beforeexit(1).For consistency with the audit fixes applied elsewhere, consider applying the same pattern here.
♻️ Proposed fix for consistent error handling
.bind_rustls_0_23((addr.ip(), addr.port()), tls_config) - .unwrap() + .unwrap_or_else(|e| { + error!("[WEBSOCKET MASTER] Unable to bind to {addr}: {e}"); + exit(1); + }) .disable_signals()And for the non-SSL path:
.bind((addr.ip(), addr.port())) - .unwrap() + .unwrap_or_else(|e| { + error!("[WEBSOCKET MASTER] Unable to bind to {addr}: {e}"); + exit(1); + }) .disable_signals()Also applies to: 604-608
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/websocket/websocket.rs` around lines 587 - 591, The websocket_master_service currently calls .bind_rustls_0_23(...).unwrap() (and the non-SSL .bind(...) path) which is inconsistent with the HTTP/API servers; change those unwrap() calls to use .unwrap_or_else(|e| { error!("Failed to bind websocket server on {}:{}: {}", addr.ip(), addr.port(), e); std::process::exit(1) }) (or the existing project logger/exit pattern) so binding failures are logged with the error before exiting; apply the same replacement for both the TLS bind (bind_rustls_0_23) and the non-TLS bind to match the other servers and preserve returning (server.handle(), server).src/database/impls/database_connector_mysql.rs (1)
69-88: ⚡ Quick winUse
matchfor clearer error handlingThe current
if let Errpattern works, but usingmatchis more idiomatic and makes the flow explicit. This would avoid shadowing the variable name and make it clear that only theOkpath continues to the unwrap:Suggested refactor
- let mysql_connect = - DatabaseConnectorMySQL::create(config.database.clone().path.as_str()).await; - if let Err(mysql_connect) = mysql_connect { + let mysql_pool = + match DatabaseConnectorMySQL::create(config.database.clone().path.as_str()).await { + Ok(pool) => pool, + Err(mysql_connect) => { error!( "{} Unable to connect to MySQL on DSL {}", LOG_PREFIX, config.database.clone().path ); - error!("{LOG_PREFIX} Message: {mysql_connect}"); - exit(1); - } + error!("{LOG_PREFIX} Message: {mysql_connect}"); + exit(1); + } + }; @@ structure.mysql = Some(DatabaseConnectorMySQL { - pool: mysql_connect.unwrap(), + pool: mysql_pool, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/database/impls/database_connector_mysql.rs` around lines 69 - 88, Replace the current if-let shadowing flow with a match on the result returned by DatabaseConnectorMySQL::create so the Ok and Err branches are explicit; in the Err branch log the error using processLogger-style error calls (preserving LOG_PREFIX and config.database.path) and exit, and in the Ok(branch) take the successful pool value and set structure.mysql = Some(DatabaseConnectorMySQL { pool: pool_value }) without calling unwrap; reference DatabaseConnectorMySQL::create, the mysql_connect variable/result, and the structure.mysql assignment to locate and change the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/config/impls/configuration.rs`:
- Around line 597-601: The env fallback defaults for HTTP server timeouts are
incorrect: in the configuration init() the http_server defaults use
request_timeout = 15 and disconnect_timeout = 15, but the env parsing for
HTTP_{n}_REQUEST_TIMEOUT and HTTP_{n}_DISCONNECT_TIMEOUT currently falls back to
30; update the parse_env_num calls for block.request_timeout and
block.disconnect_timeout to use 15 as the default value so
parse_env_num(format!("HTTP_{http_iteration}_REQUEST_TIMEOUT"), &value, 15) and
parse_env_num(format!("HTTP_{http_iteration}_DISCONNECT_TIMEOUT"), &value, 15)
match the http_server init() defaults.
- Line 287: The rtc_interval fallback is set to 10 when parsing env in the
assignment to config.tracker_config.rtc_interval using
parse_env_num::<u64>("TRACKER__RTC_INTERVAL", &value, 10) but init() defines the
default as 30; update the fallback constant to 30 so the parse_env_num call uses
30 (i.e., replace the third argument 10 with 30) to keep defaults consistent
with init().
In `@src/structs.rs`:
- Around line 45-57: The import flags (e.g., the struct fields
import_file_torrents, import_file_whitelists, import_file_blacklists,
import_file_keys and the import_file_users entry) are incorrectly set to
requires("export"); change each attribute to requires("import") so the CLI
enforces that these --import-file-* options require the --import flag instead of
--export, then run cargo build to verify the CLI parsing compiles.
In `@src/tracker/impls/torrent_tracker_torrents_updates.rs`:
- Around line 97-103: The code currently uses SystemTime-derived nanoseconds for
"timestamp" which is non-monotonic; replace that with a process-local monotonic
sequence (e.g., an AtomicU64 incremented at enqueue time) or use
std::time::Instant-based monotonic ordering so newer updates never lose to older
ones on clock jumps. Change the places that create timestamps (the enqueue sites
currently using SystemTime at the locations corresponding to the original lines
~26 and ~42) to assign the monotonic sequence value instead, keep the same
"timestamp" variable name/type everywhere it flows, and update comparisons in
the dedupe logic (the mapping.entry(...) branch around the current match/if
timestamp > o.get().0) and in the restore handling (the code path around the
current lines ~166-168) to compare the monotonic sequence rather than wall-clock
nanoseconds.
In `@src/tracker/impls/torrent_tracker_torrents.rs`:
- Around line 100-106: get_torrent_counts() can return stale IPv6 totals because
add_torrent's occupied branch only replaces seeds and peers but not
seeds_ipv6/peers_ipv6; locate the add_torrent function and in the code path that
updates an existing entry (the "occupied" branch) also update/replace the IPv6
counters (seeds_ipv6 and peers_ipv6) to match the incoming/refresh values (or
recompute them consistently), so the entry stored under torrents_sharding (the
shard obtained via get_shard and locked via read_recursive) has both IPv4 and
IPv6 fields updated and TorrentCounts::from_entry will return correct totals.
---
Outside diff comments:
In `@Cargo.toml`:
- Line 104: The package.metadata.bundle.version ("4.2.9") in Cargo.toml is out
of sync with package.version ("4.2.10"); update the
package.metadata.bundle.version value to match the main package.version so both
read "4.2.10", ensuring the bundle version and package.version stay consistent
(look for the keys package.version and package.metadata.bundle.version in
Cargo.toml and change the bundle.version entry to the new version).
In `@src/database/impls/database_connector_sqlite.rs`:
- Around line 71-75: The error message in the SQLite connector logs the full
DSN/path (config.database.path) which may contain secrets; change the error!
call that currently references LOG_PREFIX and config.database.clone().path to
log a redacted endpoint instead—implement or call a small redaction helper
(e.g., redact_db_path or mask_credentials) and pass
redact_db_path(&config.database.path) or simply log a non-secret context string
like "sqlite://<REDACTED_PATH>" along with LOG_PREFIX and any safe fields
(database type or host if applicable) in the DatabaseConnectorSqlite connection
error path so credentials are not emitted.
---
Nitpick comments:
In `@README.md`:
- Around line 569-570: Update the v4.2.10 changelog entry (the "#### v4.2.10"
section) to replace the vague line "Various audit findings and performance
fixes" with specific bullet points describing the notable changes: mention the
refactor of scrape responses to use aggregated TorrentCounts, extraction of hex
encoding utilities into reusable helpers, centralization of environment variable
parsing, improvements to error handling across server initialization and
database operations, and a general note about performance optimizations.
In `@src/database/impls/database_connector_mysql.rs`:
- Around line 69-88: Replace the current if-let shadowing flow with a match on
the result returned by DatabaseConnectorMySQL::create so the Ok and Err branches
are explicit; in the Err branch log the error using processLogger-style error
calls (preserving LOG_PREFIX and config.database.path) and exit, and in the
Ok(branch) take the successful pool value and set structure.mysql =
Some(DatabaseConnectorMySQL { pool: pool_value }) without calling unwrap;
reference DatabaseConnectorMySQL::create, the mysql_connect variable/result, and
the structure.mysql assignment to locate and change the logic.
In `@src/tracker/impls/torrent_tracker_torrents_updates.rs`:
- Around line 111-116: The code clones the entire BTreeMap into save_torrents by
calling torrents_to_save.clone(), which is expensive; instead, avoid duplicating
TorrentEntry by either (A) constructing only the minimal payloads needed for
persistence (e.g., separate small cache/delete lists) from mapping and pass
those to save_torrents, or (B) change the signature of save_torrents to accept
torrents_to_save by value (moving it) or by reference/borrow
(&BTreeMap<InfoHash,(TorrentEntry,UpdatesAction)>) so you can move
torrents_to_save into the call without cloning; locate the creation of
torrents_to_save (mapping.iter().map(...).collect()) and the save_torrents call
and replace the clone with a move or pass a smaller precomputed payload as
appropriate, keeping symbols mapping, torrents_to_save, save_torrents,
TorrentEntry, UpdatesAction, is_persistent and torrent_tracker in mind.
In `@src/websocket/websocket.rs`:
- Around line 587-591: The websocket_master_service currently calls
.bind_rustls_0_23(...).unwrap() (and the non-SSL .bind(...) path) which is
inconsistent with the HTTP/API servers; change those unwrap() calls to use
.unwrap_or_else(|e| { error!("Failed to bind websocket server on {}:{}: {}",
addr.ip(), addr.port(), e); std::process::exit(1) }) (or the existing project
logger/exit pattern) so binding failures are logged with the error before
exiting; apply the same replacement for both the TLS bind (bind_rustls_0_23) and
the non-TLS bind to match the other servers and preserve returning
(server.handle(), server).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 424de258-6641-4c06-90b5-e051e66e1d1a
📒 Files selected for processing (28)
Cargo.tomlREADME.mddocker/Dockerfiledocker/build.batsrc/api/api.rssrc/common/common.rssrc/config/impls/configuration.rssrc/database/impls/database_connector_mysql.rssrc/database/impls/database_connector_pgsql.rssrc/database/impls/database_connector_sqlite.rssrc/http/http.rssrc/main.rssrc/stats/impls/torrent_tracker.rssrc/structs.rssrc/tracker/impls/info_hash.rssrc/tracker/impls/peer_id.rssrc/tracker/impls/torrent_tracker_handlers.rssrc/tracker/impls/torrent_tracker_torrents.rssrc/tracker/impls/torrent_tracker_torrents_updates.rssrc/tracker/impls/user_id.rssrc/tracker/structs.rssrc/tracker/structs/scrape_query_request.rssrc/tracker/structs/torrent_counts.rssrc/udp/impls/parse_pool.rssrc/udp/impls/udp_server.rssrc/websocket/websocket.rstests/udp_tests.rstorrust-actix.desktop
💤 Files with no reviewable changes (2)
- src/tracker/structs/scrape_query_request.rs
- src/udp/impls/parse_pool.rs
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/tracker/impls/torrent_tracker_torrents.rs (1)
80-81: 💤 Low valueVerify: RTC peers intentionally excluded from global stats?
The
rtc_seedsandrtc_peersfields are now correctly copied during updates, but they're not included in theSeeds/Peersstats calculations (unlike IPv4 and IPv6 counts). This appears consistent with the vacant path, so likely intentional.Please confirm this is the desired behavior—if RTC peers should contribute to the global seed/peer counts, the delta calculations on lines 62-65 would need to include them as well.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tracker/impls/torrent_tracker_torrents.rs` around lines 80 - 81, The update copies current.rtc_seeds and current.rtc_peers from torrent_entry but the global Seeds/Peers stats computations (the seed_delta/peer_delta calculation that sums IPv4/IPv6 counts) currently omit RTC counts; if RTC peers should contribute, add current.rtc_seeds and current.rtc_peers (and corresponding previous values) into the seed_delta and peer_delta calculations used to update global stats so the deltas include RTC counts alongside IPv4/IPv6; otherwise leave as-is and add a comment in the update path near current.rtc_seeds/current.rtc_peers to document that RTC is intentionally excluded.src/tracker/impls/torrent_tracker_torrents_updates.rs (1)
175-183: 💤 Low valueOn failure, only deduplicated entries are restored—superseded updates are lost.
The restore loop iterates over
mappingwhich contains only the newest entry perInfoHash. If the original queue had multiple updates for the same hash (e.g., timestamps 1, 2, 3 for hash A), only the newest (timestamp 3) is restored after a DB failure. The older updates (1, 2) are permanently discarded.This is likely acceptable since superseded updates represent stale state, but if the intent was to preserve all original entries on failure for potential retry, this behavior differs. The stats accounting is correct for the post-restore state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/tracker/impls/torrent_tracker_torrents_updates.rs` around lines 175 - 183, The restore loop currently iterates over a deduplicated `mapping` (newest per `InfoHash`) and inserts only one entry per hash into `self.torrents_updates.write()` via `lock.entry(timestamp)`, losing superseded updates; change the restore logic to iterate over the full original set of saved updates (or make `mapping` map each `InfoHash` to a Vec of `(timestamp, torrent_entry, updates_action)`) and insert each preserved update into the write-lock (ensuring you still use `lock.entry(timestamp)` or a disambiguated key to avoid collisions), then compute `restored` as the count of all re-inserted entries and call `self.update_stats(StatsEvent::TorrentsUpdates, restored)` so all original updates are restored rather than only the deduplicated newest ones.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/tracker/impls/torrent_tracker_torrents_updates.rs`:
- Around line 175-183: The restore loop currently iterates over a deduplicated
`mapping` (newest per `InfoHash`) and inserts only one entry per hash into
`self.torrents_updates.write()` via `lock.entry(timestamp)`, losing superseded
updates; change the restore logic to iterate over the full original set of saved
updates (or make `mapping` map each `InfoHash` to a Vec of `(timestamp,
torrent_entry, updates_action)`) and insert each preserved update into the
write-lock (ensuring you still use `lock.entry(timestamp)` or a disambiguated
key to avoid collisions), then compute `restored` as the count of all
re-inserted entries and call `self.update_stats(StatsEvent::TorrentsUpdates,
restored)` so all original updates are restored rather than only the
deduplicated newest ones.
In `@src/tracker/impls/torrent_tracker_torrents.rs`:
- Around line 80-81: The update copies current.rtc_seeds and current.rtc_peers
from torrent_entry but the global Seeds/Peers stats computations (the
seed_delta/peer_delta calculation that sums IPv4/IPv6 counts) currently omit RTC
counts; if RTC peers should contribute, add current.rtc_seeds and
current.rtc_peers (and corresponding previous values) into the seed_delta and
peer_delta calculations used to update global stats so the deltas include RTC
counts alongside IPv4/IPv6; otherwise leave as-is and add a comment in the
update path near current.rtc_seeds/current.rtc_peers to document that RTC is
intentionally excluded.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2aeb64d1-ba44-4c76-ac0d-39566d970d69
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
src/config/impls/configuration.rssrc/structs.rssrc/tracker/impls/torrent_tracker_torrents.rssrc/tracker/impls/torrent_tracker_torrents_updates.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/config/impls/configuration.rs
v4.2.10
Summary by CodeRabbit
Bug Fixes
Performance
Documentation
Chores
Tests