/* JSGF: no idea what this is actually doing, but it really gives the
   signals/sigaltstack/threads machinery a working out */
/**
 * Compile with: 
 * gcc -g -Wall -lpthread -o susphello susphello.c
 *
 * Author Magnus Ihse, ihse at bea.com 
 */

#include <signal.h>


#include <errno.h>
#include <stddef.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <dlfcn.h>


#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>

#define THREAD_COUNT 10
#define ITER_COUNT 200

static volatile int finishedArray[THREAD_COUNT];
static int pKey;

static sigset_t srSigset;

pthread_t main_thread;

int srSignal = SIGUSR1;


void
ptiSrSigHandler(int sig, siginfo_t *sip, void *arg)
{
   //ucontext_t *ucontext = (ucontext_t *)arg;
   
   long mypos = (long) pthread_getspecific(pKey);
   
   
//   int threadPos = (int)pthread_getspecific(srThreadKey);

//   thread->os_context = (OSContextP)&(ucontext->uc_mcontext);

   // Notify suspender that we have been suspended
   if (pthread_kill(main_thread, srSignal) == -1) {
      perror("pthread_kill");
      exit(1);
   }
   
   finishedArray[mypos]++;

//   printf("this is thread %d: i'm now suspended!\n", mypos);

   // Wait until we are resumed
   while (sigwaitinfo(&srSigset, NULL) == -1) {
      // Interrupted by SIGSTOP in gdb
      if(errno != EINTR) {
      	perror("sigwaitinfo");
        exit(1);
      }
   }

//   printf("this is thread %d: i'm now resumed!\n", mypos);

   //thread->os_context = NULL; // just for the sake of it...

   // Notify resumer that we have been resumed
   if (pthread_kill(main_thread, srSignal) == -1) {
      perror("pthread_kill");
      exit(1);
   }
//   printf("this is thread %d: and I've told Master!!\n", mypos);

}


void
suspendOrResume(pthread_t thread, int i)
{
   sigset_t oss;

   // Mask out suspend/resume signal until we explicitly wait for it
   sigprocmask(SIG_BLOCK, &srSigset, &oss);

   // Send signal to suspend or resume the thread
   if (pthread_kill(thread, srSignal) == -1) {
      perror("pthread_kill");
      exit(1);
   }

//   printf("sent signal to %d...", i);
   // Wait for notification from thread being suspended/resumed
   while (sigwaitinfo(&srSigset, NULL) == -1) {
      // Interrupted by SIGSTOP in gdb
      if(errno != EINTR) {
      	perror("sigwaitinfo");
        exit(1);
      }
   }

   // Restore original signal mask
   sigprocmask(SIG_SETMASK, &oss, NULL);

//   printf("... okay, %d suspended\n", i);
}



void
initSignalling(void)
{
   struct sigaction sa;

   // Signal mask for suspend/resume
   sigemptyset(&srSigset);
   sigaddset(&srSigset, srSignal);

   // Set up signal handler for suspend/resume
   sa.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
   sa.sa_sigaction = ptiSrSigHandler;
   sigfillset(&sa.sa_mask);

#  if !defined(_AIX)
   /* jrs 20060615: is this important?  I don't know. */
   sigdelset(&sa.sa_mask, (__SIGRTMIN+1));
#  endif

   if (sigaction(srSignal, &sa, 0) == -1) {
      perror("sigaction");
      exit(1);
   }

   // Unblock suspend signal
   sigprocmask(SIG_UNBLOCK, &srSigset, 0);

   main_thread = pthread_self();
}


void* setup_altstack(void) {
   stack_t ss;

       ss.ss_sp = malloc(20*1024);
       if (ss.ss_sp == 0) {
	   return NULL;
       }
       ss.ss_size = 20*1024;
       ss.ss_flags = 0;
       
       if (sigaltstack(&ss, NULL) == -1) {
	   perror("sigaltstack");
           return NULL;
       }
       return ss.ss_sp;
}

void takedown_altstack(void* stack) {
#  if defined(_AIX)
   stack_t ss;
#  else
   struct sigaltstack ss;
#  endif
   int result;
   
   ss.ss_flags = SS_DISABLE;
   ss.ss_sp = (void*)47;  // This value should be ignored when ss_flags is SS_DISABLE
   ss.ss_size = 29;       // This value should be ignored when ss_flags is SS_DISABLE
   
   {
       result = sigaltstack(&ss, NULL);
       free(stack);
   }
}

void *threadfunc(void *arg) {
   long mypos = (long)arg;
   long i;
   long square = 1;
   void* altstack = setup_altstack();
   
   pthread_setspecific(pKey, arg);
   for (i=0; i < 1000; i++) {
      square = i*i + square*mypos;
   }

// wait for signal   
      while (finishedArray[mypos] == 0) {
   struct timespec req, rem;

   req.tv_sec = 0;
   req.tv_nsec = 5 * 1000 * 1000;

   nanosleep(&req, &rem);
         
   };
   
   finishedArray[mypos]++;
   
   takedown_altstack(altstack);
   
   return NULL;
}


int main(int argc, char ** argv) {
  pthread_t threads[THREAD_COUNT];
  pthread_attr_t attr;
  long result;
  long i;
  long iteration;
  long finished;

  initSignalling();
  
  pthread_attr_init(&attr);
  pthread_attr_setstacksize(&attr, 128*1024);

  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  
  pthread_key_create(&pKey, NULL);

  for (iteration = 0; iteration < ITER_COUNT; iteration++) {
#if 0
     if ((iteration % 100) == 0) {
	printf("\nStarting run series %i: ", iteration);
     }

     if ((iteration % 10) == 0) {
	printf(".");
        fflush(stdout);
     }
#endif

      // Clear array
      for (i = 0; i< THREAD_COUNT; i++) {
	 finishedArray[i] = 0;
      }

      // Start threads
      for (i = 0; i< THREAD_COUNT; i++) {
	 result = pthread_create(&threads[i], &attr, threadfunc, (void*)i);
	 if (result != 0) {
	    perror("pthread_create");
	    exit(1);
	 }
    }
    
//    printf("all threads started\n");
    // suspend threads
    for (i = 0; i< THREAD_COUNT; i++) {
    	suspendOrResume(threads[i], i);
    }
    
//    printf("now all threads are suspended\n");
    
    // resume threads
    for (i = 0; i< THREAD_COUNT; i++) {
    	suspendOrResume(threads[i], i);
    }
    

      // Join threads
/*      
      printf("about to join...");
      for (i = 0; i< THREAD_COUNT; i++) {
	 result = pthread_join(threads[i], NULL);
	 if (result != 0) {
	    perror("pthread_join");
	    exit(1);
	 }
      }
      
      printf("...joined");
*/      
//      printf("Spin waiting for results\n");
      finished = 1;
      do {
   struct timespec req, rem;

   req.tv_sec = 0;
   req.tv_nsec = 5 * 1000 * 1000;
   finished = 1;

   nanosleep(&req, &rem);
         
//         sleep(1);
         for (i = 0; i< THREAD_COUNT; i++) {
            if (finishedArray[i] < 2) {
               finished = 0;
//               printf("no result at: %d, value: %d\n", i, finishedArray[i]);
               break;
            }
	 }
//         sleep(1);
      } while (!finished);

  }

  printf("PASSED\n");
  return 0;
}
