311 lines
9.6 KiB
C
311 lines
9.6 KiB
C
/*
|
|
** The program demonstrates how a child process created using fork()
|
|
** can continue to use SQLite for a database that the parent had opened and
|
|
** and was writing into when the fork() occurred.
|
|
**
|
|
** This program executes the following steps:
|
|
**
|
|
** 1. Create a new database file. Open it, and populate it.
|
|
** 2. Start a transaction and make changes.
|
|
** ^-- close the transaction prior to fork() if --commit-before-fork
|
|
** 3. Fork()
|
|
** 4. In the child, close the database connection. Special procedures
|
|
** are needed to close the database connection in the child. See the
|
|
** implementation below.
|
|
** 5. Commit the transaction in the parent.
|
|
** 6. Verify that the transaction committed in the parent.
|
|
** 7. In the child, after a delay to allow time for (5) and (6),
|
|
** open a new database connection and verify that the transaction
|
|
** committed by (5) is seen.
|
|
** 8. Add make further changes and commit them in the child, using the
|
|
** new database connection.
|
|
** 9. In the parent, after a delay to account for (8), verify that
|
|
** the new transaction added by (8) can be seen.
|
|
**
|
|
** Usage:
|
|
**
|
|
** fork-test FILENAME [options]
|
|
**
|
|
** Options:
|
|
**
|
|
** --wal Run the database in WAL mode
|
|
** --vfstrace Enable VFS tracing for debugging
|
|
** --commit-before-fork COMMIT prior to the fork() in step 3
|
|
** --delay-after-4 N Pause for N seconds after step 4
|
|
**
|
|
** How To Compile:
|
|
**
|
|
** gcc -O0 -g -Wall -I$(SQLITESRC) \
|
|
** f1.c $(SQLITESRC)/ext/misc/vfstrace.c $(SQLITESRC/sqlite3.c \
|
|
** -ldl -lpthread -lm
|
|
**
|
|
** Test procedure:
|
|
**
|
|
** (1) Run "fork-test x1.db". Verify no I/O errors occur and that
|
|
** both parent and child see all three rows in the t1 table.
|
|
**
|
|
** (2) Repeat (1) adding the --wal option.
|
|
**
|
|
** (3) Repeat (1) and (2) adding the --commit-before-fork option.
|
|
**
|
|
** (4) Repeat all prior steps adding the --delay-after-4 option with
|
|
** a timeout of 15 seconds or so. Then, while both parent and child
|
|
** are paused, run the CLI against the x1.db database from a separate
|
|
** window and verify that all the correct file locks are still working
|
|
** correctly.
|
|
**
|
|
** Take-Aways:
|
|
**
|
|
** * If a process has open SQLite database connections when it fork()s,
|
|
** the child can call exec() and all it well. Nothing special needs
|
|
** to happen.
|
|
**
|
|
** * If a process has open SQLite database connections when it fork()s,
|
|
** the child can do anything that does not involve using SQLite without
|
|
** causing problems in the parent. No special actions are needed in
|
|
** the child.
|
|
**
|
|
** * If a process has open SQLite database connections when it fork()s,
|
|
** the child can call sqlite3_close() on those database connections
|
|
** as long as there were no pending write transactions when the fork()
|
|
** occurred.
|
|
**
|
|
** * If a process has open SQLite database connections that are in the
|
|
** middle of a write transaction and then the processes fork()s, the
|
|
** child process should close the database connections using the
|
|
** procedures demonstrated in Step 4 below before trying to do anything
|
|
** else with SQLite.
|
|
**
|
|
** * If a child process can safely close SQLite database connections that
|
|
** it inherited via fork() using the procedures shown in Step 4 below
|
|
** even if the database connections were not involved in a write
|
|
** transaction at the time of the fork(). The special procedures are
|
|
** required if a write transaction was active. They are optional
|
|
** otherwise. No harm results from using the special procedures when
|
|
** they are not necessary.
|
|
**
|
|
** * Child processes that use SQLite should open their own database
|
|
** connections. They should not attempt to use a database connection
|
|
** that is inherited from the parent.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include "sqlite3.h"
|
|
|
|
/*
|
|
** Process ID of the parent
|
|
*/
|
|
static pid_t parentPid = 0;
|
|
|
|
/*
|
|
** Return either "parent" or "child", as appropriate.
|
|
*/
|
|
static const char *whoAmI(void){
|
|
return getpid()==parentPid ? "parent" : "child";
|
|
}
|
|
|
|
/*
|
|
** This is an sqlite3_exec() callback routine that prints all results.
|
|
*/
|
|
static int execCallback(void *pNotUsed, int nCol, char **aVal, char **aCol){
|
|
int i;
|
|
const char *zWho = whoAmI();
|
|
for(i=0; i<nCol; i++){
|
|
const char *zVal = aVal[i];
|
|
const char *zCol = aCol[i];
|
|
if( zVal==0 ) zVal = "NULL";
|
|
if( zCol==0 ) zCol = "NULL";
|
|
printf("%s: %s = %s\n", zWho, zCol, zVal);
|
|
fflush(stdout);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** Execute one or more SQL statements.
|
|
*/
|
|
static void sqlExec(sqlite3 *db, const char *zSql, int bCallback){
|
|
int rc;
|
|
char *zErr = 0;
|
|
printf("%s: %s\n", whoAmI(), zSql);
|
|
fflush(stdout);
|
|
rc = sqlite3_exec(db, zSql, bCallback ? execCallback : 0, 0, &zErr);
|
|
if( rc || zErr!=0 ){
|
|
printf("%s: %s: rc=%d: %s\n",
|
|
whoAmI(), zSql, rc, zErr);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Trace callback for the vfstrace extension.
|
|
*/
|
|
static int vfsTraceCallback(const char *zMsg, void *pNotUsed){
|
|
printf("%s: %s", whoAmI(), zMsg);
|
|
fflush(stdout);
|
|
return 0;
|
|
}
|
|
|
|
/* External VFS module provided by ext/misc/vfstrace.c
|
|
*/
|
|
extern int vfstrace_register(
|
|
const char *zTraceName, // Name of the newly constructed VFS
|
|
const char *zOldVfsName, // Name of the underlying VFS
|
|
int (*xOut)(const char*,void*), // Output routine. ex: fputs
|
|
void *pOutArg, // 2nd argument to xOut. ex: stderr
|
|
int makeDefault // Make the new VFS the default
|
|
);
|
|
|
|
|
|
int main(int argc, char **argv){
|
|
sqlite3 *db;
|
|
int rc;
|
|
int i;
|
|
int useWal = 0;
|
|
const char *zFilename = 0;
|
|
pid_t child = 0, c2;
|
|
int status;
|
|
int bCommitBeforeFork = 0;
|
|
int nDelayAfter4 = 0;
|
|
|
|
for(i=1; i<argc; i++){
|
|
const char *z = argv[i];
|
|
if( z[0]=='-' && z[1]=='-' && z[2]!=0 ) z++;
|
|
if( strcmp(z, "-wal")==0 ){
|
|
useWal = 1;
|
|
}else if( strcmp(z, "-commit-before-fork")==0 ){
|
|
bCommitBeforeFork = 1;
|
|
}else if( strcmp(z, "-delay-after-4")==0 && i+1<argc ){
|
|
i++;
|
|
nDelayAfter4 = atoi(argv[i]);
|
|
}else if( strcmp(z, "-vfstrace")==0 ){
|
|
vfstrace_register("vfstrace", 0, vfsTraceCallback, 0, 1);
|
|
}else if( z[0]=='-' ){
|
|
printf("unknown option: \"%s\"\n", argv[i]);
|
|
exit(1);
|
|
}else if( zFilename!=0 ){
|
|
printf("unknown argument: \"%s\"\n", argv[i]);
|
|
exit(1);
|
|
}else{
|
|
zFilename = argv[i];
|
|
}
|
|
}
|
|
if( zFilename==0 ){
|
|
printf("Usage: %s FILENAME\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
/** Step 1 **/
|
|
printf("Step 1:\n");
|
|
parentPid = getpid();
|
|
unlink(zFilename);
|
|
rc = sqlite3_open(zFilename, &db);
|
|
if( rc ){
|
|
printf("sqlite3_open() returns %d\n", rc);
|
|
exit(1);
|
|
}
|
|
if( useWal ){
|
|
sqlExec(db, "PRAGMA journal_mode=WAL;", 0);
|
|
}
|
|
sqlExec(db, "CREATE TABLE t1(x);", 0);
|
|
sqlExec(db, "INSERT INTO t1 VALUES('First row');", 0);
|
|
sqlExec(db, "SELECT x FROM t1;", 1);
|
|
|
|
/** Step 2 **/
|
|
printf("Step 2:\n");
|
|
sqlExec(db, "BEGIN IMMEDIATE;", 0);
|
|
sqlExec(db, "INSERT INTO t1 VALUES('Second row');", 0);
|
|
sqlExec(db, "SELECT x FROM t1;", 1);
|
|
if( bCommitBeforeFork ) sqlExec(db, "COMMIT", 0);
|
|
|
|
/** Step 3 **/
|
|
printf("Step 3:\n"); fflush(stdout);
|
|
child = fork();
|
|
if( child!=0 ){
|
|
printf("Parent = %d\nChild = %d\n", getpid(), child);
|
|
}
|
|
|
|
/** Step 4 **/
|
|
if( child==0 ){
|
|
int k;
|
|
printf("Step 4:\n"); fflush(stdout);
|
|
|
|
/***********************************************************************
|
|
** The following block of code closes the database connection without
|
|
** rolling back or changing any files on disk. This is necessary to
|
|
** preservce the pending transaction in the parent.
|
|
*/
|
|
for(k=0; 1/*exit-by-break*/; k++){
|
|
const char *zDbName = sqlite3_db_name(db, k);
|
|
sqlite3_file *pJrnl = 0;
|
|
if( k==1 ) continue;
|
|
if( zDbName==0 ) break;
|
|
sqlite3_file_control(db, zDbName, SQLITE_FCNTL_NULL_IO, 0);
|
|
sqlite3_file_control(db, zDbName, SQLITE_FCNTL_JOURNAL_POINTER, &pJrnl);
|
|
if( pJrnl && pJrnl->pMethods && pJrnl->pMethods->xFileControl ){
|
|
pJrnl->pMethods->xFileControl(pJrnl, SQLITE_FCNTL_NULL_IO, 0);
|
|
}
|
|
}
|
|
sqlite3_close(db);
|
|
/*
|
|
** End of special close procedures for SQLite database connections
|
|
** inherited via fork().
|
|
***********************************************************************/
|
|
|
|
printf("%s: database connection closed\n", whoAmI()); fflush(stdout);
|
|
}else{
|
|
/* Pause the parent briefly to give the child a chance to close its
|
|
** database connection */
|
|
sleep(1);
|
|
}
|
|
|
|
if( nDelayAfter4>0 ){
|
|
printf("%s: Delay for %d seconds\n", whoAmI(), nDelayAfter4);
|
|
fflush(stdout);
|
|
sleep(nDelayAfter4);
|
|
printf("%s: Continue after %d delay\n", whoAmI(), nDelayAfter4);
|
|
fflush(stdout);
|
|
}
|
|
|
|
/** Step 5 **/
|
|
if( child!=0 ){
|
|
printf("Step 5:\n");
|
|
if( !bCommitBeforeFork ) sqlExec(db, "COMMIT", 0);
|
|
sqlExec(db, "SELECT x FROM t1;", 1);
|
|
}
|
|
|
|
|
|
/** Step 7 **/
|
|
if( child==0 ){
|
|
sleep(2);
|
|
printf("Steps 7 and 8:\n");
|
|
rc = sqlite3_open(zFilename, &db);
|
|
if( rc ){
|
|
printf("Child unable to reopen the database. rc = %d\n", rc);
|
|
exit(1);
|
|
}
|
|
sqlExec(db, "SELECT * FROM t1;", 1);
|
|
|
|
/** Step 8 **/
|
|
sqlExec(db, "INSERT INTO t1 VALUES('Third row');", 0);
|
|
sqlExec(db, "SELECT * FROM t1;", 1);
|
|
sleep(1);
|
|
return 0;
|
|
}
|
|
c2 = wait(&status);
|
|
printf("Process %d finished with status %d\n", c2, status);
|
|
|
|
/** Step 9 */
|
|
if( child!=0 ){
|
|
printf("Step 9:\n");
|
|
sqlExec(db, "SELECT * FROM t1;", 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|