I'm a newbie on the block and this is my first  post, but have you considered 
'threading'? This sounds like something that you need to create a thread to do. 
Creating a thread should then allow you to set parameters to wait until one 
process completes and switch back and forth. I've done this in VB and C# but I 
think you stated below you using Linux and C, which I have no clue... but I 
found a video here:  https://www.youtube.com/watch?v=rvbGbmXJ0f0
Scott ValleryEcclesiastes 4:9-10 

    On Saturday, January 19, 2019, 7:11:57 AM EST, andrew.g...@l3t.com 
<andrew.g...@l3t.com> wrote:  
 
 I am having significant difficulty getting two process to not slam into each 
other when accessing the same database.  The behavior I want is for one process 
to wait while the database is busy being written to by the other, but instead I 
immediately get SQLITE_BUSY.  This is happening to me when using a local 
filesystem, so NFS is not the issue.  (Though I expect NFS to bite me soon 
enough.)

My trouble began on a Red Hat Enterprise Linux 6.4 system, though said computer 
exhibited other bizarre behaviors in the past (randomly unmounting filesystems 
and changing existing processes' current working directories to empty string), 
so I moved to Slackware64-current to make sure I could reproduce the problem in 
a modern environment.  Sure enough, I can.

sqlite3_busy_timeout(db, 5000) doesn't seem to help, not even when I add 
-DSQLITE_ENABLE_SETLK_TIMEOUT to my SQLite build command line.  sqlite3_step() 
doing an INSERT randomly fails to acquire the RESERVED lock.  I even tried 
writing my own retry-on-busy code (made several attempts), with no success.

I'm using SQLite 3.26.0 [bf8c1b2b7a] with GCC 8.2.0 on Linux 4.19.6, though I 
also had the same problems with GCC 4.4.7 on Linux 2.6.32-431.3.1.el6.x86_64.

Tracing through the SQLite amalgamation code with gdb, one thing I find odd is 
line 66334 doesn't seem to actually ever try calling 
btreeInvokeBusyHandler(pBt) despite rc being SQLITE_BUSY, since 
pbt->inTransaction isn't TRANS_NONE but rather TRANS_WRITE (I think).  This 
prevents it from waiting and/or retrying if busy, so the loop immediately 
bails, then SQLITE_BUSY rapidly percolates up to my application.

However, there's a chance my analysis may be invalid because I made my 
application pause with SIGSTOP on SQLITE_BUSY, at which point I attached gdb 
and forced it to call sqlite3_step() again so I could watch the execution path. 
 So maybe pbt->inTransaction was set sometime after unixFileLock() decided 
things went wrong.  But I highly doubt it.

Maybe a better debug strategy would have been to set a breakpoint in one 
process right when it acquires RESERVED, then start a second process in gdb and 
see what happens when it can't immediately get its own RESERVED lock.  (Or 
can't get SHARED because the first process had PENDING, etc.)  But it's 
midnight on Friday and I need to go home.

By the way, the SQLITE_LOCK_TRACE code seems to have a little bitrot.  I had to 
move the #define sqlite3DebugPrintf section earlier in the amalgamation, plus 
change the first %d on line 33121 to %ld because my pthread_t is a 64-bit value.

I created a test program that reproduces the issue, pasted at the bottom of 
this email.  (Are attachments allowed on this mailing list?  I sure hope my 
mailer doesn't eat my newlines.)  Sometimes it fails every time I try, 
sometimes I have to try it a dozen times.  To make it fail more frequently, 
uncomment more fork() calls.  (My original application on the Red Hat system 
failed every single time I ran two copies at once.)

No clue if this is of any value, but here's part of the output from 
SQLITE_LOCK_TRACE:

OPEN 11480672 test.db
fcntl 140340145452864 3 SETLK RDLCK 1073741824 1 4222915 0
fcntl 140340145452864 3 SETLK RDLCK 1073741826 510 4222915 0
fcntl 140340145452864 3 SETLK UNLCK 1073741824 1 4222915 0
FETCH 11480672 page 1 hash(00000000)
fcntl 140340145452864 3 SETLK UNLCK 0 0 4221434 0
fcntl 140340145452864 3 SETLK RDLCK 1073741824 1 0 0
fcntl 140340145452864 3 SETLK RDLCK 1073741826 510 0 0
fcntl 140340145452864 3 SETLK UNLCK 1073741824 1 0 0
FETCH 11480672 page 2 hash(00000000)
fcntl 140340145452864 3 SETLK WRLCK 1073741825 1 4273584 0
TRANSACTION 11480672
JOURNAL 11480672 page 2 needSync=1 hash(00000000)
fcntl 140340145452864 3 SETLK WRLCK 1073741824 1 2 0
fcntl 140340145452864 3 SETLK WRLCK 1073741826 510 2 -1
fcntl-failure-reason: RDLCK 1073741826 510 16020
fcntl 140340145452864 3 SETLK WRLCK 1073741826 510 2 -1
fcntl-failure-reason: RDLCK 1073741826 510 16020
(last two lines repeat)

Here's the test SQL being executed:

CREATE TABLE t(a, b, c);              -- Once only
BEGIN;
SELECT count(*) FROM t;              -- Used as :rows below
INSERT INTO t VALUES(:pid, :rows, 0); -- :pid is getpid()
INSERT INTO t VALUES(:pid, :rows, 1);
INSERT INTO t VALUES(:pid, :rows, 2);
COMMIT;

Here's test.c:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "sqlite3.h"
#define ACTION raise(SIGSTOP)
/*#define ACTION exit(EXIT_FAILURE) */
#define die(format, ...) (fprintf(stderr, "(%d) %s:%d: " format "\n", \
        getpid(), strrchr("/" __FILE__, '/') + 1, __LINE__, ##__VA_ARGS__), \
        ACTION)
int main(void)
{
    sqlite3 *db;
    sqlite3_stmt *stmt;
    char *p;
    int i;
    int rows;
    int rc;
    /* Initialize database if the file does not exist. */
    if (access("test.db", F_OK) < 0) {
        if ((rc = sqlite3_open_v2("test.db", &db,
                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
                NULL)) != SQLITE_OK) {
            die("sqlite3_open_v2: %s",
                    db ? sqlite3_errmsg(db) : sqlite3_errstr(rc));
        } else if (sqlite3_exec(db, "CREATE TABLE t(a, b, c)",
                NULL, NULL, &p) != SQLITE_OK) {
            die("sqlite3_exec: %s", p);
        } else if ((rc = sqlite3_close(db)) != SQLITE_OK) {
            die("sqlite3_close: %s", sqlite3_errstr(rc));
        }
    }
    /* Do everything simultaneously. */
    fork();
/*  fork();
    fork();
    fork(); Uncomment more of these for increased parallelism. */
    /* Open the database. */
    if ((rc = sqlite3_open_v2("test.db", &db,
            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
            NULL)) != SQLITE_OK) {
        die("sqlite3_open_v2: %s",
                db ? sqlite3_errmsg(db) : sqlite3_errstr(rc));
    }
    /* Set busy timeout. */
    if (sqlite3_busy_timeout(db, 5000) != SQLITE_OK) {
        die("sqlite3_busy_timeout: %s", sqlite3_errmsg(db));
    }
    /* Begin the transaction. */
    if (sqlite3_exec(db, "BEGIN", NULL, NULL, &p) != SQLITE_OK) {
        die("sqlite3_exec: %s", p);
    }
    /* Get the number of rows already present in the table. */
    sqlite3_prepare_v2(db, "SELECT count(*) FROM t", -1, &stmt, NULL);
    if (!stmt) {
        die("sqlite3_prepare_v2: %s", sqlite3_errmsg(db));
    } else if (sqlite3_step(stmt) != SQLITE_ROW) {
        die("sqlite3_step: %s", sqlite3_errmsg(db));
    }
    rows = sqlite3_column_int(stmt, 0);
    if (sqlite3_finalize(stmt) != SQLITE_OK) {
        die("sqlite3_finalize: %s", sqlite3_errmsg(db));
    }
    /* Insert some rows. */
    sqlite3_prepare_v2(db, "INSERT INTO t VALUES(?, ?, ?)", -1, &stmt, NULL);
    if (!stmt) {
        die("sqlite3_prepare_v2: %s", sqlite3_errmsg(db));
    }
    for (i = 0; i < 3; ++i) {
        if (sqlite3_bind_int(stmt, 1, (int)getpid()) != SQLITE_OK) {
            die("sqlite3_bind_int: %s", sqlite3_errmsg(db));
        } else if (sqlite3_bind_int(stmt, 2, rows) != SQLITE_OK) {
            die("sqlite3_bind_int: %s", sqlite3_errmsg(db));
        } else if (sqlite3_bind_int(stmt, 3, i) != SQLITE_OK) {
            die("sqlite3_bind_int: %s", sqlite3_errmsg(db));
        } else if (sqlite3_step(stmt) != SQLITE_DONE) {
            die("sqlite3_step: %s", sqlite3_errmsg(db));
        } else if (sqlite3_reset(stmt) != SQLITE_OK) {
            die("sqlite3_reset: %s", sqlite3_errmsg(db));
        }
    }
    if (sqlite3_finalize(stmt) != SQLITE_OK) {
        die("sqlite3_finalize: %s", sqlite3_errmsg(db));
    }
    /* Commit the transaction. */
    if (sqlite3_exec(db, "COMMIT", NULL, NULL, &p) != SQLITE_OK) {
        die("sqlite3_exec: %s", p);
    }
    /* Close the database. */
    if ((rc = sqlite3_close(db)) != SQLITE_OK) {
        die("sqlite3_close: %s", sqlite3_errstr(rc));
    }
    /* Congratulations on making it this far. */
    return EXIT_SUCCESS;
}
/* vim: set sts=4 sw=4 tw=80 et ft=c: */

Here's the makefile:

CFLAGS += -ggdb3
CFLAGS += -Wall
test: LDFLAGS += -ldl
test: LDFLAGS += -lpthread
test: test.o sqlite3.o
test.o: test.c sqlite3.h
sqlite3.o: CPPFLAGS += -D_GNU_SOURCE
sqlite3.o: CPPFLAGS += -DHAVE_INTTYPES_H
sqlite3.o: CPPFLAGS += -DHAVE_STDINT_H
sqlite3.o: CPPFLAGS += -DHAVE_FDATASYNC
sqlite3.o: CPPFLAGS += -DHAVE_USLEEP
sqlite3.o: CPPFLAGS += -DHAVE_LOCALTIME_R
sqlite3.o: CPPFLAGS += -DHAVE_GMTIME_R
sqlite3.o: CPPFLAGS += -DHAVE_STRERROR_R
sqlite3.o: CPPFLAGS += -DHAVE_POSIX_FALLOCATE
#sqlite3.o: CPPFLAGS += -DSQLITE_ENABLE_SETLK_TIMEOUT
sqlite3.o: sqlite3.c
clean:
    -$(RM) test test.o sqlite3.o
# vim: set ts=4 sw=4 tw=80 noet ft=make:
------------------------------------------- CONFIDENTIALITY NOTICE: This email 
and any attachments are for the sole use of the intended recipient and may 
contain material that is proprietary, confidential, privileged or otherwise 
legally protected or restricted under applicable government laws. Any review, 
disclosure, distributing or other use without expressed permission of the 
sender is strictly prohibited. If you are not the intended recipient, please 
contact the sender and delete all copies without reading, printing, or saving..
_______________________________________________
sqlite-users mailing list
sqlite-users@mailinglists.sqlite.org
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users
  
_______________________________________________
sqlite-users mailing list
sqlite-users@mailinglists.sqlite.org
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to