1+ use std:: collections:: BTreeMap ;
12use std:: sync:: atomic:: Ordering ;
2- use std:: time:: Duration ;
3+ use std:: time:: { Duration , Instant } ;
34use std:: { io, thread} ;
45
6+ use exponential_backoff:: { Backoff , IntoIter as BackoffIter } ;
7+ use hashbrown:: HashMap ;
58use magnetic:: Consumer ;
69use magnetic:: buffer:: dynamic:: DynamicBufferP2 ;
7- use rusqlite:: { Connection , OpenFlags , OptionalExtension } ;
10+ use rusqlite:: { Connection , ErrorCode , OpenFlags , OptionalExtension } ;
811use thiserror:: Error ;
912
1013use crate :: SHUTDOWN ;
1114use crate :: crawler:: RequestCrawl ;
1215use crate :: crawler:: types:: { Command , CommandSender , RequestCrawlReceiver , Status , StatusReceiver } ;
1316use crate :: crawler:: worker:: { Worker , WorkerError } ;
14- use crate :: types:: MessageSender ;
17+ use crate :: types:: { Cursor , MessageSender } ;
1518
16- const CAPACITY : usize = 1024 ;
19+ const CAPACITY : usize = 1 << 12 ;
1720const SLEEP : Duration = Duration :: from_millis ( 10 ) ;
1821
1922#[ derive( Debug , Error ) ]
@@ -45,6 +48,8 @@ struct WorkerHandle {
4548pub struct Manager {
4649 workers : Box < [ WorkerHandle ] > ,
4750 next_id : usize ,
51+ hosts : HashMap < String , [ BackoffIter ; 2 ] > ,
52+ retries : BTreeMap < Instant , ( usize , String ) > ,
4853 conn : Connection ,
4954 request_crawl_rx : RequestCrawlReceiver ,
5055 status_rx : StatusReceiver ,
@@ -76,38 +81,24 @@ impl Manager {
7681 Ok ( Self {
7782 workers : workers. into_boxed_slice ( ) ,
7883 next_id : 0 ,
84+ hosts : HashMap :: new ( ) ,
85+ retries : BTreeMap :: new ( ) ,
7986 conn,
8087 request_crawl_rx,
8188 status_rx,
8289 } )
8390 }
8491
8592 pub fn run ( mut self ) -> Result < ( ) , ManagerError > {
86- let mut requests = Vec :: new ( ) ;
87- {
88- let mut stmt = self . conn . prepare_cached ( "SELECT host, cursor FROM hosts" ) ?;
89- let mut rows = stmt. query ( ( ) ) ?;
90- while let Some ( row) = rows. next ( ) ? {
91- let hostname = row. get_unwrap ( "host" ) ;
92- let cursor: u64 = row. get_unwrap ( "cursor" ) ;
93- requests. push ( RequestCrawl { hostname, cursor : Some ( cursor. into ( ) ) } ) ;
94- }
95- }
96- for request in requests {
97- self . handle_connect ( request) ?;
98- }
9993 while self . update ( ) ? {
10094 thread:: sleep ( SLEEP ) ;
10195 }
10296 tracing:: info!( "shutting down crawler" ) ;
103- SHUTDOWN . store ( true , Ordering :: Relaxed ) ;
10497 self . shutdown ( )
10598 }
10699
107- pub fn shutdown ( mut self ) -> Result < ( ) , ManagerError > {
108- for worker in & mut self . workers {
109- worker. command_tx . push ( Command :: Shutdown ) ?;
110- }
100+ pub fn shutdown ( self ) -> Result < ( ) , ManagerError > {
101+ SHUTDOWN . store ( true , Ordering :: Relaxed ) ;
111102 for ( id, worker) in self . workers . into_iter ( ) . enumerate ( ) {
112103 if let Err ( err) = worker. thread_handle . join ( ) . map_err ( |_| ManagerError :: Join ) ? {
113104 tracing:: warn!( %id, %err, "crawler worker error" ) ;
@@ -121,51 +112,74 @@ impl Manager {
121112 return Ok ( false ) ;
122113 }
123114
124- if let Ok ( status) = self . status_rx . try_pop ( ) {
125- if !self . handle_status ( status) ? {
126- return Ok ( false ) ;
115+ if let Some ( entry) = self . retries . first_entry ( ) {
116+ if * entry. key ( ) < Instant :: now ( ) {
117+ let ( id, hostname) = entry. remove ( ) ;
118+ let prev = self . next_id ;
119+ self . next_id = id;
120+ self . handle_connect ( RequestCrawl { hostname, cursor : None } ) ?;
121+ self . next_id = prev;
127122 }
128123 }
129124
125+ if let Ok ( status) = self . status_rx . try_pop ( ) {
126+ self . handle_status ( status) ;
127+ }
128+
130129 if let Ok ( request_crawl) = self . request_crawl_rx . pop ( ) {
131- let exists = {
132- let mut stmt = self . conn . prepare_cached ( "SELECT * FROM hosts WHERE host = ?1" ) ?;
133- stmt. exists ( ( & request_crawl. hostname , ) ) ?
134- } ;
135- if !exists {
136- thread:: sleep ( SLEEP ) ;
130+ if !self . hosts . contains_key ( & request_crawl. hostname ) {
137131 self . handle_connect ( request_crawl) ?;
138132 }
139133 }
140134
141135 Ok ( true )
142136 }
143137
144- fn handle_status ( & mut self , status : Status ) -> Result < bool , ManagerError > {
138+ fn handle_status ( & mut self , status : Status ) {
145139 match status {
146- Status :: Disconnected ( id, hostname) => {
147- // TODO: add proper backoff
148- thread :: sleep ( SLEEP * 1000 ) ;
149- let prev = self . next_id ;
150- self . next_id = id ;
151- self . handle_connect ( RequestCrawl { hostname , cursor : None } ) ? ;
152- self . next_id = prev ;
140+ Status :: Disconnected { worker_id : id, hostname, connected } => {
141+ # [ expect ( clippy :: unwrap_used ) ]
142+ let backoff =
143+ self . hosts . get_mut ( & hostname ) . unwrap ( ) . get_mut ( usize :: from ( connected ) ) . unwrap ( ) ;
144+ let Some ( Some ( delay ) ) = backoff . next ( ) else { unreachable ! ( ) } ;
145+ let next = Instant :: now ( ) + delay ;
146+ assert ! ( self . retries . insert ( next , ( id , hostname ) ) . is_none ( ) ) ;
153147 }
154148 }
155- Ok ( true )
156149 }
157150
158151 fn handle_connect ( & mut self , mut request_crawl : RequestCrawl ) -> Result < ( ) , ManagerError > {
159- let cursor: Option < u64 > = {
160- let mut stmt = self . conn . prepare_cached ( "SELECT * FROM hosts WHERE host = ?1" ) ?;
161- stmt. query_row ( ( & request_crawl. hostname , ) , |row| Ok ( row. get_unwrap ( "cursor" ) ) )
162- . optional ( ) ?
163- } ;
164- if let Some ( cursor) = cursor {
165- request_crawl. cursor = Some ( cursor. into ( ) ) ;
152+ self . hosts . entry ( request_crawl. hostname . clone ( ) ) . or_insert_with ( || {
153+ let backoff_connect =
154+ Backoff :: new ( u32:: MAX , Duration :: from_secs ( 60 ) , Duration :: from_secs ( 60 * 60 * 6 ) ) ;
155+ let backoff_reconnect =
156+ Backoff :: new ( u32:: MAX , Duration :: from_secs ( 1 ) , Duration :: from_secs ( 60 * 60 ) ) ;
157+ [ backoff_connect. iter ( ) , backoff_reconnect. iter ( ) ]
158+ } ) ;
159+ if request_crawl. cursor . is_none ( ) {
160+ request_crawl. cursor = loop {
161+ match self . get_cursor ( & request_crawl. hostname ) {
162+ Ok ( cursor) => break cursor,
163+ Err ( ManagerError :: Sqlite ( err) )
164+ if err. sqlite_error_code ( ) == Some ( ErrorCode :: DatabaseLocked ) =>
165+ {
166+ continue ;
167+ }
168+ Err ( err) => Err ( err) ?,
169+ }
170+ } ;
166171 }
167172 self . workers [ self . next_id ] . command_tx . push ( Command :: Connect ( request_crawl) ) ?;
168173 self . next_id = ( self . next_id + 1 ) % self . workers . len ( ) ;
174+ thread:: sleep ( SLEEP ) ;
169175 Ok ( ( ) )
170176 }
177+
178+ fn get_cursor ( & self , host : & str ) -> Result < Option < Cursor > , ManagerError > {
179+ let mut stmt = self . conn . prepare_cached ( "SELECT * FROM hosts WHERE host = ?1" ) ?;
180+ Ok ( stmt
181+ . query_row ( ( & host, ) , |row| Ok ( row. get_unwrap :: < _ , u64 > ( "cursor" ) ) )
182+ . optional ( ) ?
183+ . map ( Into :: into) )
184+ }
171185}
0 commit comments