-C Enhance\sthe\sedit()\sfunction\sso\sthat\sit\sconverts\stext\sfrom\s\\r\\n\sback\sinto\s\\n\nonly\sif\sthe\soriginal\sunedited\scopy\scontained\sno\s\\r\\n\svalues.
-D 2018-08-06T02:08:53.886
+C Allow\ssqlite3_snapshot_open()\sto\sbe\scalled\sto\schange\sthe\ssnapshot\safter\sa\nread\stransaction\sis\salready\sopen\son\sdatabase.
+D 2018-08-06T17:12:36.835
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F Makefile.in 0a3a6c81e6fcb969ff9106e882f0a08547014ba463cb6beca4c4efaecc924ee6
F src/insert.c 894594952bcda1dc6e1549871e4022517563545ffc7a3f4e9e5f3faa788893fd
F src/legacy.c 134ab3e3fae00a0f67a5187981d6935b24b337bcf0f4b3e5c9fa5763da95bf4e
F src/loadext.c 6aae5739198d96c51ae6eb97c4a5b1744c22ed7a5a565a5399a717780d48a36b
-F src/main.c dc023f468eda20aed1fb7c300673cbb40617607b5771840e4229ec239dade250
+F src/main.c 3c7f2159687af1b1b0d0e0776f888adb94534e0bc52e7a1a1509f83e24cb6297
F src/malloc.c 07295435093ce354c6d9063ac05a2eeae28bd251d2e63c48b3d67c12c76f7e18
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de
F src/os_unix.c d9cf5ae0c79f31019d8325e8736c83914aeed64d8327a8d91a62b6439b748948
F src/os_win.c 070cdbb400097c6cda54aa005356095afdc2f3ee691d17192c54724ef146a971
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
-F src/pager.c 76d29b8a960dcb8b67210f095899d91e4a90673a6674ea58cfd1115b705a7fb9
-F src/pager.h c571b064df842ec8f2e90855dead9acf4cbe0d1b2c05afe0ef0d0145f7fd0388
+F src/pager.c 705de01dff9c3df9739c37a6d3b58cd2b1734fdabcef829b16cdc7721a9eeaa4
+F src/pager.h ecc554a55bc55d1c4ba5e17137b72e238e00bd81e72ff2662d8b9c8c10ae3963
F src/parse.y 704c94624d41d7d46a5467574130e55aa8029a563f4df538f0121475eae46e34
F src/pcache.c 135ef0bc6fb2e3b7178d49ab5c9176254c8a691832c1bceb1156b2fbdd0869bd
F src/pcache.h 072f94d29281cffd99e46c1539849f248c4b56ae7684c1f36626797fee375170
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c ae7396a314cc1bb1d767947cd57094e3a9ffcbb155ebc1b1c391e028c44a9a04
F src/shell.c.in 6e0aad854be738a5d0368940459399be211e9ac43aebe92bb9ed46cfe38d0e1f
-F src/sqlite.h.in c6451bb876adced3aba5b1682c6317d215c5eceaba21a6ce979e71a0b8d0bf95
+F src/sqlite.h.in 82b5768e36ce796ecf93c73bd88bad99def831ce7d470138e213ac693bf4ceab
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 9887b27e69c01e79c2cbe74ef73bf01af5b5703d6a7f0a4371e386d7249cb1c7
F src/sqliteInt.h a5d212bb7ae5cfc0540af6fb09eee2092a45fe083fac4191ee64ff70e7d4d78a
F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
F src/tclsqlite.c e0bf71a6d24b8c23393c000abffab05979bbca2a72d0b0f79260e2cf1527fda5
-F src/test1.c 55424c026dd93c06ad84ff4e46cec64aa3e12e767d50c31886e6a69ee53fe81e
+F src/test1.c 31c491ccb536bd9916a084e732ffe783b3c8973f2586d5a56aed0e3a9701dfff
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6
F src/vdbetrace.c 79d6dbbc479267b255a7de8080eee6e729928a0ef93ed9b0bfa5618875b48392
F src/vtab.c 678992ac8ec677a3f9b08126aaf891441083805e3b42574e3654d44538381c14
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
-F src/wal.c d44a0811afd2155b1157c38b33141d4ac028fda6232485bed664015bb05819ca
-F src/wal.h 8de5d2d3de0956d6f6cb48c83a4012d5f227b8fe940f3a349a4b7e85ebcb492a
+F src/wal.c d0d541116c378937fad99d89737cf36cfc67ff94ba1d29b2bf93bc7333e07e25
+F src/wal.h 606292549f5a7be50b6227bd685fa76e3a4affad71bb8ac5ce4cb5c79f6a176a
F src/walker.c ba7225773931760cf60bf22f34d0cce2588df7ce5ce0f215a52eb88234b55ac4
F src/where.c 155809967fbab889374dedf970ea6561b8fb519fcb165d6ba00776552ecc5cde
F src/whereInt.h b90ef9b9707ef750eab2a7a080c48fb4900315033274689def32d0cf5a81ebe4
F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5
F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2
F test/skipscan6.test 0b4cd1b4ac9f84d91454df513c99a4932fa07e8f27b8049bea605068b3e34ac7
-F test/snapshot.test 85735bd997a4f6d710140c28fd860519a299649f
+F test/snapshot.test fef12fc5c16ff21c4748509401cfba7d9a3d91156f1bfe23fb881d3bfc65ddfe
F test/snapshot2.test 925e42427e923262db63c9d7155183f889e3e99feaedec4075f659e51608344f
F test/snapshot3.test 9719443594a04778861bd20d12596c5f880af177d6cd62f111da3198cafc6096
F test/snapshot_fault.test 52c5e97ebd218846a8ae2da4d147d3e77d71f963
+F test/snapshot_up.test b778a04561a67b8bfde828f473a8d31dbde23e3f648e36237e0369421e08f23c
F test/soak.test 18944cf21b94a7fe0df02016a6ee1e9632bc4e8d095a0cb49d95e15d5cca2d5c
F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087
F test/sort.test c2adc635c2564241fefec0b3a68391ef6868fd3b
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 1caaaaa70f21fe71fbe0af227eea8d1367870e2575eedf248cc2a0b515783390
-R 599391bf767ca60a524e57a614c06c9d
-U drh
-Z e1c623c52da1d23ca607e4f314a88bce
+P 20c995d3f0f4de5410962172cb59da0f25edf0c62e199420186cc59ea874e981
+R 462315bcd4a2eb76f7dc221ad2f185d1
+T *branch * exp-snapshot-open
+T *sym-exp-snapshot-open *
+T -sym-trunk *
+U dan
+Z f721373f748dbb5b0d3c5c4f1daf5230
-20c995d3f0f4de5410962172cb59da0f25edf0c62e199420186cc59ea874e981
\ No newline at end of file
+051ac0152048ef52723196c26ca5f2629dafb782aec1c66fc30531bf54335043
\ No newline at end of file
iDb = sqlite3FindDbName(db, zDb);
if( iDb==0 || iDb>1 ){
Btree *pBt = db->aDb[iDb].pBt;
- if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
- rc = sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), pSnapshot);
+ if( sqlite3BtreeIsInTrans(pBt)==0 ){
+ Pager *pPager = sqlite3BtreePager(pBt);
+ int bUnlock = 0;
+ if( sqlite3BtreeIsInReadTrans(pBt) ){
+ if( db->nVdbeActive==0 ){
+ rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot);
+ if( rc==SQLITE_OK ){
+ bUnlock = 1;
+ rc = sqlite3BtreeCommit(pBt);
+ }
+ }
+ }else{
+ rc = SQLITE_OK;
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3PagerSnapshotOpen(pPager, pSnapshot);
+ }
if( rc==SQLITE_OK ){
rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
- sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
+ sqlite3PagerSnapshotOpen(pPager, 0);
+ }
+ if( bUnlock ){
+ sqlite3PagerSnapshotUnlock(pPager);
}
}
}
}
return rc;
}
+
+/*
+** The caller currently has a read transaction open on the database.
+** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise,
+** this function takes a SHARED lock on the CHECKPOINTER slot and then
+** checks if the snapshot passed as the second argument is still
+** available. If so, SQLITE_OK is returned.
+**
+** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
+** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
+** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
+** lock is released before returning.
+*/
+int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot){
+ int rc;
+ if( pPager->pWal ){
+ rc = sqlite3WalSnapshotCheck(pPager->pWal, pSnapshot);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+/*
+** Release a lock obtained by an earlier successful call to
+** sqlite3PagerSnapshotCheck().
+*/
+void sqlite3PagerSnapshotUnlock(Pager *pPager){
+ assert( pPager->pWal );
+ return sqlite3WalSnapshotUnlock(pPager->pWal);
+}
+
#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */
int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
int sqlite3PagerSnapshotRecover(Pager *pPager);
+ int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot);
+ void sqlite3PagerSnapshotUnlock(Pager *pPager);
# endif
#else
# define sqlite3PagerUseWal(x,y) 0
** CAPI3REF: Start a read transaction on an historical snapshot
** METHOD: sqlite3_snapshot
**
-** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
-** read transaction for schema S of
-** [database connection] D such that the read transaction
-** refers to historical [snapshot] P, rather than the most
-** recent change to the database.
-** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
-** or an appropriate [error code] if it fails.
-**
-** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
-** the first operation following the [BEGIN] that takes the schema S
-** out of [autocommit mode].
-** ^In other words, schema S must not currently be in
-** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
-** database connection D must be out of [autocommit mode].
-** ^A [snapshot] will fail to open if it has been overwritten by a
-** [checkpoint].
+** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read
+** transaction or upgrades an existing one for schema S of
+** [database connection] D such that the read transaction refers to
+** historical [snapshot] P, rather than the most recent change to the
+** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK
+** on success or an appropriate [error code] if it fails.
+**
+** ^In order to succeed, the database connection must not be in
+** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there
+** is already a read transaction open on schema S, then the database handle
+** must have no active statements (SELECT statements that have been passed
+** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()).
+** SQLITE_ERROR is returned if either of these conditions is violated, or
+** if schema S does not exist, or if the snapshot object is invalid.
+**
+** ^A call to sqlite3_snapshot_open() will fail to open if the specified
+** snapshot has been overwritten by a [checkpoint]. In this case
+** SQLITE_BUSY_SNAPSHOT is returned.
+**
+** If there is already a read transaction open when this function is
+** invoked, then the same read transaction remains open (on the same
+** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_BUSY_SNAPSHOT
+** is returned. If another error code - for example SQLITE_PROTOCOL or an
+** SQLITE_IOERR error code - is returned, then the final state of the
+** read transaction is undefined. If SQLITE_OK is returned, then the
+** read transaction is now open on database snapshot P.
+**
** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for
** schema S is in [WAL mode]. A database connection might not know
if( rc!=SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
return TCL_ERROR;
+ }else{
+ Tcl_ResetResult(interp);
}
return TCL_OK;
}
if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1;
return 0;
}
+
+/*
+** The caller currently has a read transaction open on the database.
+** This function takes a SHARED lock on the CHECKPOINTER slot and then
+** checks if the snapshot passed as the second argument is still
+** available. If so, SQLITE_OK is returned.
+**
+** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
+** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
+** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
+** lock is released before returning.
+*/
+int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){
+ int rc;
+ rc = walLockShared(pWal, WAL_CKPT_LOCK);
+ if( rc==SQLITE_OK ){
+ WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot;
+ if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
+ || pNew->mxFrame<walCkptInfo(pWal)->nBackfillAttempted
+ ){
+ rc = SQLITE_BUSY_SNAPSHOT;
+ walUnlockShared(pWal, WAL_CKPT_LOCK);
+ }
+ }
+ return rc;
+}
+
+/*
+** Release a lock obtained by an earlier successful call to
+** sqlite3WalSnapshotCheck().
+*/
+void sqlite3WalSnapshotUnlock(Wal *pWal){
+ assert( pWal );
+ walUnlockShared(pWal, WAL_CKPT_LOCK);
+}
+
+
#endif /* SQLITE_ENABLE_SNAPSHOT */
#ifdef SQLITE_ENABLE_ZIPVFS
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
int sqlite3WalSnapshotRecover(Wal *pWal);
+int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot);
+void sqlite3WalSnapshotUnlock(Wal *pWal);
#endif
#ifdef SQLITE_ENABLE_ZIPVFS
SELECT * FROM t2;
}
} {a b c d e f}
- do_test $tn.3.2.2 {
- list [catch {snapshot_open db main $snapshot } msg] $msg
+
+ # Update - it is no longer an error to have a read-transaction open,
+ # provided there are no active SELECT statements.
+ do_test $tn.3.2.2a {
+ db eval "SELECT * FROM t2" {
+ set res [list [catch {snapshot_open db main $snapshot } msg] $msg]
+ break
+ }
+ set res
} {1 SQLITE_ERROR}
+ do_test $tn.3.2.2b {
+ snapshot_open db main $snapshot
+ } {}
do_test $tn.3.2.3 {
execsql {
} {1 SQLITE_ERROR}
do_execsql_test $tn.3.2.4 COMMIT
- do_test $tn.3.3.1 {
+ do_test $tn.3.3.1a {
execsql { PRAGMA journal_mode = DELETE }
execsql { BEGIN }
list [catch {snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}
+ do_test $tn.3.3.1b {
+ execsql { COMMIT ; BEGIN ; SELECT * FROM t2 }
+ list [catch {snapshot_open db main $snapshot } msg] $msg
+ } {1 SQLITE_ERROR}
+
do_test $tn.$tn.3.3.2 {
snapshot_free $snapshot
execsql COMMIT
--- /dev/null
+# 2018 August 6
+#
+# 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.
+#
+#***********************************************************************
+#
+# Tests for calling sqlite3_snapshot_open() when there is already
+# a read transaction open on the database.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+ifcapable !snapshot {finish_test; return}
+set testprefix snapshot_up
+
+# This test does not work with the inmemory_journal permutation. The reason
+# is that each connection opened as part of this permutation executes
+# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
+# and there are one or more existing connections.
+if {[permutation]=="inmemory_journal"} {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a, b, c);
+ PRAGMA journal_mode = wal;
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+ INSERT INTO t1 VALUES(7, 8, 9);
+} {wal}
+
+do_test 1.1 {
+ execsql BEGIN
+ set ::snap1 [sqlite3_snapshot_get db main]
+ execsql COMMIT
+ execsql { INSERT INTO t1 VALUES(10, 11, 12); }
+ execsql BEGIN
+ set ::snap2 [sqlite3_snapshot_get db main]
+ execsql COMMIT
+ execsql { INSERT INTO t1 VALUES(13, 14, 15); }
+ execsql BEGIN
+ set ::snap3 [sqlite3_snapshot_get db main]
+ execsql COMMIT
+} {}
+
+do_execsql_test 1.2 {
+ BEGIN;
+ SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15}
+
+do_test 1.3 {
+ sqlite3_snapshot_open db main $::snap1
+ execsql { SELECT * FROM t1 }
+} {1 2 3 4 5 6 7 8 9}
+
+do_test 1.4 {
+ sqlite3_snapshot_open db main $::snap2
+ execsql { SELECT * FROM t1 }
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 1.5 {
+ sqlite3 db2 test.db
+ execsql { PRAGMA wal_checkpoint } db2
+} {0 5 4}
+
+do_execsql_test 1.6 {
+ SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 1.7 {
+ list [catch { sqlite3_snapshot_open db main $::snap1 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+do_execsql_test 1.8 {
+ SELECT * FROM t1
+} {1 2 3 4 5 6 7 8 9 10 11 12}
+
+do_test 1.9 {
+ execsql { COMMIT ; BEGIN }
+ list [catch { sqlite3_snapshot_open db main $::snap1 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+
+do_test 1.10 {
+ execsql { COMMIT }
+ execsql {
+ PRAGMA wal_checkpoint;
+ DELETE FROM t1 WHERE a = 1;
+ } db2
+ execsql BEGIN
+ set ::snap4 [sqlite3_snapshot_get db main]
+ execsql COMMIT
+ execsql {
+ DELETE FROM t1 WHERE a = 4;
+ } db2
+} {}
+
+do_test 1.11 {
+ execsql {
+ BEGIN;
+ SELECT * FROM t1
+ }
+} {7 8 9 10 11 12 13 14 15}
+do_test 1.12 {
+ sqlite3_snapshot_open db main $::snap4
+ execsql { SELECT * FROM t1 }
+} {4 5 6 7 8 9 10 11 12 13 14 15}
+
+do_test 1.13 {
+ list [catch { sqlite3_snapshot_open db main $::snap3 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+do_test 1.14 {
+ execsql { SELECT * FROM t1 }
+} {4 5 6 7 8 9 10 11 12 13 14 15}
+
+db close
+db2 close
+sqlite3 db test.db
+do_execsql_test 1.15 {
+ BEGIN;
+ SELECT * FROM t1
+} {7 8 9 10 11 12 13 14 15}
+do_test 1.16 {
+ list [catch { sqlite3_snapshot_open db main $::snap4 } msg] $msg
+} {1 SQLITE_BUSY_SNAPSHOT}
+do_execsql_test 1.17 { COMMIT }
+
+sqlite3_snapshot_free $::snap1
+sqlite3_snapshot_free $::snap2
+sqlite3_snapshot_free $::snap3
+sqlite3_snapshot_free $::snap4
+
+#-------------------------------------------------------------------------
+catch { db close }
+sqlite3 db test.db
+sqlite3 db2 test.db
+sqlite3 db3 test.db
+
+proc xBusy {args} { return 1 }
+db3 busy xBusy
+
+do_test 2.1 {
+ execsql { INSERT INTO t1 VALUES(16, 17, 18) } db2
+ execsql BEGIN
+ set ::snap1 [sqlite3_snapshot_get db main]
+ execsql COMMIT
+ execsql { INSERT INTO t1 VALUES(19, 20, 21) } db2
+ execsql BEGIN
+ set ::snap2 [sqlite3_snapshot_get db main]
+ execsql COMMIT
+ set {} {}
+} {}
+
+do_execsql_test -db db2 2.2 {
+ BEGIN;
+ INSERT INTO t1 VALUES(19, 20, 21);
+}
+
+do_test 2.3 {
+ execsql BEGIN
+ sqlite3_snapshot_open db main $::snap1
+ execsql { SELECT * FROM t1 }
+} {7 8 9 10 11 12 13 14 15 16 17 18}
+
+proc xBusy {args} {
+ set ::res [list [catch { sqlite3_snapshot_open db main $::snap2 } msg] $msg]
+ return 1
+}
+db3 busy xBusy
+do_test 2.4 {
+ execsql {PRAGMA wal_checkpoint = restart} db3
+ set ::res
+} {1 SQLITE_BUSY}
+
+sqlite3_snapshot_free $::snap1
+sqlite3_snapshot_free $::snap2
+
+finish_test
+