Skip to content

Commit 61706e4

Browse files
committed
1.3.6 Retry cache update timeouts three times, change new-user cache size default to 16MiB.
1 parent c83e9ab commit 61706e4

File tree

4 files changed

+103
-64
lines changed

4 files changed

+103
-64
lines changed

assets/drop-in/object-cache.php

+94-59
Original file line numberDiff line numberDiff line change
@@ -575,9 +575,8 @@ private function open_connection() {
575575
if ( $this->sqlite ) {
576576
return;
577577
}
578-
$max_retries = 3;
579-
$retries = 0;
580-
while ( ++ $retries <= $max_retries ) {
578+
$retries = 3;
579+
while ( $retries -- > 0 ) {
581580
try {
582581
$this->actual_open_connection();
583582

@@ -950,7 +949,7 @@ public function sqlite_remove_expired() {
950949
$items_removed += $hit;
951950
}
952951
} catch ( Exception $ex ) {
953-
$this->error_log( 'sqlite_clean_up_cache', $ex );
952+
$this->error_log( 'sqlite_remove_expired', $ex );
954953
}
955954

956955
return $items_removed > 0;
@@ -1396,7 +1395,7 @@ private function get_by_name( $name ) {
13961395
$result->finalize();
13971396
} catch ( Exception $ex ) {
13981397
unset( $this->not_in_persistent_cache [ $name ] );
1399-
$this->error_log( 'getone', $ex );
1398+
$this->error_log( 'get_by_name', $ex );
14001399
$this->delete_offending_files();
14011400
self::drop_dead();
14021401
}
@@ -1451,7 +1450,7 @@ public function set( $key, $data, $group = 'default', $expire = 0 ) {
14511450
}
14521451

14531452
/**
1454-
* Write to the persistent cache.
1453+
* Write to the persistent cache, with timeout retry.
14551454
*
14561455
* @param string $name What to call the contents in the cache.
14571456
* @param mixed $data The contents to store in the cache.
@@ -1460,51 +1459,76 @@ public function set( $key, $data, $group = 'default', $expire = 0 ) {
14601459
* @return void
14611460
*/
14621461
private function put_by_name( $name, $data, $expire ) {
1463-
try {
1464-
$start = $this->time_usec();
1465-
$value = $this->maybe_serialize( $data );
1466-
$expires = $expire ?: $this->noexpire_timestamp_offset;
1467-
if ( $this->upsertone ) {
1468-
$stmt = $this->upsertone;
1469-
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1470-
$stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1471-
$stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1472-
$result = $stmt->execute();
1473-
$result->finalize();
1474-
} else {
1475-
/* Pre-upsert version (pre- 3.24) of SQLite,
1476-
* Need to try update, then do insert if need be.
1477-
* Race conditions are possible, hence BEGIN / COMMIT
1478-
*/
1479-
if ( ! $this->transaction_active ) {
1480-
$this->sqlite->exec( 'BEGIN' );
1462+
$exception = null;
1463+
$start = $this->time_usec();
1464+
$value = $this->maybe_serialize( $data );
1465+
$expires = $expire ?: $this->noexpire_timestamp_offset;
1466+
$retries = 3;
1467+
while ( $retries -- > 0 ) {
1468+
try {
1469+
$this->actual_put_by_name( $name, $value, $expires );
1470+
unset( $this->not_in_persistent_cache[ $name ] );
1471+
$this->insert_times[] = $this->time_usec() - $start;
1472+
1473+
return;
1474+
} catch ( Exception $ex ) {
1475+
$exception = $ex;
1476+
if ( ! str_contains( $ex->getMessage(), 'database is locked' ) || 5 !== $this->sqlite->lastErrorCode() ) {
1477+
break;
14811478
}
1482-
$stmt = $this->updateone;
1479+
}
1480+
sleep( 1 );
1481+
}
1482+
if ( $exception ) {
1483+
$this->error_log( 'put_by_name', $exception );
1484+
$this->delete_offending_files();
1485+
self::drop_dead();
1486+
}
1487+
}
1488+
1489+
/**
1490+
* Actually write to the cache.
1491+
*
1492+
* @param string $name What to call the contents in the cache.
1493+
* @param string $value The seriolized value.
1494+
* @param int $expires Expiration time.
1495+
*
1496+
* @return void
1497+
*/
1498+
private function actuaL_put_by_name( $name, $value, $expires ) {
1499+
if ( $this->upsertone ) {
1500+
$stmt = $this->upsertone;
1501+
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1502+
$stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1503+
$stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1504+
$result = $stmt->execute();
1505+
$result->finalize();
1506+
} else {
1507+
/* Pre-upsert version (pre- 3.24) of SQLite,
1508+
* Need to try update, then do insert if need be.
1509+
* Race conditions are possible, hence BEGIN / COMMIT
1510+
*/
1511+
if ( ! $this->transaction_active ) {
1512+
$this->sqlite->exec( 'BEGIN' );
1513+
}
1514+
$stmt = $this->updateone;
1515+
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1516+
$stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1517+
$stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1518+
$result = $stmt->execute();
1519+
$result->finalize();
1520+
if ( 0 === $this->sqlite->changes() ) {
1521+
/* Updated zero rows, so we need an insert. */
1522+
$stmt = $this->insertone;
14831523
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
14841524
$stmt->bindValue( ':value', $value, SQLITE3_BLOB );
14851525
$stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
14861526
$result = $stmt->execute();
14871527
$result->finalize();
1488-
if ( 0 === $this->sqlite->changes() ) {
1489-
/* Updated zero rows, so we need an insert. */
1490-
$stmt = $this->insertone;
1491-
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1492-
$stmt->bindValue( ':value', $value, SQLITE3_BLOB );
1493-
$stmt->bindValue( ':expires', $expires, SQLITE3_INTEGER );
1494-
$result = $stmt->execute();
1495-
$result->finalize();
1496-
}
1497-
if ( ! $this->transaction_active ) {
1498-
$this->sqlite->exec( 'COMMIT' );
1499-
}
15001528
}
1501-
unset( $this->not_in_persistent_cache[ $name ] );
1502-
/* track how long it took. */
1503-
$this->insert_times[] = $this->time_usec() - $start;
1504-
} catch ( Exception $ex ) {
1505-
$this->error_log( 'handle_put', $ex );
1506-
$this->delete_offending_files();
1507-
self::drop_dead();
1529+
if ( ! $this->transaction_active ) {
1530+
$this->sqlite->exec( 'COMMIT' );
1531+
}
15081532
}
15091533
}
15101534

@@ -1887,7 +1911,6 @@ public function delete( $key, $group = 'default', $deprecated = false ) {
18871911
$name = $this->normalize_name( $key, $group );
18881912
unset ( $this->cache[ $name ] );
18891913
$this->delete_by_name( $name );
1890-
$this->not_in_persistent_cache[ $name ] = true;
18911914

18921915
return true;
18931916
}
@@ -1945,7 +1968,7 @@ public function sqlite_delete_old( $target_size, $current_size ) {
19451968
}
19461969
$this->checkpoint();
19471970
} catch ( Exception $ex ) {
1948-
$this->delete_offending_files();
1971+
/* Empty, intenionally. */
19491972
}
19501973
}
19511974

@@ -1957,18 +1980,32 @@ public function sqlite_delete_old( $target_size, $current_size ) {
19571980
* @return void
19581981
*/
19591982
private function delete_by_name( $name ) {
1960-
$start = $this->time_usec();
1961-
try {
1962-
$this->not_in_persistent_cache[ $name ] = true;
1963-
$stmt = $this->deleteone;
1964-
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1965-
$result = $stmt->execute();
1966-
$result->finalize();
1967-
} catch ( Exception $ex ) {
1983+
$exception = null;
1984+
$retries = 3;
1985+
$stmt = $this->deleteone;
1986+
$this->not_in_persistent_cache[ $name ] = true;
1987+
$start = $this->time_usec();
1988+
while ( $retries -- > 0 ) {
1989+
try {
1990+
$stmt->bindValue( ':name', $name, SQLITE3_TEXT );
1991+
$result = $stmt->execute();
1992+
$result->finalize();
1993+
$this->delete_times[] = $this->time_usec() - $start;
1994+
1995+
return;
1996+
} catch ( Exception $ex ) {
1997+
$exception = $ex;
1998+
if ( ! str_contains( $ex->getMessage(), 'database is locked' ) || 5 !== $this->sqlite->lastErrorCode() ) {
1999+
break;
2000+
}
2001+
}
2002+
sleep( 1 );
2003+
}
2004+
if ( $exception ) {
2005+
$this->error_log( 'delete_by_name', $exception );
19682006
$this->delete_offending_files();
2007+
self::drop_dead();
19692008
}
1970-
/* track how long it took. */
1971-
$this->delete_times[] = $this->time_usec() - $start;
19722009
}
19732010

19742011
/**
@@ -2066,9 +2103,8 @@ public function flush( $vacuum = false ) {
20662103
$this->sqlite->exec( 'VACUUM;' );
20672104
}
20682105
} catch ( Exception $ex ) {
2069-
$this->error_log( 'flush', $ex );
2106+
$this->error_log( 'flush failure, recreate cache.', $ex );
20702107
$this->delete_offending_files();
2071-
self::drop_dead();
20722108
}
20732109

20742110
return true;
@@ -2117,7 +2153,6 @@ public function flush_group( $group ) {
21172153
} catch ( Exception $ex ) {
21182154
$this->error_log( 'flush_group', $ex );
21192155
$this->delete_offending_files();
2120-
self::drop_dead();
21212156
}
21222157
/* remove hints about what is in the persistent cache */
21232158
$this->not_in_persistent_cache = array();

includes/class-sqlite-object-cache-settings.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ private function settings_fields() {
116116
'label' => __( 'Cached data size', 'sqlite-object-cache' ),
117117
'description' => __( 'MiB. When data in the cache grows larger than this, hourly cleanup removes the oldest entries.', 'sqlite-object-cache' ),
118118
'type' => 'number',
119-
'default' => 4,
120-
'min' => 0.5,
119+
'default' => 16,
120+
'min' => 1,
121121
'step' => 'any',
122122
'cssclass' => 'narrow',
123123
'placeholder' => __( 'MiB.', 'sqlite-object-cache' ),

includes/class-sqlite-object-cache.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public function load_plugin_textdomain() {
190190
*/
191191
public function clean_job( $grace_factor = 1.0 ) {
192192
$option = get_option( $this->_token . '_settings', array() );
193-
$target_size = empty ( $option['target_size'] ) ? 4 : $option['target_size'];
193+
$target_size = empty ( $option['target_size'] ) ? 16 : $option['target_size'];
194194
$target_size *= ( 1024 * 1024 );
195195
$threshold_size = (int) ( $target_size * $grace_factor );
196196

readme.txt

+6-2
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,15 @@ causes your object cache data to go into the `/tmp` folder in a file named `mysi
147147

148148
`define( 'WP_SQLITE_OBJECT_CACHE_MMAP_SIZE', 32 );`
149149

150+
Notice that using memory-mapped I/O may not help performance in the highly concurrent environment of a busy web server.
151+
150152
= I sometimes get timeout errors from SQLite. How can I fix them? =
151153

152154
Some sites occasionally generate error messages looking like this one:
153155

154156
`Unable to execute statement: database is locked in /var/www/wp-content/object-cache.php:1234`
155157

156-
This can happen if your server places your WordPress files on network-attached storage (that is, on a network drive). To solve this, store your cached data on a locally attached drive. See the question about storing your data in a more secure place.
158+
This can happen if your server places your WordPress files on network-attached storage (that is, on a network drive). To solve this, store your cached data on a locally attached drive. See the question about storing your data in a more secure place. It also can happen in a very busy site.
157159

158160
= Why do I get errors when I use WP-CLI to administer my site? =
159161

@@ -191,6 +193,8 @@ Please look for more questions and answers [here](https://www.plumislandmedia.ne
191193

192194
* Clean up in chunks in an attempt to reduce contention delays and timeouts.
193195
* Do PRAGMA wal_checkpoint(RESTART) when cleaning up, and also occasionally, to prevent the write-ahead log from growing without bound on busy systems.
196+
* Retry three times if cache updates time out.
197+
* Increase default cache size to 16MiB for new users.
194198

195199
= 1.3.5 =
196200

@@ -210,6 +214,6 @@ Please look for more questions and answers [here](https://www.plumislandmedia.ne
210214

211215
== Upgrade Notice ==
212216

213-
This release attempts to reduce cache timeouts by doing cleanup operations in chunks. It also does PRAGMA wal_checkpoint(RESTART) when cleaning up, and also occasionally, to prevent the write-ahead log from growing without bound on busy systems.
217+
This release attempts to reduce cache timeouts by doing cleanup operations in chunks, and by retrying timed-out cache update operations. It also does PRAGMA wal_checkpoint(RESTART) when cleaning up, and also occasionally, to prevent the write-ahead log from growing without bound on busy systems.
214218

215219
Thanks, dear users, especially @bourgesloic, @spacedmonkey, @spaceling and @ss88_uk, for letting me know about errors you found, and for your patience as I figure this out. All remaining errors are solely the responsibility of the author.

0 commit comments

Comments
 (0)