CoolFace
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
fork-test.c311 linesDownload Raw Back to test
1/*2** The program demonstrates how a child process created using fork()3** can continue to use SQLite for a database that the parent had opened and4** and was writing into when the fork() occurred.5**6** This program executes the following steps:7**8**   1.  Create a new database file.  Open it, and populate it.9**   2.  Start a transaction and make changes.10**       ^-- close the transaction prior to fork() if --commit-before-fork11**   3.  Fork()12**   4.  In the child, close the database connection.  Special procedures13**       are needed to close the database connection in the child.  See the14**       implementation below.15**   5.  Commit the transaction in the parent.16**   6.  Verify that the transaction committed in the parent.17**   7.  In the child, after a delay to allow time for (5) and (6),18**       open a new database connection and verify that the transaction19**       committed by (5) is seen.20**   8.  Add make further changes and commit them in the child, using the21**       new database connection.22**   9.  In the parent, after a delay to account for (8), verify that23**       the new transaction added by (8) can be seen.24**25** Usage:26**27**    fork-test FILENAME [options]28**29** Options:30**31**    --wal                       Run the database in WAL mode32**    --vfstrace                  Enable VFS tracing for debugging33**    --commit-before-fork        COMMIT prior to the fork() in step 334**    --delay-after-4 N           Pause for N seconds after step 435**36** How To Compile:37**38**   gcc -O0 -g -Wall -I$(SQLITESRC) \39**         f1.c $(SQLITESRC)/ext/misc/vfstrace.c $(SQLITESRC/sqlite3.c \40**         -ldl -lpthread -lm41**42** Test procedure:43**44**   (1)  Run "fork-test x1.db".  Verify no I/O errors occur and that45**        both parent and child see all three rows in the t1 table.46**47**   (2)  Repeat (1) adding the --wal option.48**49**   (3)  Repeat (1) and (2) adding the --commit-before-fork option.50**51**   (4)  Repeat all prior steps adding the --delay-after-4 option with52**        a timeout of 15 seconds or so.  Then, while both parent and child53**        are paused, run the CLI against the x1.db database from a separate54**        window and verify that all the correct file locks are still working55**        correctly.56**57** Take-Aways:58**59**   *   If a process has open SQLite database connections when it fork()s,60**       the child can call exec() and all it well.  Nothing special needs61**       to happen.62**63**   *   If a process has open SQLite database connections when it fork()s,64**       the child can do anything that does not involve using SQLite without65**       causing problems in the parent.  No special actions are needed in66**       the child.67**68**   *   If a process has open SQLite database connections when it fork()s,69**       the child can call sqlite3_close() on those database connections70**       as long as there were no pending write transactions when the fork()71**       occurred.72**73**   *   If a process has open SQLite database connections that are in the74**       middle of a write transaction and then the processes fork()s, the75**       child process should close the database connections using the76**       procedures demonstrated in Step 4 below before trying to do anything77**       else with SQLite.78**79**   *   If a child process can safely close SQLite database connections that80**       it inherited via fork() using the procedures shown in Step 4 below81**       even if the database connections were not involved in a write82**       transaction at the time of the fork().  The special procedures are83**       required if a write transaction was active.  They are optional84**       otherwise.  No harm results from using the special procedures when85**       they are not necessary.86**87**   *   Child processes that use SQLite should open their own database88**       connections.  They should not attempt to use a database connection89**       that is inherited from the parent.90*/91#include <stdio.h>92#include <string.h>93#include <sys/types.h>94#include <sys/wait.h>95#include <unistd.h>96#include <stdlib.h>97#include <errno.h>98#include "sqlite3.h"99 100/*101** Process ID of the parent102*/103static pid_t parentPid = 0;104 105/*106** Return either "parent" or "child", as appropriate.107*/108static const char *whoAmI(void){109  return getpid()==parentPid ? "parent" : "child";110}111 112/*113** This is an sqlite3_exec() callback routine that prints all results.114*/115static int execCallback(void *pNotUsed, int nCol, char **aVal, char **aCol){116  int i;117  const char *zWho = whoAmI();118  for(i=0; i<nCol; i++){119    const char *zVal = aVal[i];120    const char *zCol = aCol[i];121    if( zVal==0 ) zVal = "NULL";122    if( zCol==0 ) zCol = "NULL";123    printf("%s: %s = %s\n", zWho, zCol, zVal);124    fflush(stdout);125  }126  return 0;127}128 129/*130** Execute one or more SQL statements.131*/132static void sqlExec(sqlite3 *db, const char *zSql, int bCallback){133  int rc;134  char *zErr = 0;135  printf("%s: %s\n", whoAmI(), zSql);136  fflush(stdout);137  rc = sqlite3_exec(db, zSql, bCallback ? execCallback : 0, 0, &zErr);138  if( rc || zErr!=0 ){139    printf("%s: %s: rc=%d: %s\n", 140      whoAmI(), zSql, rc, zErr);141    exit(1);142  }143}144 145/*146** Trace callback for the vfstrace extension.147*/148static int vfsTraceCallback(const char *zMsg, void *pNotUsed){149  printf("%s: %s", whoAmI(), zMsg);150  fflush(stdout);151  return 0;152}153 154/* External VFS module provided by ext/misc/vfstrace.c155*/156extern int vfstrace_register(157  const char *zTraceName,         // Name of the newly constructed VFS158  const char *zOldVfsName,        // Name of the underlying VFS159  int (*xOut)(const char*,void*), // Output routine.  ex: fputs160  void *pOutArg,                  // 2nd argument to xOut.  ex: stderr161  int makeDefault                 // Make the new VFS the default162);163 164 165int main(int argc, char **argv){166  sqlite3 *db;167  int rc;168  int i;169  int useWal = 0;170  const char *zFilename = 0;171  pid_t child = 0, c2;172  int status;173  int bCommitBeforeFork = 0;174  int nDelayAfter4 = 0;175 176  for(i=1; i<argc; i++){177    const char *z = argv[i];178    if( z[0]=='-' && z[1]=='-' && z[2]!=0 ) z++;179    if( strcmp(z, "-wal")==0 ){180      useWal = 1;181    }else if( strcmp(z, "-commit-before-fork")==0 ){182      bCommitBeforeFork = 1;183    }else if( strcmp(z, "-delay-after-4")==0 && i+1<argc ){184      i++;185      nDelayAfter4 = atoi(argv[i]);186    }else if( strcmp(z, "-vfstrace")==0 ){187      vfstrace_register("vfstrace", 0, vfsTraceCallback, 0, 1);188    }else if( z[0]=='-' ){189      printf("unknown option: \"%s\"\n", argv[i]);190      exit(1);191    }else if( zFilename!=0 ){192      printf("unknown argument: \"%s\"\n", argv[i]);193      exit(1);194    }else{195      zFilename = argv[i];196    }197  }198  if( zFilename==0 ){199    printf("Usage: %s FILENAME\n", argv[0]);200    return 1;201  }202 203  /**  Step 1 **/204  printf("Step 1:\n");205  parentPid = getpid();206  unlink(zFilename);207  rc = sqlite3_open(zFilename, &db);208  if( rc ){209    printf("sqlite3_open() returns %d\n", rc);210    exit(1);211  }212  if( useWal ){213    sqlExec(db, "PRAGMA journal_mode=WAL;", 0);214  }215  sqlExec(db, "CREATE TABLE t1(x);", 0);216  sqlExec(db, "INSERT INTO t1 VALUES('First row');", 0);217  sqlExec(db, "SELECT x FROM t1;", 1);218 219  /**  Step 2 **/220  printf("Step 2:\n");221  sqlExec(db, "BEGIN IMMEDIATE;", 0);222  sqlExec(db, "INSERT INTO t1 VALUES('Second row');", 0);223  sqlExec(db, "SELECT x FROM t1;", 1);224  if( bCommitBeforeFork ) sqlExec(db, "COMMIT", 0);225 226  /**  Step 3 **/227  printf("Step 3:\n"); fflush(stdout);228  child = fork();229  if( child!=0 ){230    printf("Parent = %d\nChild = %d\n", getpid(), child);231  }232 233  /**  Step 4 **/234  if( child==0 ){235    int k;236    printf("Step 4:\n"); fflush(stdout);237 238    /***********************************************************************239    ** The following block of code closes the database connection without240    ** rolling back or changing any files on disk.  This is necessary to241    ** preservce the pending transaction in the parent. 242    */243    for(k=0; 1/*exit-by-break*/; k++){244      const char *zDbName = sqlite3_db_name(db, k);245      sqlite3_file *pJrnl = 0;246      if( k==1 ) continue;247      if( zDbName==0 ) break;248      sqlite3_file_control(db, zDbName, SQLITE_FCNTL_NULL_IO, 0);249      sqlite3_file_control(db, zDbName, SQLITE_FCNTL_JOURNAL_POINTER, &pJrnl);250      if( pJrnl && pJrnl->pMethods && pJrnl->pMethods->xFileControl ){251        pJrnl->pMethods->xFileControl(pJrnl, SQLITE_FCNTL_NULL_IO, 0);252      }253    }    254    sqlite3_close(db);255    /*256    ** End of special close procedures for SQLite database connections257    ** inherited via fork().258    ***********************************************************************/259 260    printf("%s: database connection closed\n", whoAmI()); fflush(stdout);261  }else{262    /* Pause the parent briefly to give the child a chance to close its263    ** database connection */264    sleep(1);265  }266 267  if( nDelayAfter4>0 ){268    printf("%s: Delay for %d seconds\n", whoAmI(), nDelayAfter4);269    fflush(stdout);270    sleep(nDelayAfter4);271    printf("%s: Continue after %d delay\n", whoAmI(), nDelayAfter4);272    fflush(stdout);273  }274 275  /**  Step 5 **/276  if( child!=0 ){277    printf("Step 5:\n");278    if( !bCommitBeforeFork ) sqlExec(db, "COMMIT", 0);279    sqlExec(db, "SELECT x FROM t1;", 1);280  }    281  282 283  /** Step 7 **/284  if( child==0 ){285    sleep(2);286    printf("Steps 7 and 8:\n");287    rc = sqlite3_open(zFilename, &db);288    if( rc ){289      printf("Child unable to reopen the database.  rc = %d\n", rc);290      exit(1);291    }292    sqlExec(db, "SELECT * FROM t1;", 1);293 294    /** Step 8 **/295    sqlExec(db, "INSERT INTO t1 VALUES('Third row');", 0);296    sqlExec(db, "SELECT * FROM t1;", 1);297    sleep(1);298    return 0;299  }300  c2 = wait(&status);301  printf("Process %d finished with status %d\n", c2, status);302 303  /** Step 9 */304  if( child!=0 ){305    printf("Step 9:\n");306    sqlExec(db, "SELECT * FROM t1;", 1);307  }308 309  return 0;310}311