Module: ReactOnRailsPro::RollingDeployCacheStager
- Defined in:
- lib/react_on_rails_pro/rolling_deploy_cache_stager.rb
Overview
Seeds previous deploy bundle hashes into the Node Renderer cache so that during a rolling deploy, new renderer instances can serve requests for bundles referenced by draining Rails instances without hitting the 410 retry path.
Discovery:
* ENV["PREVIOUS_BUNDLE_HASHES"] (comma-separated) — overrides adapter discovery.
* ReactOnRailsPro.configuration.rolling_deploy_adapter#previous_bundle_hashes — the default.
Retrieval:
* rolling_deploy_adapter#fetch(hash) must return a Hash with keys
:bundle (String path to the bundle file) and :assets (Array<String>
of companion asset file paths). Returns nil if the bundle is
unavailable.
Protocol model: each hash is one bundle's cache entry. Adapters
advertise separate hashes for server and RSC bundles; the stager
stages each hash at
Missing previous bundles degrade gracefully (warn + continue) because the runtime 410-retry path is still a valid fallback — a failed rolling-deploy seed is less catastrophic than a failed current bundle seed.
Constant Summary collapse
- DISCOVERY_TIMEOUT_SECONDS =
Duplicated in react_on_rails/lib/react_on_rails/doctor.rb as a hardcoded fallback when the Pro gem isn't loaded. The cross-package equality is asserted in spec/dummy/spec/rolling_deploy_cache_stager_spec.rb so a change here fails that spec instead of silently drifting past the doctor probe. Note:
Timeout.timeoutinterrupts the discovery call at a quasi- random thread-execution point. The bundled reference adapters use pure- Ruby HTTP clients that release the GIL, so the interrupt is safe. Adapter authors using native-extension or FFI-backed clients should add their own SDK-levelopen_timeout/read_timeoutrather than rely solely on this outer wrapper. Same caveat applies toFETCH_TIMEOUT_SECONDSbelow and the upload timeout inassets_precompile.rb. 10- FETCH_TIMEOUT_SECONDS =
Per-hash fetch budget during pre-seeding. Large cross-region stores may need adapters to keep fetches comfortably under this limit.
30- STALE_TEMP_DIR_TTL_SECONDS =
Age threshold for sweeping leftover
.staging-*/.previous-*dirs. Any temp dir older than this is assumed to be from a crashed or abandoned prior run and safe to remove. Ifassets:precompileitself routinely takes longer than one hour on a persistent-volume deploy (uncommon), raise this so a concurrent seeder's still-in-use staging dir is not swept mid-operation. The degradation is graceful regardless — the racingreplace_bundle_directoryrolls back cleanly onENOENT. 3600- TEMPORARY_DIRECTORY_PATTERN =
Match temp dirs created by
temporary_bundle_directory(and the analogous.previous-backup suffix inreplace_bundle_directory). The 8-hex random suffix defeats false positives where a real bundle hash happens to end with.staging-<digits>-<short hex>. PID is\d+rather than\d{4,}because container deployments (Docker, Kubernetes) commonly run the seeding process as PID 1; a stricter floor would silently leave PID-1 staging dirs in the cache to accumulate forever. /\.(?:staging|previous)-\d+-[0-9a-f]{8,}\z/- SAFE_HASH_PATTERN =
Bundle hashes are used as directory names under the renderer cache path (
/ / .js). The shared pattern rejects path separators, ./.., any leading dot, and any leading hyphen.bundle_directory'sstart_with?guard already prevents path traversal, but a leading-dot hash (e.g..hidden) would still create a hidden cache subdirectory invisible tols, surprising operators who count bundle-hash entries during incident response.NOTE: this is a tightening of the previous local pattern, which allowed leading hyphens. Webpack content hashes never start with
-in practice, but custom adapters emitting hyphen-prefixed hashes will now see those hashes silently dropped from the staged set. See CHANGELOG for the upgrade note. ReactOnRailsPro::RollingDeploy::SAFE_HASH_PATTERN
Class Method Summary collapse
Class Method Details
.call(cache_dir:, current_hashes:, mode:) ⇒ Object
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/react_on_rails_pro/rolling_deploy_cache_stager.rb', line 79 def self.call(cache_dir:, current_hashes:, mode:) adapter = ReactOnRailsPro.configuration.rolling_deploy_adapter return handle_missing_adapter unless adapter sweep_stale_temporary_directories(cache_dir) hashes = resolve_previous_hashes(adapter, current_hashes) if hashes.empty? puts "[ReactOnRailsPro] No previous bundle hashes to seed for rolling deploy." return end # Create the cache root once we know we have at least one hash to stage. # bundle_directory then resolves real paths against an existing dir without # needing to mutate the filesystem itself. FileUtils.mkdir_p(cache_dir) normalized_cache_dir = File.realpath(cache_dir) puts "[ReactOnRailsPro] Seeding previous bundle hashes for rolling deploy: #{hashes.inspect}" hashes.each { |hash| seed_previous_hash(adapter, hash, normalized_cache_dir, mode) } end |