Why adding a printf caused my program to hang

Or “how to cancel a pthread safely; and reverse time”

I was doing some work with external Python functions, and attaching a subtask to intercept operator requests. It was very frustrating when I added a printf to the C program to provide diagnostic information – and the program did not produce any output even from a previous printf(spooky). Remove the printf and it worked including the earlier print(“Starting”) before my new printf.

After a couple of days, and some long walks I found out the reason why. It was all down to my lack of knowledge about what is available with pthreads, and locking.

Python has a lock to serialise work. While a thread has this lock, no other thread can do any Python work.

An attached thread can be configured as to how it responds to a cancel request. For example you may not want to cancel the thread in the middle of a critical update, for example while holding a lock.

By default it looks like threads are non-cancellable, unless you allow for it.

When I ran my job, there was an abend A03 A task tried to end normally by issuing a RETURN macro or by branching to the return address in register 14. The task was not ready to end processing because …: The task had attached one or more subtasks that had not ended.

The task needs to be told to shutdown – or to respond to a cancel thread.

Creating a thread

struct thread_args {
   PyObject *method;
   ...
   } 
#define _OPEN_THREADS 2 
#include <pthread.h>
//create a structure to pass parameters to the thread.
struct thread_args *zargs = malloc (sizeof (struct thread_args));
zargs -> method = method;
...
pthread_t thid; 
int rc; 
// invoke pThread to create thread and pass the parms through 
rc = pthread_create(&thid, NULL, cthread, zargs); 
if (rc != 0) { 
  printf("pthread rc %d \n", rc); 
  perror("pthread_create() error"); 
} 

To cancel a thread

The short answer to how to cancel a thread is

rc = pthread_cancel(thid);
if ( rc != 0) 
{
   perror("Trying to cancel the thread");
}

Return code 0 means the request to cancel the thread was successfully issued, but it does necessarily mean the thread has been cancelled, because the thread could be set as non- cancellable.

Within the thread program.

You can configure the program running as a thread to be cancellable:

  • Not cancellable – the default
  • Cancellable
    • At this point
    • At any time.
    • Not between these instructions

To make a thread non cancellable

int previous = pthread_setintr(PTHREAD_INTR_DISABLE);

You can use the returned variable to reset the status with pthread_setintr(previous).

To make a thread cancellable at this point

Set up the thread. Do pthread_setintrtype before pthread_setintr to eliminate a timing window.

// Specify how it is interruptible, any time, or controlled
if (pthread_setintrtype(PTHREAD_INTR_CONTROLLED ) == -1 )
{ perror(“error setting pthread_setintrtype”);… }

// Say it is interruptible
int previous = pthread_setintr(PTHREAD_INTR_ENABLE);

The initial values are

  • pthread_setintrtype is PTHREAD_INTR_CONTROLLED (0)
  • pthread_setintr is PTHREAD_INTR_ENABLE(0)

So you may not need to use the pthread_setintr* functions.

The thread needs an “interruptible” function.

The documentation says

PTHREAD_INTR_CONTROLLED:
The thread can be cancelled, but only at specific points of execution. These are:

  • When waiting on a condition variable, which is pthread_cond_wait() or pthread_cond_timedwait()
  • When waiting for the end of another thread, which is pthread_join()
  • While waiting for an asynchronous signal, which is sigwait()
  • When setting the calling thread’s cancel-ability state, which is pthread_setintr()
  • Testing specifically for a cancel request, which is pthread_testintr()
  • When suspended because of POSIX functions or one of the following C standard functions: close(), fcntl(), open(), pause(), read(), tcdrain(), tcsetattr(), sigsuspend(), sigwait(), sleep(), wait(), or write().

In my thread I had used the interruptible function pthread_testintr().

printf(“before testcancel\n”);
pthread_testintr() ;
printf(“after testcancel\n”);

When my code was running I had

before testcancel
after testcancel

before testcancel
after testcancel

pthread_cancel() was issued and the output was

before testcancel

So we can see the code was behaving as expected,and was cancelled inside/at the pthread_testintr() function.

To make a thread cancellable at any time

if (pthread_setintrtype(PTHREAD_INTR_ASYNCHRONOUS ) == -1 )
{ perror(“error setting pthread_setintrtype”);… }
int previous = pthread_setintr(PTHREAD_INTR_ENABLE);

If you are using this you need to design the code so the thread has no locks or mutexes. These will not be released automatically.

To make a thread not cancellable between these instructions

pthread_setintrtype(PTHREAD_INTR_ASYNCHRONOUS)
pthread_setintr(PTHREAD_INTR_DISABLE)
// thread non cancellable

get a lock
do some work
free a lock

pthread_setintr(PTHREAD_INTR_ENABLE);
// thread now cancellable any point after this

The pthread_setintr(PTHREAD_INTR_ DISABLE|ENABLE) code protects the non cancellable code.

The pthread_setintrtype(PTHREAD_INTR_ASYNCHRONOUS) says that outside of the non-cancellable code it can be cancelled at any point when interrupts are enabled.
Instead you could use pthread_setintrtype(PTHREAD_INTR_CONTROLLED ) and pthread_testintr(), to make your code interruptible at a specific point.

It is not spooky.

When running my code. I initially had it running so it was interruptible anywhere.

What was happening was

  • get python lock
  • get interrupted. Thread ends

By adding a printf to my code, it changed where the thread was interrupted. With the printf – it was interrupted while the Python lock was held, the thread was cancelled with the lock still held, and no other Python work ran.

Without the additional printf, the thread abended without the Python lock from being held.

By putting the pthread_ calls around the code with the lock I could make sure the lock was released before the thread ended.

Spooky lack of printing

The Python program had used print(“starting”), but this was written to the print buffers, it was not forced out to disk.

When I used Python print(“starting”,force=True) the data was forced out before progressing.

The C function is fflush(stdout);

Overall – not spooky at all, just a lack of understanding.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s