1

I'm having trouble debugging the following program I wrote. The idea is to have two seperate threads; one thread executes a 5 second countdown while the other waits for key input from the user. Whichever thread completes first should cancel the sibling thread and exit the program. However, the following code just hangs.

Any help would be appreciated, but I would be most grateful for an explanation as to the problem.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // For sleep()

#define NUM_THREADS 2

// The stuct to be passed as an argument to the countdown routine
typedef struct countdown_struct {
pthread_t *thread;
signed int num_secs;
} CountdownStruct;

// Struct for passing to the input wait routine
typedef struct wait_struct {
pthread_t *thread;
int *key;
} WaitStruct;

// Countdown routine; simply acts as a timer counting down
void * countdown(void *args)
{
CountdownStruct *cd_str = (CountdownStruct *)args;
signed int secs = cd_str->num_secs;
printf("Will use default setting in %d seconds...", secs);
while (secs >= 0)
{
    sleep(1);
    secs -= 1;
    printf("Will use default setting in %d seconds...", secs);
}

// Cancel the other struct
pthread_cancel(*(cd_str->thread));
return NULL;
}

// Waits for the user to pass input through the tty
void * wait_for_input(void *args)
{
WaitStruct *wait_str = (WaitStruct *) args;
int c = 0;
do {
    c = getchar();
} while (!(c == '1' || c == '2'));
*(wait_str->key) = c;

// Cancel the other thread
pthread_cancel(*(wait_str->thread));
return NULL;
}

int main(int argc, char **argv)
{
pthread_t wait_thread;
pthread_t countdown_thread;
pthread_attr_t attr;
int key=0;
long numMillis=5000;
int rc=0;
int status=0;

// Create the structs to be passe as paramaters to both routines
CountdownStruct *cd_str = (CountdownStruct *) malloc(sizeof(CountdownStruct));
if (cd_str == NULL)
{
    printf("Couldn't create the countdown struct. Aborting...");
    return -1;
}
cd_str->thread = &wait_thread;
cd_str->num_secs = 5;

WaitStruct *wait_str = (WaitStruct *) malloc(sizeof(WaitStruct));
if (wait_str == NULL)
{
    printf("Couldn't create the iput wait struct. Aborting...");
    return -1;
}
wait_str->thread = &countdown_thread;
wait_str->key = &key;

// Create the joinable attribute
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

// Create both threads
rc = pthread_create(&countdown_thread, &attr, countdown, (void *) cd_str);
if (rc) { printf("Error with the thread creation!"); exit(-1); }
rc = pthread_create(&wait_thread, &attr, wait_for_input, (void *) wait_str);
if (rc) { printf("Error with the thread creation!"); exit(-1); }

// Destroy the pthread_attribute
pthread_attr_destroy(&attr);

// now join on the threads and wait for main
pthread_join(wait_thread, NULL);
pthread_join(countdown_thread, NULL);

// Call pthread_exit
pthread_exit(NULL);

// Free the function structs
free(cd_str);
free(wait_str);
}
Lancophone
  • 310
  • 2
  • 17
  • I notice in a couple spots including http://stackoverflow.com/questions/433989/posix-cancellation-points that getchar() is not required to be a cancellation point. I suppose doing a select on stdin would give you the timeout and the read. – dennis May 26 '14 at 01:12
  • @dennis Even with the call to getchar() the timeout thread never completes and closes the program. This is the bit that is most puzzling. Thanks for the help, but select() is a workaround and doesn't deal with the problem stated here – Lancophone May 26 '14 at 01:22
  • You meant without the getchar() call? Then the wait_for_input thread would sit in a spin loop and never hit a cancellation point. The join would hang preventing main from exiting. Or did you just let the wait_for_input loop fall through? – dennis May 26 '14 at 01:29
  • Cut and pasted on to my Fedora 19 machine, compiled, and it exited after the timeout. (well counted to -1, but close enough.) – dennis May 26 '14 at 01:41
  • @dennis I fell through. That's odd; the timeout loop wouldn't exit for me. I'm running on Mac so could it be a platform issue? I'll try your answer now to see if it works for me – Lancophone May 26 '14 at 10:06
  • It is optional if getchar is cancellation point, so possible that Fedora 19 it is and Mac it isn't. In way of explanation, cancel is not like kill. Since threads share data structures killing one could leave data structures used by others in an undetermined state corrupting the entire application. I believe the implementation of cancel with a limited number of points where the thread can check to see if it has been requested to terminate is intended to provide better safety for the application than a kill-like termination. – dennis May 26 '14 at 12:34

1 Answers1

2

Getchar is not required to be a cancellation point. Select and pselect are. Even if you want to continue to use a countdown thread you could still provide a cancellation point in the opposing thread by use of select.

I had reasonable behavior with the following modified wait_for_input()

    // Waits for the user to pass input through the tty
    void * wait_for_input(void *args)
    {
      WaitStruct *wait_str = (WaitStruct *) args;
      int c = 0;
      fd_set  readFds;
      int numFds=0;
      FD_ZERO(&readFds);

      do {
        struct timeval timeout={.tv_sec=8,.tv_usec=0};
        /* select here is primarily to serve as a cancellation
         * point.  Though there is a possible race condition
         * still between getchar() getting called right as the
         * the timeout thread calls cancel.(). 
         * Using the timeout option on select would at least 
         * cover that, but not done here while testing. 
         *******************************************************/  
        FD_ZERO(&readFds);
        FD_SET(STDOUT_FILENO,&readFds);
        numFds=select(STDOUT_FILENO+1,&readFds,NULL,NULL,&timeout);
        if(numFds==0 )  
        {
          /* must be timeout if no FD's selected */
          break;
        }
        if(FD_ISSET(STDOUT_FILENO,&readFds))
        {
          printf("Only get here if key pressed\n");
          c = getchar();
        }  
      } while (!(c == '1' || c == '2'));
      *(wait_str->key) = c;
      // Cancel the other thread
      pthread_cancel(*(wait_str->thread));
      return NULL;
    }
dennis
  • 221
  • 1
  • 5
  • solution worked for me. Fair point on the count to -1. Printf also won't flush without the return character. I wrote this at 2am though if thats a valid excuse ;-) Thanks again – Lancophone May 26 '14 at 10:36