11use core:: time;
2- use std:: path:: Path ;
3- use std:: { collections:: HashMap , path :: PathBuf , thread} ;
2+ use std:: path:: { Path , PathBuf } ;
3+ use std:: { cmp , collections:: HashMap , collections :: HashSet , iter :: FromIterator , thread} ;
44
55use config:: LinkCheckerLevel ;
66use libs:: rayon:: prelude:: * ;
@@ -52,7 +52,7 @@ pub fn check_internal_links_with_anchors(site: &Site) -> Vec<String> {
5252 full_path. push ( part) ;
5353 }
5454 // NOTE: This will also match _index.foobar.md where foobar is not a language
55- // as well as any other sring containing "_index." which is now referenced as
55+ // as well as any other string containing "_index." which is now referenced as
5656 // unsupported page path in the docs.
5757 if md_path. contains ( "_index." ) {
5858 let section = library. sections . get ( & full_path) . unwrap_or_else ( || {
@@ -172,7 +172,11 @@ pub fn check_external_links(site: &Site) -> Vec<String> {
172172
173173 println ! (
174174 "Checking {} external link(s). Skipping {} external link(s).{}" ,
175- checked_links. len( ) ,
175+ // Get unique links count from Vec by creating a temporary HashSet.
176+ HashSet :: <& str >:: from_iter(
177+ checked_links. iter( ) . map( |link_def| link_def. external_link. as_str( ) )
178+ )
179+ . len( ) ,
176180 skipped_link_count,
177181 if invalid_url_links == 0 {
178182 "" . to_string( )
@@ -199,24 +203,43 @@ pub fn check_external_links(site: &Site) -> Vec<String> {
199203 }
200204 }
201205
206+ let cpu_count = match thread:: available_parallelism ( ) {
207+ Ok ( count) => count. get ( ) ,
208+ Err ( _) => 1 ,
209+ } ;
202210 // create thread pool with lots of threads so we can fetch
203211 // (almost) all pages simultaneously, limiting all links for a single
204212 // domain to one thread to avoid rate-limiting
205- let threads = std :: cmp:: min ( links_by_domain. len ( ) , 8 ) ;
206- match rayon:: ThreadPoolBuilder :: new ( ) . num_threads ( threads ) . build ( ) {
213+ let num_threads = cmp:: min ( links_by_domain. len ( ) , cmp :: max ( 8 , cpu_count ) ) ;
214+ match rayon:: ThreadPoolBuilder :: new ( ) . num_threads ( num_threads ) . build ( ) {
207215 Ok ( pool) => {
208216 let errors = pool. install ( || {
209217 links_by_domain
210218 . par_iter ( )
211219 . map ( |( _, links) | {
212220 let mut num_links_left = links. len ( ) ;
221+ let mut checked_links: HashMap < & str , Option < link_checker:: Result > > =
222+ HashMap :: new ( ) ;
213223 links
214224 . iter ( )
215225 . filter_map ( move |link_def| {
216226 num_links_left -= 1 ;
217227
228+ // Avoid double-checking the same url (e.g. for translated pages).
229+ let external_link = link_def. external_link . as_str ( ) ;
230+ if let Some ( optional_res) = checked_links. get ( external_link) {
231+ if let Some ( res) = optional_res {
232+ return Some ( (
233+ & link_def. file_path ,
234+ external_link,
235+ res. clone ( ) ,
236+ ) ) ;
237+ }
238+ return None ;
239+ }
240+
218241 let res = link_checker:: check_url (
219- & link_def . external_link ,
242+ external_link,
220243 & site. config . link_checker ,
221244 ) ;
222245
@@ -226,9 +249,11 @@ pub fn check_external_links(site: &Site) -> Vec<String> {
226249 }
227250
228251 if link_checker:: is_valid ( & res) {
252+ checked_links. insert ( external_link, None ) ;
229253 None
230254 } else {
231- Some ( ( & link_def. file_path , & link_def. external_link , res) )
255+ checked_links. insert ( external_link, Some ( res. clone ( ) ) ) ;
256+ return Some ( ( & link_def. file_path , external_link, res) ) ;
232257 }
233258 } )
234259 . collect :: < Vec < _ > > ( )
0 commit comments