/ Changes On Branch superlock
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch superlock Excluding Merge-Ins

This is equivalent to a diff from 0a95589f to 1a3e7417

2010-11-19
18:36
Merge in the superlock demonstration changes. check-in: 570e79a8 user: drh tags: trunk
18:23
Add the checkpoint_fullfsync pragma which enables F_FULLFSYNC on checkpoint operations only, not during ordinary commit fsyncs. check-in: a0698673 user: drh tags: trunk
18:20
Add file test_superlock.c with example code for obtaining an exclusive lock on either rollback or wal mode databases. Closed-Leaf check-in: 1a3e7417 user: dan tags: superlock
14:37
Add the SQLITE_FCNTL_FILE_POINTER verb to sqlite3_file_control(). check-in: 4425b064 user: drh tags: superlock
2010-11-18
16:59
Merge with latest trunk fix. check-in: a8910e89 user: dan tags: blocking-checkpoint
16:58
Ensure tcl is using utf-8 as the system encoding when running capi3e.test. check-in: 0a95589f user: dan tags: trunk
15:44
Added some tests to check that umlaut characters are supported in filenames. check-in: c36f275d user: shaneh tags: trunk

Changes to main.mk.

247
248
249
250
251
252
253

254
255
256
257
258
259
260
  $(TOP)/src/test_osinst.c \
  $(TOP)/src/test_pcache.c \
  $(TOP)/src/test_quota.c \
  $(TOP)/src/test_rtree.c \
  $(TOP)/src/test_schema.c \
  $(TOP)/src/test_server.c \
  $(TOP)/src/test_stat.c \

  $(TOP)/src/test_tclvar.c \
  $(TOP)/src/test_thread.c \
  $(TOP)/src/test_vfs.c \
  $(TOP)/src/test_wsd.c

#TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c
#TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c







>







247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
  $(TOP)/src/test_osinst.c \
  $(TOP)/src/test_pcache.c \
  $(TOP)/src/test_quota.c \
  $(TOP)/src/test_rtree.c \
  $(TOP)/src/test_schema.c \
  $(TOP)/src/test_server.c \
  $(TOP)/src/test_stat.c \
  $(TOP)/src/test_superlock.c \
  $(TOP)/src/test_tclvar.c \
  $(TOP)/src/test_thread.c \
  $(TOP)/src/test_vfs.c \
  $(TOP)/src/test_wsd.c

#TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c
#TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c

Changes to src/main.c.

2352
2353
2354
2355
2356
2357
2358



2359
2360
2361
2362
2363
2364
2365
2366
      Pager *pPager;
      sqlite3_file *fd;
      sqlite3BtreeEnter(pBtree);
      pPager = sqlite3BtreePager(pBtree);
      assert( pPager!=0 );
      fd = sqlite3PagerFile(pPager);
      assert( fd!=0 );



      if( fd->pMethods ){
        rc = sqlite3OsFileControl(fd, op, pArg);
      }
      sqlite3BtreeLeave(pBtree);
    }
  }
  sqlite3_mutex_leave(db->mutex);
  return rc;   







>
>
>
|







2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
      Pager *pPager;
      sqlite3_file *fd;
      sqlite3BtreeEnter(pBtree);
      pPager = sqlite3BtreePager(pBtree);
      assert( pPager!=0 );
      fd = sqlite3PagerFile(pPager);
      assert( fd!=0 );
      if( op==SQLITE_FCNTL_FILE_POINTER ){
        *(sqlite3_file**)pArg = fd;
        rc = SQLITE_OK;
      }else if( fd->pMethods ){
        rc = sqlite3OsFileControl(fd, op, pArg);
      }
      sqlite3BtreeLeave(pBtree);
    }
  }
  sqlite3_mutex_leave(db->mutex);
  return rc;   

Changes to src/sqlite.h.in.

701
702
703
704
705
706
707


708
709
710
711
712
713
714
....
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245






