diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:41:32 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:41:32 +0000 |
| commit | c54ce061d6d278cce8362d5af085808ca60c239b (patch) | |
| tree | ec967d6195d9f7ec4f061449596611afe3a0950f /src/purgatory/sync/context.rs | |
| parent | e0ad39a489b3398f8208713bf728db0cb11475b0 (diff) | |
| parent | 113928aa84894ea8f65c247d9987527e792b32a9 (diff) | |
feat: announcement purgatory
Extends purgatory to hold repository announcements until git data arrives,
preventing empty repositories from being served to clients.
When an announcement is received, a bare repo is created immediately and the
announcement is held in purgatory. It is only promoted and served once a git
push confirms real content exists. If no push arrives before expiry, the bare
repo is deleted and the announcement is silently discarded.
Key behaviours:
- Soft expiry: announcements are hidden from clients but kept alive while git
pushes are in progress, reviving on successful push
- Expiry is extended when a matching state event or git push is observed
- NIP-09 deletion events remove announcements from purgatory
- Purgatory state (announcements, state events, PR events, expired set) is
persisted to disk on graceful shutdown and restored on startup, with elapsed
downtime subtracted from expiry deadlines
- Purgatory announcements drive StateOnly sync in the sync system so state
events are fetched from listed relays before promotion
- SyncLevel added to RepoSyncIndex to distinguish purgatory repos (StateOnly)
from promoted repos (Full L2+L3 sync)
Diffstat (limited to 'src/purgatory/sync/context.rs')
| -rw-r--r-- | src/purgatory/sync/context.rs | 24 |
1 files changed, 21 insertions, 3 deletions
diff --git a/src/purgatory/sync/context.rs b/src/purgatory/sync/context.rs index 904f8af..8297515 100644 --- a/src/purgatory/sync/context.rs +++ b/src/purgatory/sync/context.rs | |||
| @@ -75,7 +75,12 @@ pub trait SyncContext: Send + Sync { | |||
| 75 | /// # Returns | 75 | /// # Returns |
| 76 | /// Set of clone URLs from PR events in purgatory for this identifier | 76 | /// Set of clone URLs from PR events in purgatory for this identifier |
| 77 | fn collect_pr_clone_urls(&self, identifier: &str) -> HashSet<String>; | 77 | fn collect_pr_clone_urls(&self, identifier: &str) -> HashSet<String>; |
| 78 | /// Get repository data (announcements, clone URLs, etc.) from the database. | 78 | /// Get repository data (announcements, clone URLs, etc.) from the database and purgatory. |
| 79 | /// | ||
| 80 | /// Checks both the database (promoted announcements) and purgatory (announcements | ||
| 81 | /// awaiting git data). This is necessary to obtain clone URLs when an announcement | ||
| 82 | /// has not yet been promoted - without purgatory data, the sync loop would have no | ||
| 83 | /// URLs to fetch from and the announcement could never be promoted (circular deadlock). | ||
| 79 | /// | 84 | /// |
| 80 | /// # Arguments | 85 | /// # Arguments |
| 81 | /// * `identifier` - The repository identifier (d-tag value) | 86 | /// * `identifier` - The repository identifier (d-tag value) |
| @@ -279,7 +284,16 @@ impl SyncContext for RealSyncContext { | |||
| 279 | } | 284 | } |
| 280 | 285 | ||
| 281 | async fn fetch_repository_data(&self, identifier: &str) -> Result<RepositoryData> { | 286 | async fn fetch_repository_data(&self, identifier: &str) -> Result<RepositoryData> { |
| 282 | crate::git::authorization::fetch_repository_data(&self.database, identifier).await | 287 | // Use the purgatory-aware variant so that clone URLs from announcements still |
| 288 | // in purgatory (not yet promoted) are available. Without this, the sync loop | ||
| 289 | // would find no URLs to fetch from and the announcement could never be promoted | ||
| 290 | // (circular deadlock: can't promote without git data, can't get git data without URLs). | ||
| 291 | crate::git::authorization::fetch_repository_data_with_purgatory( | ||
| 292 | &self.database, | ||
| 293 | &self.purgatory, | ||
| 294 | identifier, | ||
| 295 | ) | ||
| 296 | .await | ||
| 283 | } | 297 | } |
| 284 | 298 | ||
| 285 | fn collect_needed_oids(&self, identifier: &str) -> HashSet<String> { | 299 | fn collect_needed_oids(&self, identifier: &str) -> HashSet<String> { |
| @@ -487,7 +501,9 @@ impl SyncContext for RealSyncContext { | |||
| 487 | source_repo_path: &Path, | 501 | source_repo_path: &Path, |
| 488 | new_oids: &HashSet<String>, | 502 | new_oids: &HashSet<String>, |
| 489 | ) -> Result<ProcessResult> { | 503 | ) -> Result<ProcessResult> { |
| 490 | // Delegate to the unified function from git::sync | 504 | // Delegate to the unified function from git::sync. |
| 505 | // Pass None for write_policy and rejected_events_index: the purgatory sync path | ||
| 506 | // already handles hot-cache re-processing via SyncManager::process_event_static. | ||
| 491 | let result = crate::git::sync::process_newly_available_git_data( | 507 | let result = crate::git::sync::process_newly_available_git_data( |
| 492 | source_repo_path, | 508 | source_repo_path, |
| 493 | new_oids, | 509 | new_oids, |
| @@ -495,6 +511,8 @@ impl SyncContext for RealSyncContext { | |||
| 495 | self.local_relay.as_ref(), | 511 | self.local_relay.as_ref(), |
| 496 | &self.purgatory, | 512 | &self.purgatory, |
| 497 | &self.git_data_path, | 513 | &self.git_data_path, |
| 514 | None, | ||
| 515 | None, | ||
| 498 | ) | 516 | ) |
| 499 | .await?; | 517 | .await?; |
| 500 | 518 | ||