Conversation
The crawler lane (.pid file) could stay held forever ("lane in use") after a
run died mid-crawl, blocking cron and Run Manually until someone deleted the
file by hand.
- Acquire the lane atomically with fopen( 'x' ), creating the crawler dir first.
- Release_lane( $force, $expected_owner ): only the owner releases unless forced;
a stale reclaim only deletes the lane it judged stale.
- Release the lane in finally and via a shutdown function, so fatals and
timeouts no longer leak it.
- Mid-run strict check only verifies ownership and never releases the lane.
- Empty, unreadable, >1h old, or >60s future-dated lane files expire.
- WP-CLI `crawler run` force-releases the lane again.
- _touch_lane() refreshes the lane only while this request owns it and never recreates a released lane file. - Short-write cleanup deletes only the file this request created (inode check, expected owner derived from the bytes written). - Release_lane() tolerates a lane that vanished and logs real unlink failures. - Run Manually may take over a lane idle for min( 3600, max( 120, MAP_TIMEOUT + 2 * TIMEOUT + 60 ) ) seconds (300s by default); cron and CLI keep the 1h rule. - _terminate_running() skips saving when another crawler owns the lane, so a displaced crawler cannot overwrite the new owner's summary; exceptions from _engine_start() now terminate with end_reason 'exception'. - "lane in use" debug line now includes the lane path, owner and age.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The crawler lane lock (
wp-content/litespeed/crawler/meta.data.pid) can stay held indefinitely. Every later run, including Run Manually, then stops at⚠️ lane in useand nothing gets crawled. The only way out has been to delete the file by hand or runwp litespeed-crawler run. There are several causes:_engine_start()leaks it.time() - filemtime()is negative.$manually_runis consulted.⚠️ lane in usegives no context, so support cannot tell a live crawler from a dead lock.Changes
Only the lane logic in
src/crawler.cls.phpchanges, plus one line incli/crawler.cls.php.Commit 1: atomic lane, always released
fopen( $lane_file, 'x' ). Thecrawler/directory is created first, because otherwise it is only created later bysave_summary().Release_lane( $force = false, $expected_owner = null )deletes the lane only for its owner, unless forced. A stale reclaim only deletes the lane it actually judged stale.finallyand throughregister_shutdown_function. Both are ownership-checked, so neither can delete a newer crawler's lane._check_valid_lane( true )only verifies ownership and never releases.wp litespeed-crawler runusesRelease_lane( true ), so the CLI force-release keeps working.Commit 2: lifecycle hardening and Run Manually take-over
_touch_lane()rewrites the lane only while this request owns it, and never recreates a released lane file. The oldtouch()could recreate it as an empty file that then blocked the lane.Release_lane()tolerates a lane that vanished and logs genuine unlink failures.min( 3600, max( 120, MAP_TIMEOUT + 2 * TIMEOUT + 60 ) )seconds, which is 300 s with the defaults. Cron and CLI keep the 1 h rule._terminate_running()does not save when another crawler owns the lane, so a displaced crawler cannot overwrite the new owner's summary. Exceptions from_engine_start()now terminate withend_reason = 'exception'.⚠️ lane in usenow logs the lane file path, the owner hash and the age.Testing
Isolated harness. The lane methods were extracted from the real file and each scenario ran in its own child process:
End-to-end on LiteSpeed Enterprise 6.3.7 with cPanel and PHP 8.2 (plugin 7.9.1 with these two files swapped in). Cron runs were triggered with
Task::async_call( 'crawler' ), and Run Manually withcrawler_force:lane in use [file] … [owner] "sLl4paiL" [age] 1swp litespeed-crawler runmid-crawllane_invalid), skips terminating, and does not release the new lanekill -9mid-crawlLane file is stale (age 405s))crawler/dir missingOn that host the crawler worker was killed after about 36 s every run, without any log entry. Before this change, each kill leaked the lane for an hour. With this change the shutdown release frees it, and the next run resumes from
last_pos.Known limitations, not addressed here
fopen( 'x' )is not atomic on NFSv2.Task::async_call()is not bound to its type, so a capturedcrawlerhash can be replayed ascrawler_force.