Hello,

On several machines running Suse10.2 (gcc 4.1.2) we get the following 
assertion
prog: sqlite3.c:15173: pthreadMutexEnter: Assertion `p->id==1 || 
pthreadMutexNotheld(p)' failed.
Aborted

The problem does not seem to occur on Suse11 (gcc 4.3.2), our Ubuntu, nor 
on our Windows platforms.
We are able to reproduce the problem with both sqlite 3.6.18 and 3.6.3 
(have not tried with other versions)

Using the sqlite-amalgamation-3_6_18.zip package I can reproduce the 
problem with a small example. I have included the example main.c below.
The example is compiled as follows:
gcc -DSQLITE_THREADSAFE=1 -DSQLITE_DEBUG=1 -DSQLITE_OMIT_LOAD_EXTENSION -g 
 -pthread  -O2 sqlite3.c main.c -o testprog

The assertion occurs when using -O2, -O3, or -Os, but not when using -O.

My best guess at what is going wrong (disclaimer: I am just guessing here, 
feel free to skip this part) is as follows:
Inside pthreadMutexEnter sqlite do:
  pthread_mutex_lock(&p->mutex);
  p->owner = pthread_self();
  p->nRef++;
I believe the two last statements get swap'ed around by the optimizer, 
such that nRef++ occurs first. This leads to a possible race with the 
assertion in the beginning of the pthreadMutexEnter (another thread 
entering while the first thread is updating nRef and owner). 
The reason I think this is because:
1) gdb is not sure of which line it is at when inspecting the two above 
lines
2) If I insert a printf just before the assertion I can see that nRef is 1 
and owner==self (thus another thread has updated nRef but not yet owner)
Note pthreadMutexEnter gets called like 10000 times before it fails for 
the first time.
Since newer compilers seems to be less likely to generate the race, it 
might be a known dangerous/faulty optimization pattern in gcc 4.1.2 that 
is causing the issue (however my friend google could not confirm that).

Regards,
Jan

----main.c-------
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>

#include "sqlite3.h"


// Compile: gcc -DSQLITE_THREADSAFE=1 -DSQLITE_DEBUG=1 
-DSQLITE_OMIT_LOAD_EXTENSION -g  -pthread  -O2 sqlite3.c main.c -o 
testprog
// Run: ./testprog;rm test.db;./testprog;rm test.db;./testprog
// the problem usually occurs within 1-3 attemps (but only on Suse10.2)

//#threads= 2*NUMBER_OF_QUERIES
#define NUMBER_OF_QUERIES 1
//size of test db
#define NUMBER_OF_ROWS 1024*1024
#define NUMBER_OF_ROWS_PER_OUTPUT 1024*10


static void *read_db(sqlite3_stmt *statement, int id)
{
    int i = 0;

    for (;;)
    {
        int res = sqlite3_step(statement);
        switch (res)
        {
            case SQLITE_DONE:
            case SQLITE_BUSY:
                //continue
                break;
            case SQLITE_ROW:
            {
                const unsigned char *text  = 
sqlite3_column_text(statement, 1);
 
                if (i % NUMBER_OF_ROWS_PER_OUTPUT == 0)
                {
                    printf("Thread(%d): \"%s\"\n", id, text);
                }
                i++;
                break;
            }
            case SQLITE_MISUSE:
            case SQLITE_ERROR:
            default:
                printf("Failed to read statement '%d'\n", statement);
                res = SQLITE_DONE;
                break;
        }
        if (res == SQLITE_DONE)
            break; //done
    }
    return NULL;
}

static int populate_db(sqlite3 *db)
{
    int res = SQLITE_OK;
    const char *insert_item = "INSERT INTO MyTable VALUES(%d,'Test data 
for SQLite:%d')";
    int size = strlen(insert_item);
    char *buf = (char *) malloc((size + 1 + 20)*sizeof(char));
 
    if ((res = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL)) != SQLITE_OK)
        printf("Failed to start transaction.\n");

    if (res == SQLITE_OK)
    {
        int i = 0;
        int insert_max = NUMBER_OF_ROWS; //lots of data to warmup the 
cache
        for (i=0;res==SQLITE_OK && i<insert_max;i++)
        {
            sprintf(buf, insert_item, i, i);
            if ((res = sqlite3_exec(db, buf, NULL, NULL, NULL)) != 
SQLITE_OK)
                printf("Failed to add item number '%d' to table.\n", i);
        }
    }
    if (res == SQLITE_OK && (res = sqlite3_exec(db, "COMMIT;", NULL, NULL, 
NULL)) != SQLITE_OK)
        printf("Failed to commit transaction.\n");
 
    free(buf);
    return res;
}

struct Data
{
    const char *filename;
    const char *query;
    const char *debugname;
    int id;
};

static void *open_connection(void *arg)
{
    struct Data *data = (struct Data *) arg;

    sqlite3 *db = NULL;
    sqlite3_stmt *resultSet = NULL;
    const char *query = data->query;
    const char *filename = data->filename;

    int res = sqlite3_open(filename, &db);

    if (res == SQLITE_OK)
    {
        if (res == SQLITE_OK && (res = sqlite3_prepare(db, query, -1, 
&resultSet, NULL)) != SQLITE_OK)
            printf("Failed to prepare %s statement.\n", data->debugname);
 
        read_db(resultSet, data->id);

        if (resultSet != NULL)
            sqlite3_finalize(resultSet);
        sqlite3_close(db);
    }
    else
    {
        printf("db open failed!\n");
    }
    return NULL;
}

int main(int argc, char **argv)
{
    const char *filename = "test.db";
    const char *query_asc = "SELECT id,name FROM MyTable ORDER by name 
ASC";
    const char *query_desc = "SELECT id,name FROM MyTable ORDER by name 
DESC";
    int res = SQLITE_OK;

    if (argc > 1)
    {
        printf("Reusing old db\n");
    }
    else
    {
        printf("creating test.db file\n");
        sqlite3 *db = NULL;
        res = sqlite3_open(filename, &db);
        if (res == SQLITE_OK)
        {
            const char *create_table = "CREATE TABLE MyTable (id INTEGER 
PRIMARY KEY AUTOINCREMENT, name VARCHAR2);";

            if (res == SQLITE_OK && (res = sqlite3_exec(db, create_table, 
NULL, NULL, NULL)) != SQLITE_OK)
                printf("Failed to create table. Please delete '%s' before 
running.\n", filename);
            if (res == SQLITE_OK)
                res = populate_db(db);
            sqlite3_close(db);
        }
        else
        {
            printf("db open in main failed!\n");
        }
    }

    if (res == SQLITE_OK)
    {
        pthread_t thread[2*NUMBER_OF_QUERIES];
        int isNotAlive[2*NUMBER_OF_QUERIES];
        struct Data data[2*NUMBER_OF_QUERIES];
        int i = 0;
        for (i=0; i <2*NUMBER_OF_QUERIES;i++)
        {
            isNotAlive[i] = 1; //isAlive==false
        }

        for (i=0; i <2*NUMBER_OF_QUERIES;i++)
        {
            data[i].id = i;
            data[i].filename = filename;
            if (i % 2 == 0)
            {
                data[i].query = query_asc;
                data[i].debugname = "asc";
            }
            else
            {
                data[i].query = query_desc;
                data[i].debugname = "desc";
            }
 
            pthread_t *tptr = &(thread[i]);
            isNotAlive[i] = pthread_create(tptr, NULL, open_connection, 
(void *) &(data[i]));
            if (isNotAlive[i] != 0)
                printf("Failed to create thread.\n");
        }

 
        for (i=0; i <2*NUMBER_OF_QUERIES;i++)
        {
            if (isNotAlive[i] == 0)
                pthread_join(thread[i], NULL);
        }
    }

    return 0;
}
_______________________________________________
sqlite-users mailing list
sqlite-users@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to