]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the new threadtest5 test program for stressing multiple database
authordrh <>
Wed, 12 May 2021 14:17:20 +0000 (14:17 +0000)
committerdrh <>
Wed, 12 May 2021 14:17:20 +0000 (14:17 +0000)
connections in the same process hammering on a single database.
Primarily designed to test memdb, but works on any database.

FossilOrigin-Name: 8db1c06958b8e1691440d4fd392648b74a1940b721852dabd315005efad520fc

Makefile.in
main.mk
manifest
manifest.uuid
test/threadtest5.c [new file with mode: 0644]

index 72be0e4fabf64d29a7a9649518b1dde0d0522d4c..f010cdaa159495337d32c1cadcfd133ed9fa4ca8 100644 (file)
@@ -1427,6 +1427,9 @@ threadtest3$(TEXE): sqlite3.lo $(THREADTEST3_SRC)
 threadtest: threadtest3$(TEXE)
        ./threadtest3$(TEXE)
 
+threadtest5: sqlite3.c $(TOP)/test/threadtest5.c
+       $(LTLINK) $(TOP)/test/threadtest5.c sqlite3.c -o $@ $(TLIBS)
+
 releasetest:
        $(TCLSH_CMD) $(TOP)/test/releasetest.tcl
 
@@ -1480,6 +1483,7 @@ clean:
        rm -f sqldiff sqldiff.exe
        rm -f dbhash dbhash.exe
        rm -f fts5.* fts5parse.*
+       rm -f threadtest5
 
 distclean:     clean
        rm -f config.h config.log config.status libtool Makefile sqlite3.pc
diff --git a/main.mk b/main.mk
index 8f1204d3860814f09202e39ee7ef72574d2b4573..d8b44f4190ac86f6db602f927d4c5c4f0f40ee07 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -1079,6 +1079,9 @@ rbu$(EXE): $(TOP)/ext/rbu/rbu.c $(TOP)/ext/rbu/sqlite3rbu.c sqlite3.o
 loadfts: $(TOP)/tool/loadfts.c libsqlite3.a
        $(TCC) $(TOP)/tool/loadfts.c libsqlite3.a -o loadfts $(THREADLIB)
 
+threadtest5:   $(TOP)/test/threadtest5.c libsqlite3.a
+       $(TCC) $(TOP)/test/threadtest5.c libsqlite3.a -o threadtest5 $(THREADLIB)
+
 # This target will fail if the SQLite amalgamation contains any exported
 # symbols that do not begin with "sqlite3_". It is run as part of the
 # releasetest.tcl script.
@@ -1141,3 +1144,4 @@ clean:
        rm -f sqldiff sqldiff.exe
        rm -f fts5.* fts5parse.*
        rm -f lsm.h lsm1.c
+       rm -f threadtest5
index c6bbea6614e7bb4988a5282a30941c67c421e6d8..6aa2c423a5b343c92d4fdcd247fa5fc10b2a7def 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C Enhance\sthe\smemdb\sVFS\sso\sthat\sit\sis\sable\sto\sshare\sdatabases\samong\smultiple\ndatabase\sconnections\sin\sthe\ssame\sprocess,\sas\slong\sas\sthe\sdatabase\sfilename\nbegins\swith\s"/".\s\sThis\sprovides\sa\sway\sfor\sthreads\sto\sshare\san\sin-memory\ndatabase\swithout\sthe\suse\sof\sshared-cache\smode.
-D 2021-05-12T11:55:59.991
+C Add\sthe\snew\sthreadtest5\stest\sprogram\sfor\sstressing\smultiple\sdatabase\nconnections\sin\sthe\ssame\sprocess\shammering\son\sa\ssingle\sdatabase.\nPrimarily\sdesigned\sto\stest\smemdb,\sbut\sworks\son\sany\sdatabase.
+D 2021-05-12T14:17:20.226
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in b0982835aa57c66a43a618679b278a0e6e0e6ec08cb940b25ca9928b7c409ddc
+F Makefile.in 30c6d39386246695e951a676973e0bf57aabbd1e37024c07e657af89dd332555
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 F Makefile.msc 6443729ba6a013a0fea4f999b22e54760f36e73c2e691554f0c4bfa1dbe4d070
 F README.md 2a71913f398ecac5f3e10945fcf438aed425c2e9ed9874de561156ba77fb7023
