Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAOZ3c1rUN24Arx5iy+Zm-4gYajfBthQsgL9q-iiNQpAo1aZL8w@mail.gmail.com>
Date: Mon, 21 Feb 2022 11:36:06 +0000
From: Lee Shallis <gb2985@...il.com>
To: musl@...ts.openwall.com
Subject: Suggestion for thread safety

First I'll start with the code snippets I've copied from my own code:

/* The error locks only work for conforming code, anything that doesn't will
 * corrupt the result if it tries to do something that would need them */
typedef const char* LOCK;
typedef struct _GRIP { LOCK _; LOCK *_lock; struct _GRIP *_grip; } GRIP;
BASIC void    LockStdErr( LOCK *ud );
BASIC void    LockSysErr( LOCK *ud );
BASIC void    LockSiData( LOCK **shared, LOCK *ud );
BASIC void    LockMiData( GRIP *shared, GRIP *ud );
BASIC dint    FreeStdErr( LOCK *ud );
BASIC dint    FreeSysErr( LOCK *ud );
BASIC dint    FreeSiData( LOCK **shared, LOCK *ud );
BASIC void    FreeMiData( GRIP *shared, GRIP *ud );

#define LockErrors( ud ) LockStdErr( ud ); LockSysErr( ud )
#define FreeErrors( ud ) FreeSysErr( ud ); FreeStdErr( ud )

#define DO_LOCK( X ) { LOCK _lock = ""; X }
#define DO_GRIP( X ) { GRIP _grip = {NULL}; X }

#define LOCK_SIDATA( SID, X ) \
    DO_LOCK( LockSiData( SID, &_lock ); X *SID = NULL; )

#define LOCK_MIDATA( MID, X ) \
    DO_GRIP( LockMiData( MID, &_grip ); X FreeMiData( MID, &_grip ); )

#define LOCK_STDERR( X ) \
    DO_LOCK( LockStdErr( &_lock ); X FreeStdErr( &_lock ); )

#define LOCK_SYSERR( X ) \
    DO_LOCK( LockSysErr( &_lock ); X FreeSysErr( &_lock ); )

#define LOCK_ERRORS( X ) \
    DO_LOCK( LockErrors( &_lock ); X FreeErrors( &_lock ); )

#define LOCK_SIDATA_AND_ERRORS( SID, X ) \
    DO_LOCK \
    ( \
        LockSiData( SID, &_lock ); \
        LockErrors( &_lock ); \
        X \
        FreeErrors( &_lock ); \
        *SID = NULL; \
    )
...
SHARED_EXP void NoPause() {}

LOCK *stderr_lock = NULL;
LOCK *syserr_lock = NULL;
allot_cb AllotCB = Allot;
pause_cb pauseCB = NoPause;

void LockStdErr( LOCK *ud ) { LockSiData( &stderr_lock, ud ); }
dint FreeStdErr( LOCK *ud ) { return FreeSiData( &stderr_lock, ud ); }

void LockSysErr( LOCK *ud ) { LockSiData( &syserr_lock, ud ); }
dint FreeSysErr( LOCK *ud ) { return FreeSiData( &syserr_lock, ud ); }
...
SHARED_EXP dint    Allot( void *ud, void **data, size_t size )
{
    (void)ud;
    if ( size )
    {
        dint err;
        LOCK_ERRORS
        (
            errno = 0;
            *data = *data ? realloc( *data, size ) : malloc( size );
            err = *data ? 0 : errno;
        );
        return err;
    }
    else if ( *data )
    {
        free( *data );
        *data = NULL;
    }
    return 0;
}
SHARED_EXP void LockSiData( LOCK **shared, LOCK *ptr )
{
    while ( *shared != ptr )
    {
        if ( !(*shared) )
            *shared = ptr;
        pauseCB( ptr );
    }
}

SHARED_EXP dint FreeSiData( LOCK **shared, LOCK *ud )
{
    if ( *shared == ud )
    {
        *shared = NULL;
        return 0;
    }
    return EPERM;
}
/* These 2 are untested */
SHARED_EXP void LockMiData( GRIP *shared, GRIP *ud )
{
    while ( shared->_grip != ud )
    {
        while ( shared->_grip )
            shared = shared->_grip;
        shared->_grip = ud;
        pauseCB();
    }
}
SHARED_EXP void    FreeMiData( GRIP *shared, GRIP *ud )
{
    LOCK _ = NULL;
    LockSiData( &(shared->_lock), &_ );
    LockSiData( &(ud->_lock), &_ );
    while ( shared->_grip && shared->_grip != ud )
        shared = shared->_grip;
    if ( shared->_grip == ud )
    {
        shared->_grip = ud->_grip;
        ud->_grip = NULL;
    }
    (void)FreeSiData( &(ud->_lock), &_ );
    (void)FreeSiData( &(shared->_lock), &_ );
}

Take what you will of the above, the Allot function was included just
to give you an idea of how the locks are made, as for pauseCB that
should be re-mapped to a variant that calls something like
pthread_yield(), it's what I tested with, anyways the general idea is
that the shared lock defaults to NULL when not locked, when you want
to lock it you 1st wait for it to be NULL then as soon as it's empty
you fill it with your own pointer then wait for other threads who
detected the same situation to fill there pointers into the shared
pointer (assuming any other threads were trying to lock the pointer),
when the wait is over you check again if the pointer is not matching
your own, if not then you failed to lock and should wait longer, using
pointers lends itself well to threading as the pointer being used to
take the shared pointer will normally be unique to the thread (because
rarely will anyone try to use the same pointer in multiple threads for
this process that has a clear example in it's declaration), using
const char * for the LOCK typedef means a thread can give more
information about itself or the pointer it locked with if it so
chooses.

GRIP on the other hand is meant for multi-threaded data, for example
assigning parts of a buffer, the owning thread should fill in the '_'
member after acquiring the grip, if the previous grip doesn't have
info about the section of the buffer it took then it should wait until
it does, when it does it is then free to take it's own chunk after
analysing what has already been taken by other grips linked to the
shared grip, this method would lend itself well to graphics generation
for example as a thread for each column could be launched in ascending
order (so that the row contents are process from left to right), at
least that's the theory on the grip one, requires testing which I'm
sure you can do better than I can at the moment.

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.