5246
5247
5248
5249
5250
5251
5252
*/
#define SQLITE_FCNTL_LOCKSTATE        1
#define SQLITE_GET_LOCKPROXYFILE      2
#define SQLITE_SET_LOCKPROXYFILE      3
#define SQLITE_LAST_ERRNO             4
#define SQLITE_FCNTL_SIZE_HINT        5
#define SQLITE_FCNTL_CHUNK_SIZE       6



/*
** CAPI3REF: Mutex Handle
**
** The mutex module within SQLite defines [sqlite3_mutex] to be an
** abstract type for a mutex object.  The SQLite core never looks
** at the internal representation of an [sqlite3_mutex].  It only
................................................................................

/*
** CAPI3REF: Low-Level Control Of Database Files
**
** ^The [sqlite3_file_control()] interface makes a direct call to the
** xFileControl method for the [sqlite3_io_methods] object associated
** with a particular database identified by the second argument. ^The
** name of the database "main" for the main database or "temp" for the
** TEMP database, or the name that appears after the AS keyword for
** databases that are added using the [ATTACH] SQL command.
** ^A NULL pointer can be used in place of "main" to refer to the
** main database file.
** ^The third and fourth parameters to this routine
** are passed directly through to the second and third parameters of
** the xFileControl method.  ^The return value of the xFileControl
** method becomes the return value of this routine.






**
** ^If the second parameter (zDbName) does not match the name of any
** open database file, then SQLITE_ERROR is returned.  ^This error
** code is not remembered and will not be recalled by [sqlite3_errcode()]
** or [sqlite3_errmsg()].  The underlying xFileControl method might
** also return SQLITE_ERROR.  There is no way to distinguish between
** an incorrect zDbName and an SQLITE_ERROR return from the underlying







>
>







 







|








>
>
>
>
>
>







701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
....
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
*/
#define SQLITE_FCNTL_LOCKSTATE        1
#define SQLITE_GET_LOCKPROXYFILE      2
#define SQLITE_SET_LOCKPROXYFILE      3
#define SQLITE_LAST_ERRNO             4
#define SQLITE_FCNTL_SIZE_HINT        5
#define SQLITE_FCNTL_CHUNK_SIZE       6
#define SQLITE_FCNTL_FILE_POINTER     7


