Op zoek naar begeleiding bij een deadlockscenario

Ik heb een programma dat veel kinderen spawt en voor lange periodes werkt. Het programma bevat een SIGCHLD-handler om achterhaalde processen te kunnen oogsten. Af en toe bevriest dit programma. Ik geloof dat pstack een impasse scenario aangeeft. Is dat de juiste interpretatie van deze output?

10533:  ./asyncsignalhandler
 ff3954e4 lwp_park (0, 0, 0)
 ff391bbc slow_lock (ff341688, ff350000, 0, 0, 0, 0) + 58
 ff2c45c8 localtime_r (ffbfe7a0, 0, 0, 0, 0, 0) + 24
 ff2ba39c __posix_ctime_r (ffbfe7a0, ffbfe80e, ffbfe7a0, 0, 0, 0) + c
 00010bd8 gettimestamp (ffbfe80e, ffbfe828, 40, 0, 0, 0) + 18
 00010c50 sig_chld (12, 0, ffbfe9f0, 0, 0, 0) + 30
 ff3956fc __sighndlr (12, 0, ffbfe9f0, 10c20, 0, 0) + c
 ff38f354 call_user_handler (12, 0, ffbfe9f0, 0, 0, 0) + 234
 ff38f504 sigacthandler (12, 0, ffbfe9f0, 0, 0, 0) + 64
 --- called from signal handler with signal 18 (SIGCLD) ---
 ff391c14 pthread_mutex_lock (20fc8, 0, 0, 0, 0, 0) + 48
 ff2bcdec getenv   (ff32a9ac, 770d0, 0, 0, 0, 0) + 1c
 ff2c6f40 getsystemTZ (0, 79268, 0, 0, 0, 0) + 14
 ff2c4da8 ltzset_u (4ede65ba, 0, 0, 0, 0, 0) + 14
 ff2c45d0 localtime_r (ffbff378, 0, 0, 0, 0, 0) + 2c
 ff2ba39c __posix_ctime_r (ffbff378, ffbff402, ffbff378, ff33e000, 0, 0) + c
 00010bd8 gettimestamp (ffbff402, ffbff402, 2925, 29a7, 79c38, 10b54) + 18
 00010ae0 main     (1, ffbff4ac, ffbff4b4, 20c00, 0, 0) + 190
 00010928 _start   (0, 0, 0, 0, 0, 0) + 108

Ik vind mezelf niet echt een C-coder en ben niet bekend met de nuances van de taal. Ik gebruik specifiek de herintredende versie van ctime (_r) in het programma. Waarom zit dit nog steeds vast?

#include 
#include 
#include 

#include 

// import pid_t type
#include 

// import _exit function
#include 

// import WNOHANG definition
#include 

// import errno variable
#include 

// header for signal functions
#include 

// function prototypes
void sig_chld(int);
char * gettimestamp(char *);

// begin
int main(int argc, char **argv)
{
   time_t   sleepstart;
   time_t   sleepcheck;
   pid_t    childpid;
   int i;
   unsigned int sleeptime;
   char sleepcommand[20];
   char ctime_buf[26];

   struct sigaction act;

   /* set stdout to line buffered for logging purposes */
   setvbuf(stdout, NULL, _IOLBF, BUFSIZ);

   /* Assign sig_chld as our SIGCHLD handler */
   act.sa_handler = sig_chld;

   /* We don't want to block any other signals */
   sigemptyset(&act.sa_mask);

   /*
    * We're only interested in children that have terminated, not ones
    * which have been stopped (eg user pressing control-Z at terminal)
    */
   act.sa_flags = SA_NOCLDSTOP;

   /* Make these values effective. */
   if (sigaction(SIGCHLD, &act, NULL) < 0) 
   {
      printf("sigaction failed\n");
      return 1;
   }

   while (1) {
      for (i = 0; i < 20; i++) {
         /*   fork/exec child program                                */
         childpid = fork();
         if (childpid == 0)//child
         {
            //sleeptime = 30 + i;
            sprintf(sleepcommand, "sleep %d", i);

            printf("\t[%s][%d] Executing /bin/sh -c %s\n", gettimestamp(ctime_buf), getpid(), sleepcommand);

            execl("/bin/sh", "/bin/sh", "-c", sleepcommand, NULL);

           //only executed if exec fails
            printf("[%s][%d] Error executing program, errno: %d\n", gettimestamp(ctime_buf), getpid(), errno);
            _exit(1);
         }
         else if (childpid < 0)//error
         {
            printf("[%s][%d] Error forking, errno: %d\n", gettimestamp(ctime_buf), getpid(), errno);
         }
         else//parent
         {
            printf("[%s][%d] Spawned child, pid: %d\n", gettimestamp(ctime_buf), getpid(), childpid);
         }
      }

     //sleep is interrupted by SIGCHLD, so we can't simply sleep(5)
      printf("[%s][%d] Sleeping for 5 seconds\n", gettimestamp(ctime_buf), getpid());
      time(&sleepstart);
      while (1) {
         time(&sleepcheck);
         if (difftime(sleepcheck, sleepstart) < 5) {
            sleep(1);
         } else {
            break;
         }
      }
   }


   return(0);
}

