···11-pub mod api;
22-pub mod backfill;
31pub mod config;
44-pub mod crawler;
55-pub mod db;
22+pub mod control;
63pub mod filter;
77-pub mod ingest;
88-pub mod ops;
99-pub mod resolver;
1010-pub mod state;
114pub mod types;
1212-pub mod util;
55+66+pub(crate) mod api;
77+pub(crate) mod backfill;
88+pub(crate) mod crawler;
99+pub(crate) mod db;
1010+pub(crate) mod ingest;
1111+pub(crate) mod ops;
1212+pub(crate) mod resolver;
1313+pub(crate) mod state;
1414+pub(crate) mod util;
+13-277
src/main.rs
···11-use futures::{FutureExt, future::BoxFuture};
22-use hydrant::config::{Config, SignatureVerification};
33-use hydrant::db;
44-use hydrant::ingest::firehose::FirehoseIngestor;
55-use hydrant::state::AppState;
66-use hydrant::{api, backfill::BackfillWorker, ingest::worker::FirehoseWorker};
77-use miette::IntoDiagnostic;
11+use hydrant::config::{AppConfig, Config};
22+use hydrant::control::Hydrant;
83use mimalloc::MiMalloc;
99-use std::sync::Arc;
1010-use std::sync::atomic::Ordering;
1111-use tokio::{sync::mpsc, task::spawn_blocking};
1212-use tracing::{debug, error, info};
134145#[global_allocator]
156static GLOBAL: MiMalloc = MiMalloc;
···2112 .ok();
22132314 let cfg = Config::from_env()?;
1515+ let app = AppConfig::from_env();
24162517 let env_filter = tracing_subscriber::EnvFilter::builder()
2618 .with_default_directive(tracing::Level::INFO.into())
2719 .from_env_lossy();
2820 tracing_subscriber::fmt().with_env_filter(env_filter).init();
29213030- info!("{cfg}");
3131-3232- let state = AppState::new(&cfg)?;
3333-3434- if cfg.full_network
3535- || cfg.filter_signals.is_some()
3636- || cfg.filter_collections.is_some()
3737- || cfg.filter_excludes.is_some()
3838- {
3939- let filter_ks = state.db.filter.clone();
4040- let inner = state.db.inner.clone();
4141- let full_network = cfg.full_network;
4242- let signals = cfg.filter_signals.clone();
4343- let collections = cfg.filter_collections.clone();
4444- let excludes = cfg.filter_excludes.clone();
4545-4646- tokio::task::spawn_blocking(move || {
4747- use hydrant::filter::{FilterMode, SetUpdate};
4848- let mut batch = inner.batch();
4949-5050- let mode = if full_network {
5151- Some(FilterMode::Full)
5252- } else {
5353- None
5454- };
5555-5656- let signals_update = signals.map(SetUpdate::Set);
5757- let collections_update = collections.map(SetUpdate::Set);
5858- let excludes_update = excludes.map(SetUpdate::Set);
5959-6060- hydrant::db::filter::apply_patch(
6161- &mut batch,
6262- &filter_ks,
6363- mode,
6464- signals_update,
6565- collections_update,
6666- excludes_update,
6767- )?;
6868-6969- batch.commit().into_diagnostic()
7070- })
7171- .await
7272- .into_diagnostic()??;
7373-7474- let new_filter = hydrant::db::filter::load(&state.db.filter)?;
7575- state.filter.store(new_filter.into());
7676- }
7777-7878- let (buffer_tx, buffer_rx) = mpsc::unbounded_channel();
7979- let state = Arc::new(state);
8080-8181- if cfg.ephemeral {
8282- let state = state.clone();
8383- std::thread::Builder::new()
8484- .name("ephemeral-gc".into())
8585- .spawn(move || db::ephemeral::ephemeral_ttl_worker(state))
8686- .into_diagnostic()?;
8787- }
8888-8989- tokio::spawn({
9090- let state = state.clone();
9191- let timeout = cfg.repo_fetch_timeout;
9292- BackfillWorker::new(
9393- state.clone(),
9494- buffer_tx.clone(),
9595- timeout,
9696- cfg.backfill_concurrency_limit,
9797- matches!(
9898- cfg.verify_signatures,
9999- SignatureVerification::Full | SignatureVerification::BackfillOnly
100100- ),
101101- cfg.ephemeral,
102102- state.backfill_enabled.subscribe(),
103103- )
104104- .run()
105105- });
106106-107107- if let Err(e) = spawn_blocking({
108108- let state = state.clone();
109109- move || hydrant::backfill::manager::queue_gone_backfills(&state)
110110- })
111111- .await
112112- .into_diagnostic()?
113113- {
114114- error!(err = %e, "failed to queue gone backfills");
115115- db::check_poisoned_report(&e);
116116- }
117117-118118- std::thread::spawn({
119119- let state = state.clone();
120120- move || hydrant::backfill::manager::retry_worker(state)
121121- });
122122-123123- tokio::spawn({
124124- let state = state.clone();
125125- let mut last_id = state.db.next_event_id.load(Ordering::Relaxed);
126126- let mut last_time = std::time::Instant::now();
127127- let mut interval = tokio::time::interval(std::time::Duration::from_secs(60));
128128- async move {
129129- loop {
130130- interval.tick().await;
131131-132132- let current_id = state.db.next_event_id.load(Ordering::Relaxed);
133133- let current_time = std::time::Instant::now();
134134-135135- let delta = current_id.saturating_sub(last_id);
136136- if delta == 0 {
137137- debug!("no new events in 60s");
138138- continue;
139139- }
140140-141141- let elapsed = current_time.duration_since(last_time).as_secs_f64();
142142- let rate = if elapsed > 0.0 {
143143- delta as f64 / elapsed
144144- } else {
145145- 0.0
146146- };
147147-148148- info!("{rate:.2} events/s ({delta} events in {elapsed:.1}s)");
149149-150150- last_id = current_id;
151151- last_time = current_time;
152152- }
153153- }
154154- });
155155-156156- std::thread::spawn({
157157- let state = state.clone();
158158- let persist_interval = cfg.cursor_save_interval;
159159-160160- move || {
161161- loop {
162162- std::thread::sleep(persist_interval);
2222+ let hydrant = Hydrant::new(cfg).await?;
16323164164- // persist firehose cursors
165165- for (relay, cursor) in &state.relay_cursors {
166166- let seq = cursor.load(Ordering::SeqCst);
167167- if seq > 0 {
168168- if let Err(e) = db::set_firehose_cursor(&state.db, relay, seq) {
169169- error!(relay = %relay, err = %e, "failed to save cursor");
170170- db::check_poisoned_report(&e);
171171- }
172172- }
173173- }
174174-175175- // persist counts
176176- // TODO: make this more durable
177177- if let Err(e) = db::persist_counts(&state.db) {
178178- error!(err = %e, "failed to persist counts");
179179- db::check_poisoned_report(&e);
180180- }
181181-182182- // persist journal
183183- if let Err(e) = state.db.persist() {
184184- error!(err = %e, "db persist failed");
185185- db::check_poisoned_report(&e);
186186- }
187187- }
2424+ if app.enable_debug {
2525+ tokio::select! {
2626+ r = hydrant.run() => r,
2727+ r = hydrant.serve(app.api_port) => r,
2828+ r = hydrant.serve_debug(app.debug_port) => r,
18829 }
189189- });
190190-191191- let post_patch_crawler = match cfg.enable_crawler {
192192- Some(b) => b,
193193- None => state.filter.load().mode == hydrant::filter::FilterMode::Full,
194194- };
195195- state.crawler_enabled.send_replace(post_patch_crawler);
196196-197197- info!(
198198- crawler_enabled = *state.crawler_enabled.borrow(),
199199- firehose_enabled = *state.firehose_enabled.borrow(),
200200- filter_mode = ?state.filter.load().mode,
201201- "starting ingestion"
202202- );
203203-204204- let relay_hosts = cfg.relays.clone();
205205- let crawler_max_pending = cfg.crawler_max_pending_repos;
206206- let crawler_resume_pending = cfg.crawler_resume_pending_repos;
207207-208208- if !relay_hosts.is_empty() {
209209- let state_for_crawler = state.clone();
210210- let crawler_rx = state.crawler_enabled.subscribe();
211211- info!(
212212- relay_count = relay_hosts.len(),
213213- hosts = relay_hosts
214214- .iter()
215215- .map(|h| h.as_str())
216216- .collect::<Vec<_>>()
217217- .join(", "),
218218- enabled = *state.crawler_enabled.borrow(),
219219- "starting crawler(s)"
220220- );
221221- tokio::spawn(async move {
222222- let crawler = hydrant::crawler::Crawler::new(
223223- state_for_crawler,
224224- relay_hosts,
225225- crawler_max_pending,
226226- crawler_resume_pending,
227227- crawler_rx,
228228- );
229229- if let Err(e) = crawler.run().await {
230230- error!(err = %e, "crawler error");
231231- db::check_poisoned_report(&e);
232232- }
233233- });
234234- }
235235-236236- let firehose_worker = std::thread::spawn({
237237- let state = state.clone();
238238- let handle = tokio::runtime::Handle::current();
239239- move || {
240240- FirehoseWorker::new(
241241- state,
242242- buffer_rx,
243243- matches!(cfg.verify_signatures, SignatureVerification::Full),
244244- cfg.ephemeral,
245245- cfg.firehose_workers,
246246- )
247247- .run(handle)
3030+ } else {
3131+ tokio::select! {
3232+ r = hydrant.run() => r,
3333+ r = hydrant.serve(app.api_port) => r,
24834 }
249249- });
250250-251251- let mut tasks: Vec<BoxFuture<miette::Result<()>>> = vec![Box::pin(
252252- tokio::task::spawn_blocking(move || {
253253- firehose_worker
254254- .join()
255255- .map_err(|e| miette::miette!("buffer processor died: {e:?}"))
256256- })
257257- .map(|r| r.into_diagnostic().flatten().flatten()),
258258- )];
259259-260260- for relay_url in &cfg.relays {
261261- let ingestor = FirehoseIngestor::new(
262262- state.clone(),
263263- buffer_tx.clone(),
264264- relay_url.clone(),
265265- state.filter.clone(),
266266- state.firehose_enabled.subscribe(),
267267- matches!(cfg.verify_signatures, SignatureVerification::Full),
268268- );
269269- tasks.push(Box::pin(ingestor.run()));
27035 }
271271-272272- let state_api = state.clone();
273273- tasks.push(Box::pin(async move {
274274- api::serve(state_api, cfg.api_port)
275275- .await
276276- .map_err(|e| miette::miette!("API server failed: {e}"))
277277- }) as BoxFuture<_>);
278278-279279- if cfg.enable_debug {
280280- let state_debug = state.clone();
281281- tasks.push(Box::pin(async move {
282282- api::serve_debug(state_debug, cfg.debug_port)
283283- .await
284284- .map_err(|e| miette::miette!("debug server failed: {e}"))
285285- }) as BoxFuture<_>);
286286- }
287287-288288- let res = futures::future::select_all(tasks);
289289- if let (Err(e), _, _) = res.await {
290290- error!(err = %e, "critical worker died");
291291- db::check_poisoned_report(&e);
292292- }
293293-294294- if let Err(e) = state.db.persist() {
295295- db::check_poisoned_report(&e);
296296- return Err(e);
297297- }
298298-299299- Ok(())
30036}