/*
** CAPI3REF: Mutex Handle
**
** The mutex module within SQLite defines [sqlite3_mutex] to be an
** abstract type for a mutex object.  The SQLite core never looks
** at the internal representation of an [sqlite3_mutex].  It only
................................................................................

/*
** CAPI3REF: Low-Level Control Of Database Files
**
** ^The [sqlite3_file_control()] interface makes a direct call to the
** xFileControl method for the [sqlite3_io_methods] object associated
** with a particular database identified by the second argument. ^The
** name of the database is "main" for the main database or "temp" for the
** TEMP database, or the name that appears after the AS keyword for
** databases that are added using the [ATTACH] SQL command.
** ^A NULL pointer can be used in place of "main" to refer to the
** main database file.
** ^The third and fourth parameters to this routine
** are passed directly through to the second and third parameters of
** the xFileControl method.  ^The return value of the xFileControl
** method becomes the return value of this routine.
**
** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes
** a pointer to the underlying [sqlite3_file] object to be written into
** the space pointed to by the 4th parameter.  ^The SQLITE_FCNTL_FILE_POINTER
** case is a short-circuit path which does not actually invoke the
** underlying sqlite3_io_methods.xFileControl method.
**
** ^If the second parameter (zDbName) does not match the name of any
** open database file, then SQLITE_ERROR is returned.  ^This error
** code is not remembered and will not be recalled by [sqlite3_errcode()]
** or [sqlite3_errmsg()].  The underlying xFileControl method might
** also return SQLITE_ERROR.  There is no way to distinguish between
** an incorrect zDbName and an SQLITE_ERROR return from the underlying

Changes to src/tclsqlite.c.

3576
3577
3578
3579
3580
3581
3582

3583
3584
3585
3586
3587
3588
3589
....
3607
3608
3609
3610
3611
3612
3613

3614
3615
3616
3617
3618
3619
3620
    extern int Sqlitetestbackup_Init(Tcl_Interp*);
    extern int Sqlitetestintarray_Init(Tcl_Interp*);
    extern int Sqlitetestvfs_Init(Tcl_Interp *);
    extern int SqlitetestStat_Init(Tcl_Interp*);
    extern int Sqlitetestrtree_Init(Tcl_Interp*);
    extern int Sqlitequota_Init(Tcl_Interp*);
    extern int Sqlitemultiplex_Init(Tcl_Interp*);


    Sqliteconfig_Init(interp);
    Sqlitetest1_Init(interp);
    Sqlitetest2_Init(interp);
    Sqlitetest3_Init(interp);
    Sqlitetest4_Init(interp);
    Sqlitetest5_Init(interp);
................................................................................
    Sqlitetestbackup_Init(interp);
    Sqlitetestintarray_Init(interp);
    Sqlitetestvfs_Init(interp);
    SqlitetestStat_Init(interp);
    Sqlitetestrtree_Init(interp);
    Sqlitequota_Init(interp);
    Sqlitemultiplex_Init(interp);


    Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);

#ifdef SQLITE_SSE
    Sqlitetestsse_Init(interp);
#endif
  }







>







 







>







3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
....
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
    extern int Sqlitetestbackup_Init(Tcl_Interp*);
    extern int Sqlitetestintarray_Init(Tcl_Interp*);
    extern int Sqlitetestvfs_Init(Tcl_Interp *);
    extern int SqlitetestStat_Init(Tcl_Interp*);
    extern int Sqlitetestrtree_Init(Tcl_Interp*);
    extern int Sqlitequota_Init(Tcl_Interp*);
    extern int Sqlitemultiplex_Init(Tcl_Interp*);
    extern int SqliteSuperlock_Init(Tcl_Interp*);

    Sqliteconfig_Init(interp);
    Sqlitetest1_Init(interp);
    Sqlitetest2_Init(interp);
    Sqlitetest3_Init(interp);
    Sqlitetest4_Init(interp);
    Sqlitetest5_Init(interp);
................................................................................
    Sqlitetestbackup_Init(interp);
    Sqlitetestintarray_Init(interp);
    Sqlitetestvfs_Init(interp);
    SqlitetestStat_Init(interp);
    Sqlitetestrtree_Init(interp);
    Sqlitequota_Init(interp);
    Sqlitemultiplex_Init(interp);
    SqliteSuperlock_Init(interp);

    Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0);

#ifdef SQLITE_SSE
    Sqlitetestsse_Init(interp);
#endif
  }

Added src/test_superlock.c.





















































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/*
** 2010 November 19
**
** 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.
**
*************************************************************************
** Example code for obtaining an exclusive lock on an SQLite database
** file. This method is complicated, but works for both WAL and rollback
** mode database files. The interface to the example code in this file 
** consists of the following two functions:
**
**   sqlite3demo_superlock()
**   sqlite3demo_superunlock()
*/

#include <sqlite3.h>
#include <string.h>               /* memset(), strlen() */
#include <assert.h>               /* assert() */

/*
** A structure to collect a busy-handler callback and argument and a count
** of the number of times it has been invoked.
*/
struct SuperlockBusy {
  int (*xBusy)(void*,int);        /* Pointer to busy-handler function */
  void *pBusyArg;                 /* First arg to pass to xBusy */
  int nBusy;                      /* Number of times xBusy has been invoked */
};
typedef struct SuperlockBusy SuperlockBusy;

/*
** The pCtx pointer passed to this function is actually a pointer to a
** SuperlockBusy structure. Invoke the busy-handler function encapsulated
** by the structure and return the result.
*/
static int superlockBusyHandler(void *pCtx, int UNUSED){
  SuperlockBusy *pBusy = (SuperlockBusy *)pCtx;
  if( pBusy->xBusy==0 ) return 0;
  return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++);
}