char * gettimestamp(char *ctime_buf)
{
   time_t now;

   time(&now);

  //format the timestamp and chomp the newline
   ctime_r(&now, ctime_buf);
   ctime_buf[strlen(ctime_buf) - 1] = '\0';

   return ctime_buf;
}

/*
 * The signal handler function -- only gets called when a SIGCHLD
 * is received, ie when a child terminates.
 */
void sig_chld(int signo)
{
   pid_t childpid;
   int childexitstatus;
   char ctime_buf[26];

   while (1) {
      childpid = waitpid(-1, &childexitstatus, WNOHANG);
      if (childpid > 0)
         printf("[%s][%d] Reaped child, pid: %d, exitstatus: %d\n", gettimestamp(ctime_buf), getpid(), childpid, WEXITSTATUS(childexitstatus));
      else
         return;
   }
}

Ik loop in een Solaris 9-omgeving. Het programma is gecompileerd met Sun WorkShop 6 update 2 C 5.3 Patch 111679-15 2009/09/10 met behulp van de volgende syntaxis:

cc -o asyncsignalhandler asyncsignalhandler.c -mt -D_POSIX_PTHREAD_SEMANTICS

Is er een fout in het programma? Zijn er betere manieren om het loggen (met tijdstempels) van een signaalverwerker af te handelen?

0
printf is niet async-signaalveilig, dus gebruik dat niet in een signaalhandler ... gebruik write in plaats van fileno (stdout) .
toegevoegd de auteur Jason, de bron
Hoewel ik zal toegeven dat printf() mogelijk een probleem is, is dat niet wat de stacktrack me vertelt. Hoe kom je bij printf() aan als de bron van de deadlock?
toegevoegd de auteur user255205, de bron

1 antwoord

U roept functies aan die niet async-signaalveilig zijn (zie sectie 2.4. 3 van de unix-specificatie) vanuit een signaalverwerker - in dit geval ctime_r() en printf() (de impasse lijkt te gebeuren vanwege een slot dat wordt gebruikt door ctime_r() in de stacktrack die je laat zien). Deze functies kunnen vastlopen en aangezien op enig moment een signaalverwerker kan worden opgeroepen, kan het slot al worden vastgehouden, wat resulteert in een impasse.

In het algemeen, in een signaal handler, alles wat je zou moeten is om een ​​notitie te maken voor de rode draad om later te bekijken. U kunt bijvoorbeeld write() (een asynchrone signaalveilige functie) gebruiken voor een pipe() -created file descriptor en uw hoofdlus (of een andere thread) doet een select lus om te wachten tot sommige gegevens op die pijp verschijnen.

Merk ook op dat thread-safe niet hetzelfde is als async-signaalveilig . ctime_r is thread safe - er zijn vergrendelingen voor nodig om ervoor te zorgen dat threads niet op elkaar gaan staan ​​en het gebruikt een doorgegeven buffer in plaats van een statische buffer. Maar het is niet async-signaalveilig, omdat het niet kan tolereren dat het op elk willekeurig punt in de uitvoering ervan wordt gerangschikt.

3
toegevoegd
Ah, oeps. Maar hetzelfde - ctime_r() wordt niet vermeld als asynchroon-signaalveilig.
toegevoegd de auteur bdonlan, de bron
Dat is juist. async-signaal-safe is een veel hogere bar om door te geven dan alleen thread-safe (ook bekend als 'reentrant').
toegevoegd de auteur bdonlan, de bron
@ninjalj, helaas zitten we nu vast met de naamgeving voor compatibiliteit: /
toegevoegd de auteur bdonlan, de bron
Echt, reentrant is een zeer slechte naam voor * _ r functies. Gewoonlijk is de enige garantie die ze maken niet om statische opslag te gebruiken, maar ze kunnen intern een niet-re-enter vergrendeling gebruiken.
toegevoegd de auteur ninjalj, de bron
Ik roep localtime() niet aan. Ik roep ctime_r() aan dat op zijn beurt localtime_r() aanroept per stacktracé.
toegevoegd de auteur user255205, de bron
ctime_r() is expliciet nieuwkomer. Wat ik dacht dat was de hoogste orde van draadveiligheid? Re-toetreder zijn maakt het niet async-signaalveilig?
toegevoegd de auteur user255205, de bron