text: /*
  ** 2011-02-02
  **
  ** 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.
  **
  *************************************************************************
  ** This file is part of the test program "threadtest3". Despite being a C
  ** file it is not compiled separately, but included by threadtest3.c using
  ** the #include directive normally used with header files.
  **
  ** This file contains the implementation of test cases:
  **
  **     checkpoint_starvation_1
  **     checkpoint_starvation_2
  */
  
  /*
  ** Both test cases involve 1 writer/checkpointer thread and N reader threads.
  ** 
  ** Each reader thread performs a series of read transactions, one after 
  ** another. Each read transaction lasts for 100 ms.
  **
  ** The writer writes transactions as fast as possible. It uses a callback
  ** registered with sqlite3_wal_hook() to try to keep the WAL-size limited to 
  ** around 50 pages.
  **
  ** In test case checkpoint_starvation_1, the auto-checkpoint uses 
  ** SQLITE_CHECKPOINT_PASSIVE. In checkpoint_starvation_2, it uses RESTART.
  ** The expectation is that in the first case the WAL file will grow very 
  ** large, and in the second will be limited to the 50 pages or thereabouts.
  ** However, the overall transaction throughput will be lower for 
  ** checkpoint_starvation_2, as every checkpoint will block for up to 200 ms
  ** waiting for readers to clear.
  */
  
  /* Frame limit used by the WAL hook for these tests. */
  #define CHECKPOINT_STARVATION_FRAMELIMIT 50
  
  /* Duration in ms of each read transaction */
  #define CHECKPOINT_STARVATION_READMS    100
  
  struct CheckpointStarvationCtx {
    int eMode;
    int nMaxFrame;
  };
  typedef struct CheckpointStarvationCtx CheckpointStarvationCtx;
  
  static int checkpoint_starvation_walhook(
    void *pCtx, 
    sqlite3 *db, 
    const char *zDb, 
    int nFrame
  ){
    CheckpointStarvationCtx *p = (CheckpointStarvationCtx *)pCtx;
    if( nFrame>p->nMaxFrame ){
      p->nMaxFrame = nFrame;
    }
    if( nFrame>=CHECKPOINT_STARVATION_FRAMELIMIT ){
      sqlite3_wal_checkpoint_v2(db, zDb, p->eMode, 0, 0);
    }
    return SQLITE_OK;
  }
  
  static char *checkpoint_starvation_reader(int iTid, void *pArg){
    Error err = {0};
    Sqlite db = {0};
  
    opendb(&err, &db, "test.db", 0);
    while( !timetostop(&err) ){
      i64 iCount1, iCount2;
      sql_script(&err, &db, "BEGIN");
      iCount1 = execsql_i64(&err, &db, "SELECT count(x) FROM t1");
      usleep(CHECKPOINT_STARVATION_READMS*1000);
      iCount2 = execsql_i64(&err, &db, "SELECT count(x) FROM t1");
      sql_script(&err, &db, "COMMIT");
  
      if( iCount1!=iCount2 ){
        test_error(&err, "Isolation failure - %lld %lld", iCount1, iCount2);
      }
    }
    closedb(&err, &db);
  
    print_and_free_err(&err);
    return 0;
  }
  
  static void checkpoint_starvation_main(int nMs, CheckpointStarvationCtx *p){
    Error err = {0};
    Sqlite db = {0};
    Threadset threads = {0};
    int nInsert = 0;
    int i;
  
    opendb(&err, &db, "test.db", 1);
    sql_script(&err, &db, 
        "PRAGMA page_size = 1024;"
        "PRAGMA journal_mode = WAL;"
        "CREATE TABLE t1(x);"
    );
  
    setstoptime(&err, nMs);
  
    for(i=0; i<4; i++){
      launch_thread(&err, &threads, checkpoint_starvation_reader, 0);
      usleep(CHECKPOINT_STARVATION_READMS*1000/4);
    }
  
    sqlite3_wal_hook(db.db, checkpoint_starvation_walhook, (void *)p);
    while( !timetostop(&err) ){
      sql_script(&err, &db, "INSERT INTO t1 VALUES(randomblob(1200))");
      nInsert++;
    }
  
    printf(" Checkpoint mode  : %s\n",
        p->eMode==SQLITE_CHECKPOINT_PASSIVE ? "PASSIVE" : "RESTART"
    );
    printf(" Peak WAL         : %d frames\n", p->nMaxFrame);
    printf(" Transaction count: %d transactions\n", nInsert);
  
    join_all_threads(&err, &threads);
    closedb(&err, &db);
    print_and_free_err(&err);
  }
  
  static void checkpoint_starvation_1(int nMs){
    Error err = {0};
    CheckpointStarvationCtx ctx = { SQLITE_CHECKPOINT_PASSIVE, 0 };
    checkpoint_starvation_main(nMs, &ctx);
    if( ctx.nMaxFrame<(CHECKPOINT_STARVATION_FRAMELIMIT*10) ){
      test_error(&err, "WAL failed to grow - %d frames", ctx.nMaxFrame);
    }
    print_and_free_err(&err);
  }
  
  static void checkpoint_starvation_2(int nMs){
    Error err = {0};
    CheckpointStarvationCtx ctx = { SQLITE_CHECKPOINT_RESTART, 0 };
    checkpoint_starvation_main(nMs, &ctx);
    if( ctx.nMaxFrame>CHECKPOINT_STARVATION_FRAMELIMIT+10 ){
      test_error(&err, "WAL grew too large - %d frames", ctx.nMaxFrame);
    }
    print_and_free_err(&err);
  }
  