/*
** This function is used to determine if the main database file for 
** connection db is open in WAL mode or not. If no error occurs and the
** database file is in WAL mode, set *pbWal to true and return SQLITE_OK.
** If it is not in WAL mode, set *pbWal to false.
**
** If an error occurs, return an SQLite error code. The value of *pbWal
** is undefined in this case.
*/
static int superlockIsWal(sqlite3 *db, int *pbWal){
  int rc;                         /* Return Code */
  sqlite3_stmt *pStmt;            /* Compiled PRAGMA journal_mode statement */

  rc = sqlite3_prepare(db, "PRAGMA main.journal_mode", -1, &pStmt, 0);
  if( rc!=SQLITE_OK ) return rc;

  *pbWal = 0;
  if( SQLITE_ROW==sqlite3_step(pStmt) ){
    const char *zMode = (const char *)sqlite3_column_text(pStmt, 0);
    if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){
      *pbWal = 1;
    }
  }

  return sqlite3_finalize(pStmt);
}

/*
** Obtain an exclusive shm-lock on nByte bytes starting at offset idx
** of the file fd. If the lock cannot be obtained immediately, invoke
** the busy-handler until either it is obtained or the busy-handler
** callback returns 0.
*/
static int superlockShmLock(
  sqlite3_file *fd,               /* Database file handle */
  int idx,                        /* Offset of shm-lock to obtain */
  int nByte,                      /* Number of consective bytes to lock */
  SuperlockBusy *pBusy            /* Busy-handler wrapper object */
){
  int rc;
  int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock;
  do {
    rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE);
  }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) );
  return rc;
}

/*
** Obtain the extra locks on the database file required for WAL databases.
** Invoke the supplied busy-handler as required.
*/
static int superlockWalLock(
  sqlite3 *db,                    /* Database handle open on WAL database */
  SuperlockBusy *pBusy            /* Busy handler wrapper object */
){
  int rc;                         /* Return code */
  sqlite3_file *fd = 0;           /* Main database file handle */
  void volatile *p = 0;           /* Pointer to first page of shared memory */
  int nBusy = 0;                  /* Number of calls already made to xBusy */

  /* Obtain a pointer to the sqlite3_file object open on the main db file. */
  rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
  if( rc!=SQLITE_OK ) return rc;

  /* Obtain the "recovery" lock. Normally, this lock is only obtained by
  ** clients running database recovery.  
  */
  rc = superlockShmLock(fd, 2, 1, pBusy);
  if( rc!=SQLITE_OK ) return rc;

  /* Zero the start of the first shared-memory page. This means that any
  ** clients that open read or write transactions from this point on will
  ** have to run recovery before proceeding. Since they need the "recovery"
  ** lock that this process is holding to do that, no new read or write
  ** transactions may now be opened. Nor can a checkpoint be run, for the
  ** same reason.
  */
  rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);
  if( rc!=SQLITE_OK ) return rc;
  memset((void *)p, 0, 32);

  /* Obtain exclusive locks on all the "read-lock" slots. Once these locks
  ** are held, it is guaranteed that there are no active reader, writer or 
  ** checkpointer clients.
  */
  rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);
  return rc;
}