@@ -465,7 +465,7 @@ F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 6871d77dc5966921c48c50e1a12598a82a900c8b845d4fdf04c56d2a5cc6dc5d
+F main.mk d1654e2923a7195603a7d6564dabe037a3a20ea5b3817002db534ffc4ad8cecf
 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -1463,6 +1463,7 @@ F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b
 F test/threadtest2.c a70a8e94bef23339d34226eb9521015ef99f4df8
 F test/threadtest3.c e63013af10cf236c7610eb06d33bde08c861806dc64be811940ff4d9ddd34a4f
 F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925
+F test/threadtest5.c 9b4d782c58d8915d7e955ff8051f3d03628bda0d33b82971ea8c0f2f2808c421 w test/memdb-threads-1.c
 F test/time-wordcount.sh 8e0b0f8109367827ad5d58f5cc849705731e4b90
 F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c
 F test/tkt-18458b1a.test 6a62cb1ee50fa3c620da59e3a6f531eb38fceaf7e2166203816b724524e6f1d6
@@ -1912,8 +1913,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P f39666e64d6d32420170c54f874d8314eb8c6f91df625f7b28f9ccffb9474dc0 98dae595d861941bb0bcd12126ee02492587c466e6da579a58b5dc4a4d655917
-R 1441e09faa39fd11227f415ae28549ff
-T +closed 98dae595d861941bb0bcd12126ee02492587c466e6da579a58b5dc4a4d655917
+P 533fffc4a39b01c3aba75bd3271fd6ccd9516d9681ed04adbe19bd7de03f4c16
+R 4fefc92ffd76e6c05a1532a9460cad0d
 U drh
