| /* |
| ** 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; |
| } |