The new version of linuxthreads now seems extend cancellation points
into libc.  However, it does so in a manner that threads can get
cancelled within libc with libc holding internal locks, resulting
in deadlock.

I think the following example program demonstrates this.  It works
under libc_r, because libc_r carefully unwinds any internal locks
created within libc.  Linuxthreads does not have this capability,
and the sample program deadlocks (at least it does here).


-- 
Richard Seaman, Jr.           email: [EMAIL PROTECTED]
5182 N. Maple Lane            phone: 262-367-5450
Chenequa WI 53058             fax:   262-367-5852
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define BUFFSIZE 100000
int StateSet = 0;

/* Use these to keep track of where we are.  Can't use
 * printf's in the thread, since printf has become a
 * cancellation point.
 */
int ThreadReadStart = 0;
int ThreadReadError = 0;
int ThreadReadComplete = 0;
int ThreadRewindComplete = 0;
int ThreadRewindStart = 0;

/* Install latest linuxthreads port and compile as:
 * compile as gcc -Wall -I/usr/local/include/pthread/linuxthreads -L/usr/local/lib 
-llthread -llgcc_r-o testcancel testcancel.c
 */

FILE *testfile;

void * test_thread (void * arg)
{
  char in;
  int oldstate;
  int oldtype;


  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
  StateSet = 1;

  /* Simple synchronization to allow main to cancel us */
  sched_yield();

  /*
   * main should have cancelled us by now.  But since we're of type
   * DEFERRED, we only cancel at a cancellation point.
   */

  ThreadRewindStart = 1;

  /*
   * It appears that rewind() has become a cancellation point.
   */
  rewind (testfile);
  ThreadRewindComplete = 1;
  ThreadReadStart = 1;
  if (fread (&in, sizeof (in), 1, testfile) < 1)
      ThreadReadError = 1;
  else
      ThreadReadComplete = 1;

  return (NULL);
    
}

int main()
{

  pthread_t th;
  char in;
  char *buffer;

  buffer = malloc (BUFFSIZE);
  memset (buffer, 'A', BUFFSIZE);

  testfile = fopen ("/tmp/testcancel", "w+");
  if (testfile == NULL ||
      fwrite (buffer, sizeof (*buffer), BUFFSIZE, testfile) < BUFFSIZE)
      exit(1);

  pthread_create(&th, NULL, test_thread, NULL);
  printf ("Thread created\n");

  /* Simple syncronization to make sure the thread runs and sets its cancellation 
state */
  if (StateSet == 0)
    sched_yield();

  /* The thread's cancellation state and type should now be set */
  printf ("Cancelling thread\n");
  pthread_cancel (th);
  printf ("Thread cancelled\n");

  /* Sleep to let the thread resume so it can do fread() */
  sleep (2);

  printf ("Main thread sleep done\n");
  printf ("ThreadRewindStart = %i\n", ThreadRewindStart);
  printf ("ThreadRewindComplete = %i\n", ThreadRewindComplete);
  printf ("ThreadReadStart = %i\n", ThreadReadStart);
  printf ("ThreadReadError = %i\n", ThreadReadError);
  printf ("ThreadReadComplete = %i\n", ThreadReadComplete);

  printf ("Starting rewind in main\n");

  /* funlockfile (testfile); Doesn't help */

  rewind (testfile);

  /*
   * We should get here.  If we don't, its because the thread has
   * cancelled holding a lock in libc, so we're deadlocked.  It's
   * ok for us to cancel a thread that holds a lock we created
   * (ok in the sense we're allowed to deadlock ourselves if we're
   * that stupid), but libc shouldn't silently do it to us.
   *
   * We can save ourselves by unlocking testfile, but we shouldn't have
   * to clean up after libc.  However, even this doesn't work, since
   * funlockfile requires that we be the owner of the lock before we
   * can unlock it.
   */

  printf ("Starting read in main\n");
  if (fread (&in, sizeof (in), 1, testfile) < 1)
    printf ("fread error in main\n");
  else
    printf ("fread completed in main\n");

  fclose (testfile);

  return 0;
}

Reply via email to