Skip to content

Commit 28014da

Browse files
committed
Coalesce idle-timer flushes in projectile-cache-current-file
Each call to `projectile-cache-current-file' under persistent caching scheduled a fresh 30-second idle timer that closed over a snapshot of the file list at scheduling time. Opening N files in a session queued N timers; once Emacs went idle they all fired and serialized the cache N times, with the earlier ones writing stale (shorter) lists. Maintain a per-project pending timer in `projectile--pending-cache-flush-timers' instead, cancelling and rescheduling on each new file. The fired callback re-reads the current in-memory cache, so the disk write reflects the final state rather than whichever snapshot the last timer captured.
1 parent d8bbeed commit 28014da

2 files changed

Lines changed: 27 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* Fix `projectile--other-extension-files` sort comparator ignoring its second argument, producing undefined ordering; replaced with a stable partition.
4343
* Fix `projectile-toggle-project-read-only` operating on the wrong buffer after `add-dir-local-variable` by wrapping in `save-selected-window`.
4444
* Fix `projectile-cache-current-file` calling `projectile-project-root` twice instead of reusing the already-resolved value.
45+
* Fix `projectile-cache-current-file` queueing one idle timer per opened file, each capturing a stale snapshot of the file list. With persistent caching, opening many files in a session would result in N redundant disk writes after Emacs went idle. A pending flush is now coalesced per project and reads the latest in-memory cache at fire time.
4546
* Fix `projectile-load-project-cache` not recording a cache time, which combined with `projectile-files-cache-expire` made the TTL check immediately re-evict freshly loaded data — every call ended up re-reading the cache file from disk and the data was never reindexed. The cache file's mtime is now used to seed `projectile-projects-cache-time`.
4647
* Fix `projectile-load-project-cache` storing nil in cache on corrupt/empty cache files, preventing future reload attempts.
4748
* Fix `projectile-purge-dir-from-cache` only updating the in-memory cache; with persistent caching the purged directory's files would reappear on the next session. The on-disk cache is now updated as well, matching the behavior of `projectile-purge-file-from-cache`.

projectile.el

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,11 @@ cache hit requires both DIRCONFIG-PATH and MTIME to match the
685685
current file, so changing `projectile-dirconfig-file' mid-session
686686
naturally invalidates the entry.")
687687

688+
(defvar projectile--pending-cache-flush-timers (make-hash-table :test 'equal)
689+
"Map of project root to a pending idle-timer that will serialize its cache.
690+
Used by `projectile-cache-current-file' to coalesce rapid file additions
691+
into a single delayed disk write per project.")
692+
688693
(defvar projectile--alien-dirconfig-warned-projects (make-hash-table :test 'equal)
689694
"Set of project roots already warned about alien indexing skipping the dirconfig.")
690695

@@ -1273,6 +1278,23 @@ The cache is created both in memory and on the hard drive."
12731278
"Check if FILE is already in PROJECT cache."
12741279
(member file (gethash project projectile-projects-cache)))
12751280

1281+
(defun projectile--schedule-cache-flush (project)
1282+
"Arrange for PROJECT's in-memory cache to be serialized after Emacs is idle.
1283+
A pending flush for the same PROJECT is cancelled and rescheduled, so that
1284+
adding several files in quick succession only results in a single disk write,
1285+
and the write always uses the latest in-memory contents."
1286+
(when-let* ((existing (gethash project projectile--pending-cache-flush-timers)))
1287+
(cancel-timer existing))
1288+
(puthash project
1289+
(run-with-idle-timer
1290+
30 nil
1291+
(lambda ()
1292+
(remhash project projectile--pending-cache-flush-timers)
1293+
(projectile-serialize
1294+
(gethash project projectile-projects-cache)
1295+
(projectile-project-cache-file project))))
1296+
projectile--pending-cache-flush-timers))
1297+
12761298
;;;###autoload
12771299
(defun projectile-cache-current-file ()
12781300
"Add the currently visited file to the cache."
@@ -1286,16 +1308,12 @@ The cache is created both in memory and on the hard drive."
12861308
(unless (or (projectile-file-cached-p current-file current-project)
12871309
(projectile-ignored-directory-p (file-name-directory abs-current-file))
12881310
(projectile-ignored-file-p abs-current-file))
1289-
(let ((project-files (cons current-file (gethash current-project projectile-projects-cache)))
1290-
(cache-file (projectile-project-cache-file current-project)))
1311+
(let ((project-files (cons current-file (gethash current-project projectile-projects-cache))))
12911312
(puthash current-project project-files projectile-projects-cache)
1292-
;; we serialize the cache with an idle time to avoid freezing the UI
1293-
;; immediately after the new file was created
1313+
;; Defer the disk write until Emacs is idle to avoid freezing the
1314+
;; UI immediately after the new file was created.
12941315
(when (projectile-persistent-cache-p)
1295-
(run-with-idle-timer
1296-
30
1297-
nil
1298-
'projectile-serialize project-files cache-file)))
1316+
(projectile--schedule-cache-flush current-project)))
12991317
(message "File %s added to project %s cache."
13001318
(propertize current-file 'face 'font-lock-keyword-face)
13011319
(propertize current-project 'face 'font-lock-keyword-face)))))))

0 commit comments

Comments
 (0)