Fossil SCM
Improved detection of potential read/write txn conflicts. Hold a write transaction on the server-side for the duration of an /xfer request, to avoid unexpected SQLITE_BUSY errors.
Commit
d9543f4c2ca2f6ad356de58e8287d2c643787e1f32d957d15d26dc2a41e8158e
Parent
a4c1b36ee6e0d87…
2 files changed
+6
-3
+2
-2
M
src/db.c
+6
-3
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -116,10 +116,11 @@ | ||
| 116 | 116 | */ |
| 117 | 117 | static struct DbLocalData { |
| 118 | 118 | int nBegin; /* Nesting depth of BEGIN */ |
| 119 | 119 | int doRollback; /* True to force a rollback */ |
| 120 | 120 | int nCommitHook; /* Number of commit hooks */ |
| 121 | + int wrTxn; /* Outer-most TNX is a write */ | |
| 121 | 122 | Stmt *pAllStmt; /* List of all unfinalized statements */ |
| 122 | 123 | int nPrepare; /* Number of calls to sqlite3_prepare_v2() */ |
| 123 | 124 | int nDeleteOnFail; /* Number of entries in azDeleteOnFail[] */ |
| 124 | 125 | struct sCommitHook { |
| 125 | 126 | int (*xHook)(void); /* Functions to call at db_end_transaction() */ |
| @@ -195,10 +196,11 @@ | ||
| 195 | 196 | sqlite3_commit_hook(g.db, db_verify_at_commit, 0); |
| 196 | 197 | db.nPriorChanges = sqlite3_total_changes(g.db); |
| 197 | 198 | db.doRollback = 0; |
| 198 | 199 | db.zStartFile = zStartFile; |
| 199 | 200 | db.iStartLine = iStartLine; |
| 201 | + db.wrTxn = 0; | |
| 200 | 202 | } |
| 201 | 203 | db.nBegin++; |
| 202 | 204 | } |
| 203 | 205 | /* |
| 204 | 206 | ** Begin a new transaction for writing. |
| @@ -209,11 +211,12 @@ | ||
| 209 | 211 | sqlite3_commit_hook(g.db, db_verify_at_commit, 0); |
| 210 | 212 | db.nPriorChanges = sqlite3_total_changes(g.db); |
| 211 | 213 | db.doRollback = 0; |
| 212 | 214 | db.zStartFile = zStartFile; |
| 213 | 215 | db.iStartLine = iStartLine; |
| 214 | - }else{ | |
| 216 | + db.wrTxn = 1; | |
| 217 | + }else if( !db.wrTxn ){ | |
| 215 | 218 | fossil_warning("read txn at %s:%d might cause SQLITE_BUSY " |
| 216 | 219 | "for the write txn at %s:%d", |
| 217 | 220 | db.zStartFile, db.iStartLine, zStartFile, iStartLine); |
| 218 | 221 | } |
| 219 | 222 | db.nBegin++; |
| @@ -1328,11 +1331,11 @@ | ||
| 1328 | 1331 | ); |
| 1329 | 1332 | if( rc!=SQLITE_OK ){ |
| 1330 | 1333 | db_err("[%s]: %s", zDbName, sqlite3_errmsg(db)); |
| 1331 | 1334 | } |
| 1332 | 1335 | db_maybe_set_encryption_key(db, zDbName); |
| 1333 | - sqlite3_busy_timeout(db, 5000); | |
| 1336 | + sqlite3_busy_timeout(db, 15000); | |
| 1334 | 1337 | sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */ |
| 1335 | 1338 | sqlite3_create_function(db, "user", 0, SQLITE_UTF8, 0, db_sql_user, 0, 0); |
| 1336 | 1339 | sqlite3_create_function(db, "cgi", 1, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); |
| 1337 | 1340 | sqlite3_create_function(db, "cgi", 2, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); |
| 1338 | 1341 | sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0); |
| @@ -1344,11 +1347,11 @@ | ||
| 1344 | 1347 | ); |
| 1345 | 1348 | if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0); |
| 1346 | 1349 | db_add_aux_functions(db); |
| 1347 | 1350 | re_add_sql_func(db); /* The REGEXP operator */ |
| 1348 | 1351 | foci_register(db); /* The "files_of_checkin" virtual table */ |
| 1349 | - sqlite3_exec(db, "PRAGMA foreign_keys=OFF;", 0, 0, 0); | |
| 1352 | + sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc); | |
| 1350 | 1353 | return db; |
| 1351 | 1354 | } |
| 1352 | 1355 | |
| 1353 | 1356 | |
| 1354 | 1357 | /* |
| 1355 | 1358 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -116,10 +116,11 @@ | |
| 116 | */ |
| 117 | static struct DbLocalData { |
| 118 | int nBegin; /* Nesting depth of BEGIN */ |
| 119 | int doRollback; /* True to force a rollback */ |
| 120 | int nCommitHook; /* Number of commit hooks */ |
| 121 | Stmt *pAllStmt; /* List of all unfinalized statements */ |
| 122 | int nPrepare; /* Number of calls to sqlite3_prepare_v2() */ |
| 123 | int nDeleteOnFail; /* Number of entries in azDeleteOnFail[] */ |
| 124 | struct sCommitHook { |
| 125 | int (*xHook)(void); /* Functions to call at db_end_transaction() */ |
| @@ -195,10 +196,11 @@ | |
| 195 | sqlite3_commit_hook(g.db, db_verify_at_commit, 0); |
| 196 | db.nPriorChanges = sqlite3_total_changes(g.db); |
| 197 | db.doRollback = 0; |
| 198 | db.zStartFile = zStartFile; |
| 199 | db.iStartLine = iStartLine; |
| 200 | } |
| 201 | db.nBegin++; |
| 202 | } |
| 203 | /* |
| 204 | ** Begin a new transaction for writing. |
| @@ -209,11 +211,12 @@ | |
| 209 | sqlite3_commit_hook(g.db, db_verify_at_commit, 0); |
| 210 | db.nPriorChanges = sqlite3_total_changes(g.db); |
| 211 | db.doRollback = 0; |
| 212 | db.zStartFile = zStartFile; |
| 213 | db.iStartLine = iStartLine; |
| 214 | }else{ |
| 215 | fossil_warning("read txn at %s:%d might cause SQLITE_BUSY " |
| 216 | "for the write txn at %s:%d", |
| 217 | db.zStartFile, db.iStartLine, zStartFile, iStartLine); |
| 218 | } |
| 219 | db.nBegin++; |
| @@ -1328,11 +1331,11 @@ | |
| 1328 | ); |
| 1329 | if( rc!=SQLITE_OK ){ |
| 1330 | db_err("[%s]: %s", zDbName, sqlite3_errmsg(db)); |
| 1331 | } |
| 1332 | db_maybe_set_encryption_key(db, zDbName); |
| 1333 | sqlite3_busy_timeout(db, 5000); |
| 1334 | sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */ |
| 1335 | sqlite3_create_function(db, "user", 0, SQLITE_UTF8, 0, db_sql_user, 0, 0); |
| 1336 | sqlite3_create_function(db, "cgi", 1, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); |
| 1337 | sqlite3_create_function(db, "cgi", 2, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); |
| 1338 | sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0); |
| @@ -1344,11 +1347,11 @@ | |
| 1344 | ); |
| 1345 | if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0); |
| 1346 | db_add_aux_functions(db); |
| 1347 | re_add_sql_func(db); /* The REGEXP operator */ |
| 1348 | foci_register(db); /* The "files_of_checkin" virtual table */ |
| 1349 | sqlite3_exec(db, "PRAGMA foreign_keys=OFF;", 0, 0, 0); |
| 1350 | return db; |
| 1351 | } |
| 1352 | |
| 1353 | |
| 1354 | /* |
| 1355 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -116,10 +116,11 @@ | |
| 116 | */ |
| 117 | static struct DbLocalData { |
| 118 | int nBegin; /* Nesting depth of BEGIN */ |
| 119 | int doRollback; /* True to force a rollback */ |
| 120 | int nCommitHook; /* Number of commit hooks */ |
| 121 | int wrTxn; /* Outer-most TNX is a write */ |
| 122 | Stmt *pAllStmt; /* List of all unfinalized statements */ |
| 123 | int nPrepare; /* Number of calls to sqlite3_prepare_v2() */ |
| 124 | int nDeleteOnFail; /* Number of entries in azDeleteOnFail[] */ |
| 125 | struct sCommitHook { |
| 126 | int (*xHook)(void); /* Functions to call at db_end_transaction() */ |
| @@ -195,10 +196,11 @@ | |
| 196 | sqlite3_commit_hook(g.db, db_verify_at_commit, 0); |
| 197 | db.nPriorChanges = sqlite3_total_changes(g.db); |
| 198 | db.doRollback = 0; |
| 199 | db.zStartFile = zStartFile; |
| 200 | db.iStartLine = iStartLine; |
| 201 | db.wrTxn = 0; |
| 202 | } |
| 203 | db.nBegin++; |
| 204 | } |
| 205 | /* |
| 206 | ** Begin a new transaction for writing. |
| @@ -209,11 +211,12 @@ | |
| 211 | sqlite3_commit_hook(g.db, db_verify_at_commit, 0); |
| 212 | db.nPriorChanges = sqlite3_total_changes(g.db); |
| 213 | db.doRollback = 0; |
| 214 | db.zStartFile = zStartFile; |
| 215 | db.iStartLine = iStartLine; |
| 216 | db.wrTxn = 1; |
| 217 | }else if( !db.wrTxn ){ |
| 218 | fossil_warning("read txn at %s:%d might cause SQLITE_BUSY " |
| 219 | "for the write txn at %s:%d", |
| 220 | db.zStartFile, db.iStartLine, zStartFile, iStartLine); |
| 221 | } |
| 222 | db.nBegin++; |
| @@ -1328,11 +1331,11 @@ | |
| 1331 | ); |
| 1332 | if( rc!=SQLITE_OK ){ |
| 1333 | db_err("[%s]: %s", zDbName, sqlite3_errmsg(db)); |
| 1334 | } |
| 1335 | db_maybe_set_encryption_key(db, zDbName); |
| 1336 | sqlite3_busy_timeout(db, 15000); |
| 1337 | sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */ |
| 1338 | sqlite3_create_function(db, "user", 0, SQLITE_UTF8, 0, db_sql_user, 0, 0); |
| 1339 | sqlite3_create_function(db, "cgi", 1, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); |
| 1340 | sqlite3_create_function(db, "cgi", 2, SQLITE_UTF8, 0, db_sql_cgi, 0, 0); |
| 1341 | sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0); |
| @@ -1344,11 +1347,11 @@ | |
| 1347 | ); |
| 1348 | if( g.fSqlTrace ) sqlite3_trace_v2(db, SQLITE_TRACE_PROFILE, db_sql_trace, 0); |
| 1349 | db_add_aux_functions(db); |
| 1350 | re_add_sql_func(db); /* The REGEXP operator */ |
| 1351 | foci_register(db); /* The "files_of_checkin" virtual table */ |
| 1352 | sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FKEY, 0, &rc); |
| 1353 | return db; |
| 1354 | } |
| 1355 | |
| 1356 | |
| 1357 | /* |
| 1358 |
+2
-2
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -1213,11 +1213,11 @@ | ||
| 1213 | 1213 | xfer.maxTime = db_get_int("max-download-time", 30); |
| 1214 | 1214 | if( xfer.maxTime<1 ) xfer.maxTime = 1; |
| 1215 | 1215 | xfer.maxTime += time(NULL); |
| 1216 | 1216 | g.xferPanic = 1; |
| 1217 | 1217 | |
| 1218 | - db_begin_transaction(); | |
| 1218 | + db_begin_write(); | |
| 1219 | 1219 | db_multi_exec( |
| 1220 | 1220 | "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" |
| 1221 | 1221 | "CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;" |
| 1222 | 1222 | ); |
| 1223 | 1223 | manifest_crosslink_begin(); |
| @@ -1753,11 +1753,11 @@ | ||
| 1753 | 1753 | */ |
| 1754 | 1754 | zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); |
| 1755 | 1755 | @ # timestamp %s(zNow) |
| 1756 | 1756 | free(zNow); |
| 1757 | 1757 | |
| 1758 | - db_end_transaction(0); | |
| 1758 | + db_commit_transaction(); | |
| 1759 | 1759 | configure_rebuild(); |
| 1760 | 1760 | } |
| 1761 | 1761 | |
| 1762 | 1762 | /* |
| 1763 | 1763 | ** COMMAND: test-xfer |
| 1764 | 1764 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1213,11 +1213,11 @@ | |
| 1213 | xfer.maxTime = db_get_int("max-download-time", 30); |
| 1214 | if( xfer.maxTime<1 ) xfer.maxTime = 1; |
| 1215 | xfer.maxTime += time(NULL); |
| 1216 | g.xferPanic = 1; |
| 1217 | |
| 1218 | db_begin_transaction(); |
| 1219 | db_multi_exec( |
| 1220 | "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" |
| 1221 | "CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;" |
| 1222 | ); |
| 1223 | manifest_crosslink_begin(); |
| @@ -1753,11 +1753,11 @@ | |
| 1753 | */ |
| 1754 | zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); |
| 1755 | @ # timestamp %s(zNow) |
| 1756 | free(zNow); |
| 1757 | |
| 1758 | db_end_transaction(0); |
| 1759 | configure_rebuild(); |
| 1760 | } |
| 1761 | |
| 1762 | /* |
| 1763 | ** COMMAND: test-xfer |
| 1764 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1213,11 +1213,11 @@ | |
| 1213 | xfer.maxTime = db_get_int("max-download-time", 30); |
| 1214 | if( xfer.maxTime<1 ) xfer.maxTime = 1; |
| 1215 | xfer.maxTime += time(NULL); |
| 1216 | g.xferPanic = 1; |
| 1217 | |
| 1218 | db_begin_write(); |
| 1219 | db_multi_exec( |
| 1220 | "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" |
| 1221 | "CREATE TEMP TABLE unk(uuid TEXT PRIMARY KEY) WITHOUT ROWID;" |
| 1222 | ); |
| 1223 | manifest_crosslink_begin(); |
| @@ -1753,11 +1753,11 @@ | |
| 1753 | */ |
| 1754 | zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); |
| 1755 | @ # timestamp %s(zNow) |
| 1756 | free(zNow); |
| 1757 | |
| 1758 | db_commit_transaction(); |
| 1759 | configure_rebuild(); |
| 1760 | } |
| 1761 | |
| 1762 | /* |
| 1763 | ** COMMAND: test-xfer |
| 1764 |