PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 3.0.0
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v3.0.0
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | includes/class-backup-manager.php +67 -19 2.11.63.0.0 View file →
@@ -46,8 +46,15 @@
46 46 */
47 47 const COPIES_SWEEP_BATCH = 50;
48 48
49 49 /**
50 + * Lock that keeps two requests from walking the network at once
51 + *
52 + * @since 2.11.8
53 + */
54 + const COPIES_SWEEP_LOCK = 'vigilante_config_copies_sweep_lock';
55 +
56 + /**
50 57 * Legacy on-disk backup directory (kept only to clean it up on upgrade).
51 58 *
52 59 * @var string
53 60 */
@@ -137,8 +144,9 @@
137 144 }
138 145
139 146 if ( $state < 1 ) {
140 147 self::purge_stored_copies();
148 + update_option( self::COPIES_PURGED_OPTION, 1, true );
141 149 }
142 150
143 151 if ( ! is_multisite() ) {
144 152 update_option( self::COPIES_PURGED_OPTION, 2, true );
@@ -144,34 +152,74 @@
144 152 update_option( self::COPIES_PURGED_OPTION, 2, true );
145 153 return;
146 154 }
147 155
148 - $sweep = get_site_option( self::COPIES_SWEEP_OPTION, 0 );
156 + if ( 'done' !== get_site_option( self::COPIES_SWEEP_OPTION, 0 ) ) {
157 + /*
158 + * One request walks at a time. Right after an update every request
159 + * gets here, and until 2.11.8 each of them repeated the same batch of
160 + * sites. The lock lives in the options table of the main site, which
161 + * every site of the network reaches the same way.
162 + */
163 + switch_to_blog( get_main_site_id() );
164 + $locked = Vigilante_Settings::acquire_option_lock( self::COPIES_SWEEP_LOCK, MINUTE_IN_SECONDS );
165 + restore_current_blog();
149 166
150 - if ( 'done' !== $sweep ) {
151 - global $wpdb;
167 + if ( ! $locked ) {
168 + return;
169 + }
152 170
153 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- the walk needs the sites after the last one it cleaned, in id order, and get_sites() cannot ask for ids greater than a value.
154 - $site_ids = $wpdb->get_col(
155 - $wpdb->prepare(
156 - "SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = %d AND blog_id > %d ORDER BY blog_id ASC LIMIT %d",
157 - get_current_network_id(),
158 - (int) $sweep,
159 - self::COPIES_SWEEP_BATCH
160 - )
161 - );
162 -
163 - foreach ( $site_ids as $site_id ) {
164 - switch_to_blog( (int) $site_id );
165 - self::purge_stored_copies();
171 + try {
172 + self::sweep_next_batch();
173 + } finally {
174 + switch_to_blog( get_main_site_id() );
175 + Vigilante_Settings::release_option_lock( self::COPIES_SWEEP_LOCK );
166 176 restore_current_blog();
167 177 }
178 + }
168 179
169 - $sweep = count( $site_ids ) < self::COPIES_SWEEP_BATCH ? 'done' : (int) end( $site_ids );
170 - update_site_option( self::COPIES_SWEEP_OPTION, $sweep );
180 + if ( 'done' === get_site_option( self::COPIES_SWEEP_OPTION, 0 ) ) {
181 + update_option( self::COPIES_PURGED_OPTION, 2, true );
171 182 }
183 + }
172 184
173 - update_option( self::COPIES_PURGED_OPTION, 'done' === $sweep ? 2 : 1, true );
185 + /**
186 + * Clean the next batch of sites of the network, with the walk lock held
187 + *
188 + * @since 2.11.8
189 + */
190 + private static function sweep_next_batch() {
191 + global $wpdb;
192 +
193 + // Read again inside the lock: the request that held it before may have
194 + // moved the walk on.
195 + // The core caches network options under "$network_id:$option", and absent
196 + // ones in "$network_id:notoptions" (wp-includes/option.php:2091 and :2065).
197 + wp_cache_delete( get_current_network_id() . ':' . self::COPIES_SWEEP_OPTION, 'site-options' );
198 + wp_cache_delete( get_current_network_id() . ':notoptions', 'site-options' );
199 + $sweep = get_site_option( self::COPIES_SWEEP_OPTION, 0 );
200 +
201 + if ( 'done' === $sweep ) {
202 + return;
203 + }
204 +
205 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- the walk needs the sites after the last one it cleaned, in id order, and get_sites() cannot ask for ids greater than a value.
206 + $site_ids = $wpdb->get_col(
207 + $wpdb->prepare(
208 + "SELECT blog_id FROM {$wpdb->blogs} WHERE site_id = %d AND blog_id > %d ORDER BY blog_id ASC LIMIT %d",
209 + get_current_network_id(),
210 + (int) $sweep,
211 + self::COPIES_SWEEP_BATCH
212 + )
213 + );
214 +
215 + foreach ( $site_ids as $site_id ) {
216 + switch_to_blog( (int) $site_id );
217 + self::purge_stored_copies();
218 + restore_current_blog();
219 + }
220 +
221 + update_site_option( self::COPIES_SWEEP_OPTION, count( $site_ids ) < self::COPIES_SWEEP_BATCH ? 'done' : (int) end( $site_ids ) );
174 222 }
175 223
176 224 /**
177 225 * Get the legacy on-disk backup directory path.