Hi, List

Probably this is another case of database corrupted. I read the
documents about this topic and think I did not make same mistakes
described in that 'how to corrupt ...' documentation.

I have a testing code, attached in this email, if continuously run it for
20 - 40 hours, the sqlite database will be corrupted. But the code
itself is very simple, it just keeping inserting a lot of rows. After
inserted some long time, it begin to delete some rows and continues to
insert. 

There is no multi-threads, no concurrent database operations. Every
instance of the application instance owns exclusively its database file.

The application is running on an ARM Linux system with Yaffs2 filesystem
on NAND flashes.

Below are sample database error:

------------------------------------------------------
sqlite> pragma integrity_check;
*** in database main ***
Multiple uses for byte 752 of page 20506
On tree page 21363 cell 27: invalid page number 16843521
Multiple uses for byte 676 of page 21363
Fragmentation of 8 bytes reported as 0 on page 21363
Multiple uses for byte 392 of page 28676
rowid 518219 missing from index testidx
Error: database disk image is malformed
-----------------------------------------------------

Please have a look at my attached code and be kindly give me a clue on
what are possible causes?

Thanks in advance.

-- 
woody
I can't go back to yesterday - because I was a different person then.
#define _GNU_SOURCE
#include <string.h>
#include <stdbool.h>
#include <stddef.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <unistd.h>
#include <syslog.h>
#include <assert.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sqlite3.h>

#define MAX_SQL_LEN 150
#define SQL_CREATE_TESTTBL \
    "create table if not exists testtbl(nComID number(2), nMeterID number(2), 
nMPID number(2), nData number, nStatus number, nTimeReq number);"
#define SQL_CREATE_TESTIDX \
    "create index if not exists testidx on testtbl(nTimeReq asc,nComID 
asc,nMeterID asc,nMPID asc);"
#define SQL_BEGIN_TRANS "BEGIN;"
#define SQL_COMMIT_TRANS "COMMIT;"
#define SQL_ROLLBACK_TRANS "ROLLBACK;"

static char *db_fname = "test.db";
static sqlite3 *conn;
static int sql_code;
static char *sql_msg;
static int fill_time;
static int max_rec_age = 2048;
static bool auto_vacuum;
static int cache_size = 0;
static bool use_syslog;
static bool verbose;
static unsigned max_fill_count;

static void mlog(int opt, const char *fmt, ...)
{
    if (! verbose && LOG_PRI(opt) >= LOG_DEBUG)
        return;

    char s[256];
    snprintf(s, sizeof(s), "[%s] %s", basename(db_fname), fmt);
    fmt = s;
    va_list args;
    va_start(args, fmt);
    if (use_syslog)
        vsyslog(opt, fmt, args);
    else
        vprintf(fmt, args);
    va_end(args);
}

static bool sql_exec(const char *sql)
{
    mlog(LOG_DEBUG, "sql_exec: %s\n", sql);
    while (true) {
        sql_code = sqlite3_exec(conn,
                sql, NULL, NULL, &sql_msg);
        if (sql_code == SQLITE_OK)
            return true;
        else {
            mlog(LOG_NOTICE, "%s: %s\n", sql_msg, sql);
            if (sql_msg)
                sqlite3_free(sql_msg);
            if (sql_code == SQLITE_BUSY || SQLITE_LOCKED)
                sleep(1);
            else
                return false;
        }
    }
}

static bool sql_select(const char *sql, char ***result)
{
    mlog(LOG_DEBUG, "sql_select: %s\n", sql);
    while (true) {
            sql_code = sqlite3_get_table(conn, sql,
                result, NULL, NULL, &sql_msg);
        if (sql_code == SQLITE_OK)
            return true;
        else {
            mlog(LOG_NOTICE, "%s: %s\n", sql_msg, sql);
            if (sql_msg)
                sqlite3_free(sql_msg);
            if (*result)
                sqlite3_free_table(*result);
            *result = NULL;
            if (sql_code == SQLITE_BUSY || SQLITE_LOCKED)
                sleep(1);
            else
                return false;
        }
    }
}

