@@ -253,37 +253,45 @@ pub fn process_files_for_fragments(
253253 let max_frags = LIMITS . max_fragments ;
254254 let max_generated = LIMITS . max_generated_fragments ;
255255
256- let file_contents: Vec < ( PathBuf , String ) > = files
257- . iter ( )
258- . filter_map ( |file_path| {
259- let content = read_file_content (
260- file_path,
261- root_dir,
262- preferred_revs,
263- batch_reader. as_deref_mut ( ) ,
264- ) ?;
265- Some ( ( file_path. clone ( ) , content) )
266- } )
267- . collect ( ) ;
268-
269- let parsed: Vec < Vec < Fragment > > = file_contents
270- . par_iter ( )
271- . map ( |( file_path, content) | {
272- let path_arc: Arc < str > = Arc :: from ( file_path. to_string_lossy ( ) . as_ref ( ) ) ;
273- let mut raw_frags = fragment_file ( path_arc, content) ;
274-
275- let generated = is_generated_file ( file_path, content) ;
276- let cap = if generated { max_generated } else { max_frags } ;
277- if raw_frags. len ( ) > cap {
278- raw_frags. sort_by ( |a, b| b. line_count ( ) . cmp ( & a. line_count ( ) ) ) ;
279- raw_frags. truncate ( cap) ;
280- }
281- if generated {
282- raw_frags = truncate_generated_fragments ( raw_frags) ;
283- }
284- raw_frags
285- } )
286- . collect ( ) ;
256+ // Process files in chunks: sequential read (CatFileBatch is &mut, !Send) then
257+ // parallel parse within each chunk. Peak raw-content memory = chunk_size × max_file_size
258+ // instead of N_files × avg_file_size — eliminates OOM on large repos like astropy.
259+ let chunk_size = rayon:: current_num_threads ( ) . max ( 1 ) ;
260+ let mut parsed: Vec < Vec < Fragment > > = Vec :: with_capacity ( files. len ( ) ) ;
261+ for chunk in files. chunks ( chunk_size) {
262+ let chunk_contents: Vec < ( PathBuf , String ) > = chunk
263+ . iter ( )
264+ . filter_map ( |file_path| {
265+ let content = read_file_content (
266+ file_path,
267+ root_dir,
268+ preferred_revs,
269+ batch_reader. as_deref_mut ( ) ,
270+ ) ?;
271+ Some ( ( file_path. clone ( ) , content) )
272+ } )
273+ . collect ( ) ;
274+ parsed. extend (
275+ chunk_contents
276+ . par_iter ( )
277+ . map ( |( file_path, content) | {
278+ let path_arc: Arc < str > = Arc :: from ( file_path. to_string_lossy ( ) . as_ref ( ) ) ;
279+ let mut raw_frags = fragment_file ( path_arc, content) ;
280+ let generated = is_generated_file ( file_path, content) ;
281+ let cap = if generated { max_generated } else { max_frags } ;
282+ if raw_frags. len ( ) > cap {
283+ raw_frags. sort_by ( |a, b| b. line_count ( ) . cmp ( & a. line_count ( ) ) ) ;
284+ raw_frags. truncate ( cap) ;
285+ }
286+ if generated {
287+ raw_frags = truncate_generated_fragments ( raw_frags) ;
288+ }
289+ raw_frags
290+ } )
291+ . collect :: < Vec < _ > > ( ) ,
292+ ) ;
293+ // chunk_contents dropped here — raw file text freed before next chunk
294+ }
287295
288296 let mut fragments: Vec < Fragment > = Vec :: new ( ) ;
289297 for file_frags in parsed {
0 commit comments