Harden the dev route matchers against reload races
Three independent fixes for the development route matcher manager:
- A failed reload left waitTillReady holding a rejected promise, so one
transient filesystem error while scanning failed every subsequent
request until the next reload replaced it. The promise is now cleared
when it rejects.
- Concurrent reloads ran unserialized, interleaving scans of different
filesystem states and mutating shared matcher objects. Reloads are now
coalesced into one running and one queued scan; a caller that arrives
mid-scan waits for the queued one, since the running scan may predate
the change the caller wants to observe.
- A route deleted after the matchers last scanned the filesystem could
still match, and the ensure step then failed the request with a 500
where rendering the 404 page is expected. A PageNotFoundError from
ensure now means the match is stale and is skipped.