Are stdio functions thread safe?

Majority of stdio functions are deemed to be thread safe ones. I cannot say
“all”
since I didn’t check them one by one. I only had a look at *printf(),
*scanf(),
and some of put() and get().

All these functions use flockfile() and funlockfile() calls in order to gain
an
exclusive access to the resource. These calls are potentially dangerous for
multithreaded processes. Example below demonstrates why.

The flockfile() and funlockfile() lock _stdio_mutex to reach a condvar. If a
thread is cancelled during the time it locks this mutex, the mutex is left
locked
forever blocking all other threads calling flockfile(). Innocent printf()
call may
block your entire process.

A simple solution is to wrap all stdio functions to pthread_setcancelstate()
(see safe_thread() function below). Note that if an “stdio” function is the
only
cancellation point in a thread, after wrapping it with
pthread_setcancelstate()
you have to add other cancellation point outside the wrapping.

I wonder why pthread_setcancelstate() calls are not used inside the
flockfile()
and funlockfile()? They are not cancellation points anyway.

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

/*

  • higher number allows to get desirable result quicker
    */
    #define NTHREADS 10

void *safe_thread( void *args )
{
int i, state, tid = pthread_self();

for( i = 0; ; i++ ) {
pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &state );
printf( “%d: %08X\r”, tid, i );
pthread_setcancelstate( state, &state );
delay( 1 ); // cancellation point
}

return NULL;
}

void *unsafe_thread( void *args )
{
int i, tid = pthread_self();

for( i = 0; ; i++ ) {
printf( “%d: %08X\r”, tid, i ); // cancellation point
}

return NULL;
}

int main( int argc, char **argv )
{
int i;
pthread_t threadID[ NTHREADS ];

/*

  • If one of the threads is canceled while in the flockfile()
  • or funlockfile() any other thread calling one these functions
  • will be MUTEX blocked until the process is terminated with a signal.
    */
    while( 1 ) {
    for( i = 0; i < NTHREADS; i++ )
    pthread_create( &threadID_, NULL, &unsafe_thread, NULL );
    // pthread_create( &threadID, NULL, &safe_thread, NULL );

    delay( 1000 );

    for( i = 0; i < NTHREADS; i++ ) {
    pthread_cancel( threadID );
    pthread_join( threadID, NULL );
    }
    }

    return 0;
    }_

Serge Yuschenko wrote:

I wonder why pthread_setcancelstate() calls are not used inside the
flockfile()
and funlockfile()? They are not cancellation points anyway.

This question made me curious, what does pthread_cond_wait do with
the mutex under it’s control when cancellation happens ? Does it
reacquire the mutex, or is it undefined ? If it does not reacquire
the mutex, then there is no problem with flockfile as it exists right
now.

If the result is undefined, a cleanup handler could be put in place,
to unlock the mutex, and this is probably preferable to disabling
cancellation for what would typically be a relatively long operation
(pthread_cond_wait()).

Rennie Allen wrote:

Serge Yuschenko wrote:

I wonder why pthread_setcancelstate() calls are not used inside the
flockfile() and funlockfile()? They are not cancellation points anyway.

This question made me curious, what does pthread_cond_wait do with
the mutex under it’s control when cancellation happens ? Does it
reacquire the mutex, or is it undefined ? If it does not reacquire
the mutex, then there is no problem with flockfile as it exists right
now.

I think the answer is here:
http://www.qnx.com/developers/docs/momentics621_docs/neutrino/lib_ref/p/pthread_cond_wait.html

If the result is undefined, a cleanup handler could be put in place,
to unlock the mutex, and this is probably preferable to disabling
cancellation for what would typically be a relatively long operation
(pthread_cond_wait()).

I got your point. A cleanup handler is certainly a legitimate way to avoid
the deadlock. The only problem that according to the documentation
flockfile() and funlockfile() are not cancellation points, which obviously
is not true.

Rennie Allen wrote:

This question made me curious, what does pthread_cond_wait do with
the mutex under it’s control when cancellation happens ? Does it
reacquire the mutex, or is it undefined ? If it does not reacquire
the mutex, then there is no problem with flockfile as it exists right
now.

“A side effect of acting upon a cancellation request while in a
condition variable wait is that the mutex is re-acquired before calling
the first cancellation cleanup handler. In addition, the thread is no
longer considered to be waiting for the condition and the thread shall
not have consumed any pending condition signals on the condition.”