-Z bbd64c76e9984e3f1696dac12550f00b
+Z d3fbbeb2377b80ce42b7a7b26e8ca8a8
index 6e05c7e90be4e2f883ae1d1cd95dd81bc6789741..9f7f95d086a456d584fcd28b1c547414f05db099 100644 (file)
@@ -1 +1 @@
-533fffc4a39b01c3aba75bd3271fd6ccd9516d9681ed04adbe19bd7de03f4c16
\ No newline at end of file
+8db1c06958b8e1691440d4fd392648b74a1940b721852dabd315005efad520fc
\ No newline at end of file
diff --git a/test/threadtest5.c b/test/threadtest5.c
new file mode 100644 (file)
index 0000000..6e6610f
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+** 2021-05-12
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** Testing threading behavior when multiple database connections in separate
+** threads of the same process are all talking to the same database file.
+**
+** For best results, ensure that SQLite is compiled with HAVE_USLEEP=1
+**
+** Only works on unix platforms.
+**
+** Usage:
+**
+**      ./threadtest5  ?DATABASE?
+**
+** If DATABASE is omitted, it defaults to using file:/mem?vfs=memdb.
+*/
+#include "sqlite3.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+/* Name of the in-memory database */
+static char *zDbName = 0;
+
+/* True for debugging */
+static int eVerbose = 0;
+
+/* If rc is not SQLITE_OK, then print an error message and stop
+** the test.
+*/
+static void error_out(int rc, const char *zCtx, int lineno){
+  if( rc!=SQLITE_OK ){
+    fprintf(stderr, "error %d at %d in \"%s\"\n", rc, lineno, zCtx);
+    exit(-1);
+  }
+}
+
+#if 0
+/* Return the number of milliseconds since the Julian epoch (-4714-11-24).
+*/
+static sqlite3_int64 gettime(void){
+  sqlite3_int64 tm;
+  sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
+  pVfs->xCurrentTimeInt64(pVfs, &tm);
+  return tm;
+}
+#endif
+
+/* Run the SQL in the second argument.
+*/
+static int exec(
+  sqlite3 *db,
+  const char *zId,
+  int lineno,
+  const char *zFormat,
+  ...
+){
+  int rc;
+  va_list ap;
+  char *zSql;
+  va_start(ap, zFormat);
+  zSql = sqlite3_vmprintf(zFormat, ap);
+  va_end(ap);
+  if( eVerbose){
+    printf("%s:%d: [%s]\n", zId, lineno, zSql);
+    fflush(stdout);
+  }
+  rc = sqlite3_exec(db, zSql, 0, 0, 0);
+  if( rc && eVerbose ){
+    printf("%s:%d: return-code %d\n", zId, lineno, rc);
+    fflush(stdout);
+  }
+  sqlite3_free(zSql);
+  return rc;
+}
+
+/* Generate a perpared statement from the input SQL
+*/
+static sqlite3_stmt *prepare(
+  sqlite3 *db,
+  const char *zId,
+  int lineno,
+  const char *zFormat,
+  ...
+){
+  int rc;
+  va_list ap;
+  char *zSql;
+  sqlite3_stmt *pStmt = 0;
+  va_start(ap, zFormat);
+  zSql = sqlite3_vmprintf(zFormat, ap);
+  va_end(ap);
+  if( eVerbose){
+    printf("%s:%d: [%s]\n", zId, lineno, zSql);
+    fflush(stdout);
+  }
+
+  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+  if( rc ){
+    printf("%s:%d: ERROR - %s\n", zId, lineno, sqlite3_errmsg(db));
+    exit(-1);
+  }
+  sqlite3_free(zSql);
+  return pStmt;
+}
+
+/*
+** Wait for table zTable to exist in the schema.
+*/
+static void waitOnTable(sqlite3 *db, const char *zWorker, const char *zTable){
+  while(1){
+    int eFound = 0;
+    sqlite3_stmt *q = prepare(db, zWorker, __LINE__,
+             "SELECT 1 FROM sqlite_schema WHERE name=%Q", zTable);
+    if( sqlite3_step(q)==SQLITE_ROW && sqlite3_column_int(q,0)!=0 ){
+      eFound = 1;
+    }
+    sqlite3_finalize(q);
+    if( eFound ) return;
+    sqlite3_sleep(1);
+  }
+}
+
+/*
+** Return true if x is  a prime number
+*/
+static int isPrime(int x){
+  int i;
+  if( x<2 ) return 1;
+  for(i=2; i*i<=x; i++){
+    if( (x%i)==0 ) return 0;
+  }
+  return 1;
+}
+
+/* Each worker thread runs an instance of the following */
+static void *worker(void *pArg){
+  int rc;
+  const char *zName = (const char*)pArg;
+  sqlite3 *db = 0;
+
+  if( eVerbose ){
+    printf("%s: startup\n", zName);
+    fflush(stdout);
+  }
+
+  rc = sqlite3_open(zDbName, &db);
+  error_out(rc, "sqlite3_open", __LINE__);
+  sqlite3_busy_timeout(db, 2000);
+
+  while( 1 ){
+    sqlite3_stmt *q1;
+    int tid = -1;
+    q1 = prepare(db, zName, __LINE__,
+            "UPDATE task SET doneby=%Q"
+            " WHERE tid=(SELECT tid FROM task WHERE doneby IS NULL LIMIT 1)"
+            "RETURNING tid", zName
+    );
+    if( sqlite3_step(q1)==SQLITE_ROW ){
+      tid = sqlite3_column_int(q1,0);
+    }
+    sqlite3_finalize(q1);
+    if( tid<0 ) break;
+    if( eVerbose ){
+      printf("%s: starting task %d\n", zName, tid);
+      fflush(stdout);
+    }
+    if( tid==1 ){
+      exec(db, zName, __LINE__,
+         "CREATE TABLE IF NOT EXISTS p1(x INTEGER PRIMARY KEY);"
+      );
+    }else if( tid>=2 && tid<=51 ){
+      int a, b, i;
+      waitOnTable(db, zName, "p1");
+      a = (tid-2)*200 + 1;
+      b = a+200;
+      for(i=a; i<b; i++){
+        if( isPrime(i) ){
+          exec(db, zName, __LINE__,
+              "INSERT INTO p1(x) VALUES(%d)", i);
+        }
+      }
+    }else if( tid==52 ){
+      exec(db, zName, __LINE__,
+         "CREATE TABLE IF NOT EXISTS p2(x INTEGER PRIMARY KEY);"
+         "WITH RECURSIVE"
+         "  c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<10000)"
+         "INSERT INTO p2(x) SELECT x FROM c;"
+      );
+    }else if( tid>=53 && tid<=62 ){
+      int a, b, i;
+      waitOnTable(db, zName, "p2");
+      a = (tid-53)*10 + 2;
+      b = a+9;
+      for(i=a; i<=b; i++){
+        exec(db, zName, __LINE__,
+          "DELETE FROM p2 WHERE x>%d AND (x %% %d)==0", i, i);
+      }
+    }
+    if( eVerbose ){
+      printf("%s: completed task %d\n", zName, tid);
+      fflush(stdout);
+    }
+    sqlite3_sleep(1);
+  }
+
+  sqlite3_close(db);
+
+  if( eVerbose ){
+    printf("%s: exit\n", zName);
+    fflush(stdout);
+  }
+  return 0;
+}
+
+/* Print a usage comment and die */
+static void usage(const char *argv0){
+  printf("Usage: %s [options]\n", argv0);
+  printf(
+    "  -num-workers N      Run N worker threads\n"
+    "  -v                  Debugging output\n"
+  );
+  exit(1);
+}
+
+/* Maximum number of threads */
+#define MX_WORKER 100
+
+/*
+** Main routine
+*/
+int main(int argc, char **argv){
+  int i;
+  int nWorker = 4;
+  int rc;
+  sqlite3 *db = 0;
+  sqlite3_stmt *q;
+  pthread_t aWorker[MX_WORKER];
+  char aWorkerName[MX_WORKER][8];
+
+  for(i=1; i<argc; i++){
+    const char *zArg = argv[i];
+    if( zArg[0]!='-' ){
+      if( zDbName==0 ){
+        zDbName = argv[i];
+        continue;
+      }
+      printf("unknown argument: %s\n", zArg);
+      usage(argv[0]);
+    }
+    if( zArg[1]=='-' ) zArg++;
+    if( strcmp(zArg, "-v")==0 ){
+      eVerbose = 1;
+      continue;
+    }
+    if( strcmp(zArg, "-num-workers")==0 && i+1<argc ){
+      nWorker = atoi(argv[++i]);
+      if( nWorker<1 || nWorker>MX_WORKER ){
+        printf("number of threads must be between 1 and %d\n", MX_WORKER);
+        exit(1);
+      }
+      continue;
+    }
+    printf("unknown option: %s\n", argv[i]);
+    usage(argv[0]);
+  }
+  if( zDbName==0 ) zDbName = "file:/mem?vfs=memdb";
+
+  sqlite3_config(SQLITE_CONFIG_URI, (int)1);
+  rc = sqlite3_open(zDbName, &db);
+  error_out(rc, "sqlite3_open", __LINE__);
+
+  rc = exec(db, "SETUP", __LINE__,
+    "DROP TABLE IF EXISTS task;\n"
+    "DROP TABLE IF EXISTS p1;\n"
+    "DROP TABLE IF EXISTS p2;\n"
+    "DROP TABLE IF EXISTS verify;\n"
+    "CREATE TABLE IF NOT EXISTS task(\n"
+    "  tid INTEGER PRIMARY KEY,\n"
+    "  doneby TEXT\n"
+    ");\n"
+    "WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c WHERE x<100)"
+    "INSERT INTO task(tid) SELECT x FROM c;\n"
+  );
+  error_out(rc, "sqlite3_exec", __LINE__);
+
+  for(i=0; i<nWorker; i++){
+    sqlite3_snprintf(sizeof(aWorkerName[i]), aWorkerName[i],
+             "W%02d", i);
+    pthread_create(&aWorker[i], 0, worker, aWorkerName[i]);
+  }
+  for(i=0; i<nWorker; i++){
+    pthread_join(aWorker[i], 0);
+  }
+
+  for(i=0; i<nWorker; i++){
+    q = prepare(db, "MAIN", __LINE__,
+          "SELECT group_concat(tid,',') FROM task WHERE doneby=%Q",
+          aWorkerName[i]);
+    if( sqlite3_step(q)==SQLITE_ROW ){
+      printf("%s: %s\n", aWorkerName[i], sqlite3_column_text(q,0));
+    }
+    sqlite3_finalize(q);
+  }
+  q = prepare(db, "MAIN", __LINE__, "SELECT count(*) FROM p2");
+  if( sqlite3_step(q)!=SQLITE_ROW || sqlite3_column_int(q,0)<10 ){
+    printf("incorrect result\n");
+    exit(-1);
+  }
+  sqlite3_finalize(q);
+  q = prepare(db, "MAIN", __LINE__, "SELECT x FROM p1 EXCEPT SELECT x FROM p2");
+  if( sqlite3_step(q)==SQLITE_ROW ){
+    printf("incorrect result\n");
+    exit(-1);
+  }
+  sqlite3_finalize(q);
+  q = prepare(db, "MAIN", __LINE__, "SELECT x FROM p2 EXCEPT SELECT x FROM p1");
+  if( sqlite3_step(q)==SQLITE_ROW ){
+    printf("incorrect result\n");
+    exit(-1);
+  }
+  sqlite3_finalize(q);
+  printf("OK\n");
+
+  sqlite3_close(db);
+  return 0;
+}