static bool get_min_time(int *time)
{
    char **result_tbl;

    if (! sql_select("SELECT MIN(nTimeReq) FROM testtbl;", &result_tbl))
        return false;

    if (! result_tbl[1])
        *time = 0;
    else
        *time = atoi(result_tbl[1]);

    sqlite3_free_table(result_tbl);
    return true;
}

static bool get_max_time(int *time)
{
    char **result_tbl;

    if (! sql_select("SELECT MAX(nTimeReq) FROM testtbl;", &result_tbl))
        return false;

    if (! result_tbl[1])
        *time = 0;
    else
        *time = atoi(result_tbl[1]);

    sqlite3_free_table(result_tbl);
    return true;
}

static bool init_fill_time(void)
{
    return get_max_time(&fill_time);
}

static bool prepare(void)
{
    char sql_set_cache[MAX_SQL_LEN];

    mlog(LOG_INFO, "prepare database ...\n");
    if (auto_vacuum) {
        mlog(LOG_INFO, "auto_vacuum = 1\n");
        if (! sql_exec("PRAGMA auto_vacuum = 1;"))
            return false;
    }
    if (cache_size > 0) {
        mlog(LOG_INFO, "using cache_size = %d\n", cache_size);
        snprintf(sql_set_cache, sizeof(sql_set_cache), "PRAGMA cache_size = 
%d;", cache_size);
        if (! sql_exec(sql_set_cache))
            return false;
    }

    if (! sql_exec(SQL_CREATE_TESTTBL) || ! sql_exec(SQL_CREATE_TESTIDX))
        return false;

    return init_fill_time();
}

static bool rotate(void)
{
    if (max_rec_age <= 0)
        return true;

    int time0, time1, time2;

    if (! get_min_time(&time0) || !get_max_time(&time1))
        return false;

    if (time1 < time0) {
        mlog(LOG_ERR, "invalide time range [%d, %d]\n", time0, time1);
        return false;
    } else
        mlog(LOG_DEBUG, "current time range [%d, %d]\n", time0, time1);

    if (time1 - time0 <= max_rec_age * 60)
        return true;
    
    mlog(LOG_INFO, "delete nTimeReq < %d + 4\n", time0);

    char sql_del[MAX_SQL_LEN];
    sprintf(sql_del, "DELETE FROM testtbl WHERE nTimeReq < %d", time0 + 4);

    if (! sql_exec(SQL_BEGIN_TRANS))
        return false;
    if (! sql_exec(sql_del)) {
        sql_exec(SQL_ROLLBACK_TRANS);
        return false;
    }
    
    if (! sql_exec(SQL_COMMIT_TRANS))
        return false;

    if (! get_min_time(&time2))
        return false;
    if (time2 < time0 + 4) {
        mlog(LOG_ERR, "delete failed! current min: %d\n", time2);
        return false;
    } else
        return true;
}

static bool check_db(void)
{
    static time_t last_chk_time = 0;

    time_t elapsed = (unsigned) ((signed)time(0) - (signed)last_chk_time);
    if (elapsed < 30 * 60)
        return true;

        int nrow, ncol;
    char **result;
    if (sqlite3_get_table(conn, "PRAGMA integrity_check;",
            &result, &nrow, &ncol, &sql_msg) != SQLITE_OK) {
        mlog(LOG_ERR, "integrity check failed\n");
        sqlite3_free_table(result);
        if (sql_msg)
            sqlite3_free(sql_msg);
        return false;
    }
    if (nrow > 0 && strcmp(result[1], "ok") && strcmp(result[1], "OK")
            && strcmp(result[1], "Ok")) {
        sqlite3_free_table(result);
        mlog(LOG_ERR, "database corrupted!\n");
        return false;
    } else
        mlog(LOG_INFO, "database integrity ok\n");

    sqlite3_free_table(result);
    last_chk_time = time(0);
    return true;
}

static bool fill(void)
{
    char sql[MAX_SQL_LEN];

    if (! rotate())
        return false;

    mlog(LOG_DEBUG, "insert with fill_time = %d\n", fill_time);
    if (! sql_exec(SQL_BEGIN_TRANS))
        return false;

    size_t n;
    for (n = 0; n < 5; ++n) {
        sprintf(sql, "INSERT INTO testtbl VALUES(3,2,%d,596516,0,%d);", n + 1, 
fill_time);
        if (! sql_exec(sql))
            break;
    }
    if (n < 5) {
        mlog(LOG_WARNING, "insert failed\n");
        if (! sql_exec(SQL_ROLLBACK_TRANS))
            return false;
    } else if (! sql_exec(SQL_COMMIT_TRANS))
        return false;

    while ((sql_code = sqlite3_close(conn)) != SQLITE_OK) {
        mlog(LOG_WARNING, "sqlite closing (%d)\n", sql_code);
        sleep(1);
    }
    if ((sql_code = sqlite3_open(db_fname, &conn)) != SQLITE_OK) {
        mlog(LOG_ERR, "sqlite open error (%d)\n", sql_code);
        return false;
    }

    return check_db();
}

static bool do_test(void)
{
    unsigned cnt = 0;

    while ((max_fill_count == 0 || cnt++ < max_fill_count)
            && fill_time++ < INT_MAX
            && fill())
        ;
    if ((max_fill_count == 0 || cnt++ < max_fill_count)
            && fill_time < INT_MAX) {
        mlog(LOG_WARNING, "test aborted abnormally\n");
        return false;
    } else
        return true;
}

static void test(void)
{
    if (prepare()) {
        mlog(LOG_INFO, "do the test, fill_time=%d\n"
            , fill_time);
        do_test();
        mlog(LOG_INFO, "test end, fill_time=%d ...\n"
            , fill_time);
    }
}

static bool parse_opt(int argc, char **argv)
{
    int c;
    opterr = 0;
    while ((c = getopt(argc, argv, "vusc:a:n:")) != -1)
        switch (c) {
            case 'v':
                verbose = true;
                break;
            case 'u':
                auto_vacuum = true;
                break;
            case 's':
                use_syslog = true;
                break;
            case 'n':
                max_fill_count = atoi(optarg);
                break;
            case 'a':
                max_rec_age = atoi(optarg);
                break;
            case 'c':
                cache_size = atoi(optarg);
                break;
            case '?':
                if (optopt == 'a' || optopt == 'c'|| optopt == 'n')
                    fprintf(stderr, "option -%c requries an argument.\n", 
optopt);
                else if (isprint(optopt))
                    fprintf(stderr, "unknown option -%c.\n", optopt);
                else
                    fprintf(stderr, "unknown option character 0x%x", optopt);
                return false;
            default:
                return false;
        }

    if (argc - optind > 1) {
        fprintf(stderr, "too many positional arguments\n");
        return false;
    } else if (optind < argc)
        db_fname = argv[optind];

    return true;
}

int main(int argc, char **argv)
{
    
    if (! parse_opt(argc, argv))
        return -1;

    mlog(LOG_INFO, "sqlitetest start. max_rec_age = %d, auto_vacuum=%d\n",
            max_rec_age, auto_vacuum);
    if ((sql_code = sqlite3_open(db_fname, &conn)) == SQLITE_OK) {
        test();
        sqlite3_close(conn);
    } else {
        mlog(LOG_ERR, "sqlite3_open = %d (%s)\n", sql_code, sql_msg);
        if (sql_msg)
            sqlite3_free(sql_msg);
    }
    mlog(LOG_INFO, "sqlitetest end\n");
    return 0;
}

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

Reply via email to