Mam ostatnio problemy z programem, który ubijam poleceniem timeout.

Program wykonuje setki(w zasadzie to grupowo robi 10000) operacji zapisu plików do określonego folderu z wątków rayona(rust) i wygląda na to, że bez względu czy ubijam go sygnałem TERM czy KILL, to nieco później (0-10s) po zabiciu programu, nie mogę usunąć całego folderu z plikami, bo wygląda, że program ciągle w tle tworzy nowe pliki, więc próba usunięcia takiego katalogu przez "rm -rf" wypisuje błąd "rm: cannot remove '/opt/tmp_folder/short_normal_1/16474004021118382402': Directory not empty"

Zatem by rozwiązać problem przerzucam timer końca działania do programu zamiast ubijać program z zewnątrz.

Jednak mam tutaj ponownie zagwozdkę.
Mam dwie koncepcje

Pierwsza to taka, że pierwszy wątek który złapie problem, to przerywa cały program:
fn check_for_exit() {
  if time_left < 0 {
      process::exit(127);
  }
}


files_chunks.into_par_iter().for_each(|| {
   check_for_exit();

   for file in files_chunks {
       fs::copy("file", output_dir);
   }
});

Druga to taka, że czekam aż wszystkie wątki się skończą i dopiero wtedy przerywam wykonywanie programu

fn check_for_exit() -> bool {
  return time_left < 0;
}

files_chunks.into_par_iter().map(|| {
   if check_for_exit() {
       return None;
   }

   for file in files_chunks {
       fs::copy("file", output_dir);
   }

   Some(())
}).while_some().collect<()>();

if check_for_exit() {
   process::exit(127);
}

Niby punkt drugi bezpieczniejszy, ale punkt pierwszy też przecież przecież powinien wszystkie wątki z kopiowaniem plików ubić. Dobrze kminię, czy jednak punkt pierwszy nie jest bezpieczny?

#programowanie
#rustlang
Orzech

@qarmin Nie pisałem dawno w rust, zwłaszcza na tym poziomie, ale zdecydowanie druga opcja. Wydaje mi się, że w pierwszej opcji będziesz miał proces w kolejce do ubicia/ubity, a to co zostanie to będą tzw. detached threads. Ale nie jestem (już) ekspertem, podpytaj może kogoś innego

globalbus

@qarmin a to nie jest kwestia tego, że operacje na plikach robi kernel? Ubicie procesu nie przerywa fs::copy.


Po drugie, obsługa sygnałów nie jest synchroniczna. Jak zrobisz kill PID && rm costam, to na pewno to nie zadziała. Musisz poczekać, aż proces obsłuży sygnał i się zamknie.


Jak robisz timeout na wątkach wewnątrz programu, to z pewnością da się to bardziej elegancko obsłużyć.

lexico

@qarmin Analizując obie koncepcje, które przedstawiłeś, można zauważyć kilka istotnych różnic w sposobie zarządzania zakończeniem wątków i zatrzymaniem programu.

Pierwsza koncepcja


  • Zalety:

  • Każdy wątek sprawdza warunek time_left < 0 przed rozpoczęciem kopiowania.

  • Jeśli warunek jest spełniony, natychmiast wywołuje process::exit(127), co natychmiastowo kończy cały program.

  • Wady:

  • process::exit(127) powoduje natychmiastowe zakończenie programu bez czekania na zakończenie pozostałych wątków. To może skutkować niekompletnym zakończeniem operacji IO, co może być przyczyną problemów z plikami.

  • Możliwe nieprzewidywalne zachowanie, jeśli process::exit(127) jest wywoływane z wielu wątków jednocześnie.


Druga koncepcja


  • Zalety:

  • Sprawdza warunek time_left < 0 przed rozpoczęciem kopiowania w każdym wątku, ale zamiast natychmiastowego zakończenia, wątki, które spełniają warunek, po prostu kończą swoją pracę.

  • Pozwala wszystkim aktywnym wątkom dokończyć swoje operacje kopiowania, zanim program sprawdzi, czy powinien zakończyć się process::exit(127).

  • Bezpieczniejsze podejście, ponieważ nie powoduje natychmiastowego zakończenia programu, co pozwala na bardziej przewidywalne zarządzanie zasobami.

  • Wady:

  • Może powodować krótkie opóźnienie w zakończeniu programu, jeśli trzeba czekać na zakończenie wszystkich wątków.


Wnioski

Druga koncepcja jest bardziej bezpieczna i elegancka, ponieważ pozwala na kontrolowane zakończenie programu i uniknięcie problemów związanych z nieskończonym tworzeniem plików po wywołaniu timeout.

Natychmiastowe zakończenie programu przy użyciu process::exit w pierwszej koncepcji może prowadzić do nieprzewidywalnych problemów związanych z niedokończonymi operacjami IO. W drugiej koncepcji wątki mogą bezpiecznie zakończyć swoje zadania, co zmniejsza ryzyko wystąpienia problemów z plikami i zasobami.

Zatem rekomenduję skorzystanie z drugiej koncepcji. Jeśli jednak decydujesz się na pierwszą koncepcję, warto wprowadzić mechanizm, który upewni się, że wszystkie wątki zakończyły swoją pracę przed zamknięciem programu, aby uniknąć problemów z niekompletnym przetwarzaniem plików.

Zaloguj się aby komentować