diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index c8ca83ae5..8fd62c8a9 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1130,6 +1130,11 @@ struct OutFile { /// and [`n_chunks_by_line_round_robin`] functions. type OutFiles = Vec; trait ManageOutFiles { + fn instantiate_writer( + &mut self, + idx: usize, + settings: &Settings, + ) -> UResult<&mut BufWriter>>; /// Initialize a new set of output files /// Each OutFile is generated with filename, while the writer for it could be /// optional, to be instantiated later by the calling function as needed. @@ -1194,6 +1199,52 @@ impl ManageOutFiles for OutFiles { Ok(out_files) } + fn instantiate_writer( + &mut self, + idx: usize, + settings: &Settings, + ) -> UResult<&mut BufWriter>> { + let mut count = 0; + // Use-case for doing multiple tries of closing fds: + // E.g. split running in parallel to other processes (e.g. another split) doing similar stuff, + // sharing the same limits. In this scenario, after closing one fd, the other process + // might "steel" the freed fd and open a file on its side. Then it would be beneficial + // if split would be able to close another fd before cancellation. + 'loop1: loop { + let filename_to_open = self[idx].filename.as_str(); + let file_to_open_is_new = self[idx].is_new; + let maybe_writer = + settings.instantiate_current_writer(filename_to_open, file_to_open_is_new); + if let Ok(writer) = maybe_writer { + self[idx].maybe_writer = Some(writer); + return Ok(self[idx].maybe_writer.as_mut().unwrap()); + } + + if settings.filter.is_some() { + // Propagate error if in `--filter` mode + return Err(maybe_writer.err().unwrap().into()); + } + + // Could have hit system limit for open files. + // Try to close one previously instantiated writer first + for (i, out_file) in self.iter_mut().enumerate() { + if i != idx && out_file.maybe_writer.is_some() { + out_file.maybe_writer.as_mut().unwrap().flush()?; + out_file.maybe_writer = None; + out_file.is_new = false; + count += 1; + + // And then try to instantiate the writer again + continue 'loop1; + } + } + + // If this fails - give up and propagate the error + uucore::show_error!("at file descriptor limit, but no file descriptor left to close. Closed {count} writers before."); + return Err(maybe_writer.err().unwrap().into()); + } + } + fn get_writer( &mut self, idx: usize, @@ -1204,43 +1255,7 @@ impl ManageOutFiles for OutFiles { } else { // Writer was not instantiated upfront or was temporarily closed due to system resources constraints. // Instantiate it and record for future use. - let maybe_writer = - settings.instantiate_current_writer(self[idx].filename.as_str(), self[idx].is_new); - if let Ok(writer) = maybe_writer { - self[idx].maybe_writer = Some(writer); - Ok(self[idx].maybe_writer.as_mut().unwrap()) - } else if settings.filter.is_some() { - // Propagate error if in `--filter` mode - Err(maybe_writer.err().unwrap().into()) - } else { - // Could have hit system limit for open files. - // Try to close one previously instantiated writer first - for (i, out_file) in self.iter_mut().enumerate() { - if i != idx && out_file.maybe_writer.is_some() { - out_file.maybe_writer.as_mut().unwrap().flush()?; - out_file.maybe_writer = None; - out_file.is_new = false; - break; - } - } - // And then try to instantiate the writer again - // If this fails - give up and propagate the error - let result = settings - .instantiate_current_writer(self[idx].filename.as_str(), self[idx].is_new); - if let Err(e) = result { - let mut count = 0; - for out_file in self { - if out_file.maybe_writer.is_some() { - count += 1; - } - } - - return Err(USimpleError::new(e.raw_os_error().unwrap_or(1), - format!("Instantiation of writer failed due to error: {e:?}. Existing writer number: {count}"))); - } - self[idx].maybe_writer = Some(result?); - Ok(self[idx].maybe_writer.as_mut().unwrap()) - } + self.instantiate_writer(idx, settings) } } }