/*
** Obtain a superlock on the database file identified by zPath, using the
** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
** returned and output variable *ppLock is populated with an opaque handle
** that may be used with sqlite3demo_superunlock() to release the lock.
**
** If an error occurs, *ppLock is set to 0 and an SQLite error code 
** (e.g. SQLITE_BUSY) is returned.
**
** If a required lock cannot be obtained immediately and the xBusy parameter
** to this function is not NULL, then xBusy is invoked in the same way
** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
** until either the lock can be obtained or the busy-handler function returns
** 0 (indicating "give up").
*/
int sqlite3demo_superlock(
  const char *zPath,              /* Path to database file to lock */
  const char *zVfs,               /* VFS to use to access database file */
  int (*xBusy)(void*,int),        /* Busy handler callback */
  void *pBusyArg,                 /* Context arg for busy handler */
  void **ppLock                   /* OUT: Context to pass to superunlock() */
){
  sqlite3 *db = 0;                /* Database handle open on zPath */
  SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
  int rc;                         /* Return code */

  /* Open a database handle on the file to superlock. */
  rc = sqlite3_open_v2(
      zPath, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
  );

  /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
  ** a WAL database, this is all we need to do.  
  **
  ** A wrapper function is used to invoke the busy-handler instead of
  ** registering the busy-handler function supplied by the user directly
  ** with SQLite. This is because the same busy-handler function may be
  ** invoked directly later on when attempting to obtain the extra locks
  ** required in WAL mode. By using the wrapper, we are able to guarantee
  ** that the "nBusy" integer parameter passed to the users busy-handler
  ** represents the total number of busy-handler invocations made within
  ** this call to sqlite3demo_superlock(), including any made during the
  ** "BEGIN EXCLUSIVE".
  */
  if( rc==SQLITE_OK ){
    busy.xBusy = xBusy;
    busy.pBusyArg = pBusyArg;
    sqlite3_busy_handler(db, superlockBusyHandler, (void *)&busy);
    rc = sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0);
  }

  /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
  ** database, call superlockWalLock() to obtain the extra locks required
  ** to prevent readers, writers and/or checkpointers from accessing the
  ** db while this process is holding the superlock.
  **
  ** Before attempting any WAL locks, commit the transaction started above
  ** to drop the WAL read and write locks currently held. Otherwise, the
  ** new WAL locks may conflict with the old.
  */
  if( rc==SQLITE_OK ){
    int bWal;                     /* True for a WAL database, false otherwise */
    if( SQLITE_OK==(rc = superlockIsWal(db, &bWal)) && bWal ){
      rc = sqlite3_exec(db, "COMMIT", 0, 0, 0);
      if( rc==SQLITE_OK ){
        rc = superlockWalLock(db, &busy);
      }
    }
  }

  if( rc!=SQLITE_OK ){
    sqlite3_close(db);
    *ppLock = 0;
  }else{
    *ppLock = (void *)db;
  }

  return rc;
}

/*
** Release a superlock held on a database file. The argument passed to 
** this function must have been obtained from a successful call to
** sqlite3demo_superlock().
*/
void sqlite3demo_superunlock(void *pLock){
  sqlite3_close((sqlite3 *)pLock);
}

/*
** End of example code. Everything below here is the test harness.
**************************************************************************
**************************************************************************
*************************************************************************/


#ifdef SQLITE_TEST

#include <tcl.h>

struct InterpAndScript {
  Tcl_Interp *interp;
  Tcl_Obj *pScript;
};
typedef struct InterpAndScript InterpAndScript;

static void superunlock_del(ClientData cd){
  sqlite3demo_superunlock((void *)cd);
}

static int superunlock_cmd(
  ClientData cd,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  if( objc!=1 ){
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }
  Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
  return TCL_OK;
}

static int superlock_busy(void *pCtx, int nBusy){
  InterpAndScript *p = (InterpAndScript *)pCtx;
  Tcl_Obj *pEval;                 /* Script to evaluate */
  int iVal = 0;                   /* Value to return */

  pEval = Tcl_DuplicateObj(p->pScript);
  Tcl_IncrRefCount(pEval);
  Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
  Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
  Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
  Tcl_DecrRefCount(pEval);

  return iVal;
}