(http://www.opengroup.org/onlinepubs/007904975/functions/xsh_chap02_09.html#tag_02_09_05_03)

Serge Yuschenko <serge.yuschenko@rogers.com> wrote:

All these functions use flockfile() and funlockfile() calls in order
to gain an exclusive access to the resource. These calls are potentially
dangerous for multithreaded processes.

I believe for 6.3 that cancellation handlers are now pushed around
stdio flockfile to release the mutex in these situations …

“Wojtek Lerch” <Wojtek_L@yahoo.ca> wrote in message
news:c3vb75$15b$1@inn.qnx.com

Rennie Allen wrote:
This question made me curious, what does pthread_cond_wait do with
the mutex under it’s control when cancellation happens ? Does it
reacquire the mutex, or is it undefined ? If it does not reacquire
the mutex, then there is no problem with flockfile as it exists right
now.

“A side effect of acting upon a cancellation request while in a
condition variable wait is that the mutex is re-acquired before calling
the first cancellation cleanup handler. In addition, the thread is no
longer considered to be waiting for the condition and the thread shall
not have consumed any pending condition signals on the condition.”


(> http://www.opengroup.org/onlinepubs/007904975/functions/xsh_chap02_09.html> #

tag_02_09_05_03)

In other words, this mandated POSIX behavior for either
pthread_cond_wait or pthread_cond_timedwait.

Moral of the story: Now matter how cumbersome it seems
to you, always write cancellation cleanup handlers. If that’s
too annoying, write a Monitor class.

Corollary: Did you know that you can deadlock on a signal
handler if a thread in pthread_cond_wait is selected for signal
handliing, such that it needs to re-acquire a mutex it can’t get?
Yes, it’s unusual, but a good reason why sigmasks and
sigwait() should be judiciously applied to well-formed
multi-threaded programs.

The latter is especially true when you need to acquire locks in
general – never do them in signal handlers (N.B. mutexes aren’t signal safe
anyway – condition notifies or broadcasts are, but
the notification could be lost if the mutex isn’t held!).

John Garvey wrote:

Serge Yuschenko <> serge.yuschenko@rogers.com> > wrote:
All these functions use flockfile() and funlockfile() calls in order
to gain an exclusive access to the resource. These calls are potentially
dangerous for multithreaded processes.

I believe for 6.3 that cancellation handlers are now pushed around
stdio flockfile to release the mutex in these situations …

I agree that adding a cancellation handler to flockfile() is the better
solution (vs. using pthread_setcancelstate()) because setting
PTHREAD_CANCEL_DISABLE would move flockfile() out of list of cancellation
points (where contrary the documentation it is now). And it looks like it
is the only cancellation point for all stdio functions.

Serge Yuschenko wrote:

Rennie Allen wrote:


Serge Yuschenko wrote:




I think the answer is here:
http://www.qnx.com/developers/docs/momentics621_docs/neutrino/lib_ref/p/pthread_cond_wait.html

I looked at this briefly, but the effect of cancellation didn’t jump out
(since it is implied by the “in all cases” phrase).

Good to hear that this is fixed in 6.3…

Rennie

Serge Yuschenko wrote:

I agree that adding a cancellation handler to flockfile() is the better
solution (vs. using pthread_setcancelstate()) because setting
PTHREAD_CANCEL_DISABLE would move flockfile() out of list of cancellation
points (where contrary the documentation it is now). And it looks like it
is the only cancellation point for all stdio functions.

Actually, flockfile() is not on the list of functions that POSIX
requires or allows to be a cancellation point:

http://www.opengroup.org/onlinepubs/007904975/functions/xsh_chap02_09.html#tag_02_09_05_02

Wojtek Lerch wrote:

Actually, flockfile() is not on the list of functions that POSIX
requires or allows to be a cancellation point:

http://www.opengroup.org/onlinepubs/007904975/functions/xsh_chap02_09.html#tag_02_09_05_02

Bummer. Sounds like QSSL needs to change the implementation of flockfile,
and use some other method of exclusion for higher level stdio functions
then (in order to be compliant).

It seems to me that having a function with “lock” in it’s name, not be
listed as a legal cancellation point, is an oversight.