Add the test/fork-test.c test program.

FossilOrigin-Name: 0611e2b0cf3f33c28cc9ff6c5da7ebba2033bcbda5b1072a30021a3e1fb4e738
This commit is contained in:
drh 2024-11-13 16:08:02 +00:00
parent 1b37bc0e66
commit 31c160ab8f
3 changed files with 317 additions and 6 deletions

View File

@ -1,5 +1,5 @@
C Add\sthe\sSQLITE_FCNTL_NULL_IO\sfile-control.
D 2024-11-13T14:58:35.917
C Add\sthe\stest/fork-test.c\stest\sprogram.
D 2024-11-13T16:08:02.102
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md e108e1e69ae8e8a59e93c455654b8ac9356a11720d3345df2a4743e9590fb20d
@ -1154,6 +1154,7 @@ F test/fkey7.test 64fb28da03da5dfe3cdef5967aa7e832c2507bf7fb8f0780cacbca1f2338d0
F test/fkey8.test 51deda7f1a1448bca95875e4a6e1a3a75b4bd7215e924e845bd60de60e4d84bf
F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
F test/fordelete.test ba98f14446b310f9c9d935b97ec748753d0144a28b356ba30d1f4f6958fdde5c
F test/fork-test.c 9ac2e6423a1d38df3d6be0e8ac15608b545de21e2b19d9d876254c5931b63edb
F test/format4.test eeae341953db8b6bda7f549044797c3278a6cc345d11ada81471671b654f8ef4
F test/fp-speed-1.c b37de94eba034e1703668816225f54510ec60fb0685406608cc707afe6b8234d
F test/fpconv1.test d5d8aa0c427533006c112fb1957cdd1ea68c1d0709470dabb9ca02c2e4c06ad8
@ -2198,8 +2199,8 @@ F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd7227350
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P a4e976a030851357049e672bbc0ff66d9cc152b3d5f8e03fff36a7c6f060a755
R cbde25c924d15d902cdf437d5236e99e
P f0e917fcf51b59f8ccfe5b9341937341d0e6016eb275d6c33dcb10b0b301a9da
R 6602510d12e6c22a0b1d041f1323efab
U drh
Z 9feaba1d713c9ff220e94d51317a455c
Z a609b555f7a83a611caf3857959d5dfd
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
f0e917fcf51b59f8ccfe5b9341937341d0e6016eb275d6c33dcb10b0b301a9da
0611e2b0cf3f33c28cc9ff6c5da7ebba2033bcbda5b1072a30021a3e1fb4e738

310
test/fork-test.c Normal file
View File

@ -0,0 +1,310 @@
/*
** 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;
}