/*
** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
*/
static int superlock_cmd(
  ClientData cd,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  void *pLock;                    /* Lock context */
  char *zPath;
  char *zVfs = 0;
  InterpAndScript busy = {0, 0};
  int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
  int rc;                         /* Return code from sqlite3demo_superlock() */

  if( objc<3 || objc>5 ){
    Tcl_WrongNumArgs(
        interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
    return TCL_ERROR;
  }

  zPath = Tcl_GetString(objv[2]);

  if( objc>3 ){
    zVfs = Tcl_GetString(objv[3]);
    if( strlen(zVfs)==0 ) zVfs = 0;
  }
  if( objc>4 ){
    busy.interp = interp;
    busy.pScript = objv[4];
    xBusy = superlock_busy;
  }

  rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
  assert( rc==SQLITE_OK || pLock==0 );
  assert( rc!=SQLITE_OK || pLock!=0 );

  if( rc!=SQLITE_OK ){
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
    return TCL_ERROR;
  }

  Tcl_CreateObjCommand(
      interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
  );
  Tcl_SetObjResult(interp, objv[1]);
  return TCL_OK;
}

int SqliteSuperlock_Init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
  return TCL_OK;
}
#endif

Added test/superlock.test.





































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 2010 November 19
#
# 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.
#
#***********************************************************************
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl

set testprefix superlock

do_execsql_test 1.1 {
  CREATE TABLE t1(a, b);
  INSERT INTO t1 VALUES(1, 2);
  PRAGMA journal_mode = DELETE;
} {delete}

do_test 1.2 { sqlite3demo_superlock unlock test.db } {unlock}
do_catchsql_test 1.3 { SELECT * FROM t1 } {1 {database is locked}}
do_test 1.4 { unlock } {}

do_execsql_test 2.1 { 
  INSERT INTO t1 VALUES(3, 4);
  PRAGMA journal_mode = WAL;
} {wal}

do_test 2.2 { sqlite3demo_superlock unlock test.db } {unlock}
do_catchsql_test 2.3 { SELECT * FROM t1 }           {1 {database is locked}}
do_catchsql_test 2.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}}
do_catchsql_test 2.5 { PRAGMA wal_checkpoint }      {1 {database is locked}}
do_test 2.6 { unlock } {}

do_execsql_test 3.1 { INSERT INTO t1 VALUES(3, 4) } 

do_test 3.2 { sqlite3demo_superlock unlock test.db } {unlock}
do_catchsql_test 3.3 { SELECT * FROM t1 }           {1 {database is locked}}
do_catchsql_test 3.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}}
do_catchsql_test 3.5 { PRAGMA wal_checkpoint }      {1 {database is locked}}
do_test 3.6 { unlock } {}

do_execsql_test 4.1 { PRAGMA wal_checkpoint } {}

do_test 4.2 { sqlite3demo_superlock unlock test.db } {unlock}
do_catchsql_test 4.3 { SELECT * FROM t1 }           {1 {database is locked}}
do_catchsql_test 4.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}}
do_catchsql_test 4.5 { PRAGMA wal_checkpoint }      {1 {database is locked}}
do_test 4.6 { unlock } {}

do_multiclient_test tn {
  proc busyhandler {x} {
    switch -- $x {
      1 { sql1 "COMMIT" }
      2 { sql2 "COMMIT" }
      3 { sql3 "COMMIT" }
    }
    lappend ::busylist $x
    return 1
  }
  set ::busylist [list]

  do_test 5.$tn.1 {
    sql1 {
      CREATE TABLE t1(a, b);
      PRAGMA journal_mode = WAL;
      INSERT INTO t1 VALUES(1, 2);
    }
  } {wal}

  do_test 5.$tn.2 {
    sql1 { BEGIN ; SELECT * FROM t1 }
    sql2 { BEGIN ; INSERT INTO t1 VALUES(3, 4) }
    sql3 { BEGIN ; SELECT * FROM t1 }
  } {1 2}

  do_test 5.$tn.3 {
    set ::busylist [list]
    sqlite3demo_superlock unlock test.db "" busyhandler
    set ::busylist
  } {0 1 2 3}

  do_test 5.$tn.4 { csql2 { SELECT * FROM t1 } } {1 {database is locked}}
  do_test 5.$tn.5 { 
    csql3 { INSERT INTO t1 VALUES(5, 6) } 
  } {1 {database is locked}}
  do_test 5.$tn.6 { csql1 "PRAGMA wal_checkpoint" } {1 {database is locked}}

  do_test 5.$tn.7 { unlock } {}
}


finish_test