Long-lived background task that runs PHPStan on pending files. Spawned once during `initialized`, alongside the main diagnostic worker. This task is completely independent: native diagnostics (phases 1 and 2) are never blocked by PHPStan. ## Serialization guarantee At most one PHPStan process runs at a time. The worker loop: 1. Wait for a notification (new edit arrived). 2. Debounce: sleep [
(&self)
| 1110 | /// notification and loops back to step 1, starting a fresh run |
| 1111 | /// with the latest content. |
| 1112 | pub(crate) async fn phpstan_worker(&self) { |
| 1113 | loop { |
| 1114 | if self.shutdown_flag.load(Ordering::Acquire) { |
| 1115 | return; |
| 1116 | } |
| 1117 | |
| 1118 | // ── Step 1: wait for work ─────────────────────────────── |
| 1119 | self.phpstan_notify.notified().await; |
| 1120 | |
| 1121 | if self.shutdown_flag.load(Ordering::Acquire) { |
| 1122 | return; |
| 1123 | } |
| 1124 | |
| 1125 | // Drain any extra stored permits so that notifications |
| 1126 | // that arrived between the last run finishing and this |
| 1127 | // `notified()` call don't cause an immediate second run. |
| 1128 | // `Notify::notify_one()` stores at most one permit, but |
| 1129 | // multiple `schedule_phpstan` calls during debounce or |
| 1130 | // execution could leave one behind. |
| 1131 | // |
| 1132 | // We consume it by polling a fresh `notified()` with a |
| 1133 | // zero timeout — if there's a stored permit it resolves |
| 1134 | // immediately, otherwise it times out harmlessly. |
| 1135 | let _ = tokio::time::timeout(std::time::Duration::ZERO, self.phpstan_notify.notified()) |
| 1136 | .await; |
| 1137 | |
| 1138 | // ── Step 2: debounce (longer than normal diagnostics) ─── |
| 1139 | loop { |
| 1140 | let version_before = self.diag_version.load(Ordering::Acquire); |
| 1141 | tokio::time::sleep(std::time::Duration::from_millis(PHPSTAN_DEBOUNCE_MS)).await; |
| 1142 | let version_after = self.diag_version.load(Ordering::Acquire); |
| 1143 | if version_before == version_after { |
| 1144 | break; |
| 1145 | } |
| 1146 | // More edits arrived — loop and debounce again. |
| 1147 | } |
| 1148 | |
| 1149 | // ── Step 3: snapshot the pending URI ──────────────────── |
| 1150 | let uri = { |
| 1151 | let mut pending = self.phpstan_pending_uri.lock(); |
| 1152 | pending.take() |
| 1153 | }; |
| 1154 | let uri = match uri { |
| 1155 | Some(u) => u, |
| 1156 | None => continue, |
| 1157 | }; |
| 1158 | |
| 1159 | // Snapshot the file content. |
| 1160 | let content = { |
| 1161 | let files = self.open_files.read(); |
| 1162 | match files.get(&uri) { |
| 1163 | Some(c) => c.clone(), |
| 1164 | None => continue, |
| 1165 | } |
| 1166 | }; |
| 1167 | |
| 1168 | // ── Step 4: resolve PHPStan binary ────────────────────── |
| 1169 | let config = self.config(); |
no test coverage detected