]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the test/fork-test.c test program.
authordrh <>
Wed, 13 Nov 2024 16:08:02 +0000 (16:08 +0000)
committerdrh <>
Wed, 13 Nov 2024 16:08:02 +0000 (16:08 +0000)
FossilOrigin-Name: 0611e2b0cf3f33c28cc9ff6c5da7ebba2033bcbda5b1072a30021a3e1fb4e738

manifest
manifest.uuid
test/fork-test.c [new file with mode: 0644]

index eeed2863da718b1704c8e7a207a9c34a2def9b76..3fd0f2dc1e93f4d7b24132e642b65066931ffb40 100644 (file)
--- a/manifest
+++ b/manifest
@@ -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.
index 5cd62d912ceb879ca258298ec8456af454701302..c86cacfebd5aa081f86800d9737c856bac58cea6 100644 (file)
@@ -1 +1 @@
-f0e917fcf51b59f8ccfe5b9341937341d0e6016eb275d6c33dcb10b0b301a9da
+0611e2b0cf3f33c28cc9ff6c5da7ebba2033bcbda5b1072a30021a3e1fb4e738
diff --git a/test/fork-test.c b/test/fork-test.c
new file mode 100644 (file)
index 0000000..8a9b4b4
--- /dev/null
@@ -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;
+}