Skip to content

Commit 1bb654c

Browse files
authored
Configurable timeout to render requests (#220)
Added/Changed - Add `ssr_timeout` configuration so the Rails server will not wait more than this many seconds for a SSR request to return once issued. - Change default for `renderer_use_fallback_exec_js` to `false`. - Change default log level to info. Fixed - Ability to call `server_render_js(raw_js)` fixed. Previously, always errored. - Errors during rendering result in ReactOnRails::PrerenderError - When retrying rendering, the retry message is more clear
1 parent edd38d0 commit 1bb654c

File tree

15 files changed

+160
-57
lines changed

15 files changed

+160
-57
lines changed

CHANGELOG.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,27 @@ Gem and package versions are the same except for beta releases where the gem use
1212
## [Unreleased]
1313
*Add changes in master not yet tagged.*
1414

15+
### Added
16+
- Add `ssr_timeout` configuration so the Rails server will not wait more than this many seconds for a SSR request to return once issued.
17+
- Change default for `renderer_use_fallback_exec_js` to `false`.
18+
- Change default log level to info.
19+
[PR 220](https://github.com/shakacode/react_on_rails_pro/pull/220) by
20+
[justin808](https://github.com/justin808).
21+
22+
- Add support for render functions to be async (returning promises). Also add `include_execjs_polyfills` option to configuration for React on Rails to optionally stop stubbing of setTimeout, setInterval, & clearTimeout polyfills while using NodeRenderer. [PR 210](https://github.com/shakacode/react_on_rails_pro/pull/210) by [judahmeek](https://github.com/judahmeek)
23+
24+
### Fixed
25+
- Ability to call `server_render_js(raw_js)` fixed. Previously, always errored.
26+
- Errors during rendering result in ReactOnRails::PrerenderError
27+
- When retrying rendering, the retry message is more clear
28+
1529
## [2.3.0] - 2021-09-22
1630

1731
### Added
32+
- Configuration option for `ssr_timeout` so the Rails server will not wait more than this many seconds
33+
for a SSR request to return once issued. Default timeout if not set is 5.
34+
`config.ssr_timeout = 5`
35+
1836
- Added optional method `extra_files_to_cache` to the definition of the module for the configuration of
1937
the remote_bundle_cache_adapter. This allows files outside of the regular build directory to be
2038
placed in the cached zip file and then extracted and restored when the cache is restored. The use
@@ -24,8 +42,6 @@ Gem and package versions are the same except for beta releases where the gem use
2442
[PR 221](https://github.com/shakacode/react_on_rails_pro/pull/221) by
2543
[justin808](https://github.com/justin808) and [ershadul1](https://github.com/ershadul1).
2644

27-
- Add support for render functions to be async (returning promises). Also add `include_execjs_polyfills` option to configuration for React on Rails to optionally stop stubbing of setTimeout, setInterval, & clearTimeout polyfills while using NodeRenderer. [PR 210](https://github.com/shakacode/react_on_rails_pro/pull/210) by [judahmeek](https://github.com/judahmeek)
28-
2945
## [2.2.0] - 2021-07-13
3046
- Change rake react_on_rails_pro:pre_stage_bundle_for_vm_renderer to use symlinks to save slug size. [PR 202](https://github.com/shakacode/react_on_rails_pro/pull/202) by [justin808](https://github.com/justin808).
3147

Gemfile.development_dependencies

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ group :development do
4444
end
4545

4646
group :development, :test do
47-
gem "pry-byebug"
48-
gem "pry", github: "pry/pry"
49-
gem "pry-rails"
50-
gem "pry-doc", github: "pry/pry-doc"
51-
gem "pry-theme"
52-
gem "pry-rescue"
47+
gem 'pry', '>= 0.14.1' # Console with powerful introspection capabilities
48+
# Need to use master of pry-byebug to use latest pry version
49+
gem 'pry-byebug', github: 'Popmenu/pry-byebug' # Integrates pry with byebug
50+
gem 'pry-doc' # Provide MRI Core documentation
51+
gem 'pry-rails' # Causes rails console to open pry. `DISABLE_PRY_RAILS=1 rails c` can still open with IRB
52+
gem 'pry-theme' # An easy way to customize Pry colors via theme files
5353

5454
gem "rubocop", "1.18.3", require: false
5555
gem 'rubocop-performance', "1.11.4", require: false

docs/configuration.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,16 @@ ReactOnRailsPro.configure do |config|
7676
# Default for `renderer_password` is ""
7777
# config.renderer_password = ENV["RENDERER_PASSWORD"]
7878

79+
# Set the `ssr_timeout` configuration so the Rails server will not wait more than this many seconds
80+
# for a SSR request to return once issued.
81+
config.ssr_timeout = 5
82+
7983
# If false, then crash if no backup rendering when the remote renderer is not available
8084
# Can be useful to set to false in development or testing to make sure that the remote renderer
8185
# works and any non-availability of the remote renderer does not just do ExecJS.
82-
# Default for `renderer_use_fallback_exec_js` is true.
83-
config.renderer_use_fallback_exec_js = true
86+
# Suggest setting this to false if the SSR JS code cannot run in ExecJS
87+
# Default for `renderer_use_fallback_exec_js` is false.
88+
config.renderer_use_fallback_exec_js = false
8489

8590
# The maximum size of the http connection pool,
8691
# Set +pool_size+ to limit the maximum number of connections allowed.

lib/react_on_rails_pro/configuration.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def self.configuration
2020
dependency_globs: Configuration::DEFAULT_DEPENDENCY_GLOBS,
2121
excluded_dependency_globs: Configuration::DEFAULT_EXCLUDED_DEPENDENCY_GLOBS,
2222
remote_bundle_cache_adapter: Configuration::DEFAULT_REMOTE_BUNDLE_CACHE_ADAPTER,
23+
ssr_timeout: Configuration::DEFAULT_SSR_TIMEOUT,
2324
ssr_pre_hook_js: nil,
2425
assets_to_copy: nil,
2526
renderer_request_retry_limit: Configuration::DEFAULT_RENDERER_REQUEST_RETRY_LIMIT,
@@ -36,6 +37,7 @@ class Configuration
3637
DEFAULT_RENDERER_HTTP_POOL_SIZE = 10
3738
DEFAULT_RENDERER_HTTP_POOL_TIMEOUT = 5
3839
DEFAULT_RENDERER_HTTP_POOL_WARN_TIMEOUT = 0.25
40+
DEFAULT_SSR_TIMEOUT = 5
3941
DEFAULT_PRERENDER_CACHING = false
4042
DEFAULT_TRACING = false
4143
DEFAULT_DEPENDENCY_GLOBS = nil
@@ -51,15 +53,15 @@ class Configuration
5153
:renderer_http_pool_size, :renderer_http_pool_timeout, :renderer_http_pool_warn_timeout,
5254
:dependency_globs, :excluded_dependency_globs, :rendering_returns_promises,
5355
:remote_bundle_cache_adapter, :ssr_pre_hook_js, :assets_to_copy,
54-
:renderer_request_retry_limit, :throw_js_errors
56+
:renderer_request_retry_limit, :throw_js_errors, :ssr_timeout
5557

5658
def initialize(renderer_url: nil, renderer_password: nil, server_renderer: nil,
5759
renderer_use_fallback_exec_js: nil, prerender_caching: nil,
5860
renderer_http_pool_size: nil, renderer_http_pool_timeout: nil,
5961
renderer_http_pool_warn_timeout: nil, tracing: nil, include_execjs_polyfills: nil,
6062
dependency_globs: nil, excluded_dependency_globs: nil, rendering_returns_promises: nil,
6163
remote_bundle_cache_adapter: nil, ssr_pre_hook_js: nil, assets_to_copy: nil,
62-
renderer_request_retry_limit: nil, throw_js_errors: nil)
64+
renderer_request_retry_limit: nil, throw_js_errors: nil, ssr_timeout: nil)
6365
self.renderer_url = renderer_url
6466
self.renderer_password = renderer_password
6567
self.server_renderer = server_renderer
@@ -78,6 +80,7 @@ def initialize(renderer_url: nil, renderer_password: nil, server_renderer: nil,
7880
self.assets_to_copy = assets_to_copy
7981
self.renderer_request_retry_limit = renderer_request_retry_limit
8082
self.throw_js_errors = throw_js_errors
83+
self.ssr_timeout = ssr_timeout
8184
end
8285

8386
def setup_config_values

lib/react_on_rails_pro/error.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

33
module ReactOnRailsPro
4-
class Error < StandardError
4+
class Error < ::ReactOnRails::Error
55
end
66
end

lib/react_on_rails_pro/request.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ def perform_request(path, form)
4848
raise ReactOnRailsPro::Error, "Time out error when getting the response on: #{path}.\n"\
4949
"Original error:\n#{e}\n#{e.backtrace}"
5050
end
51-
available_retries -= 1
5251
Rails.logger.info do
53-
"[ReactOnRailsPro] Timed out trying to connect to the Node Renderer.\
54-
Retrying #{available_retries} more times..."
52+
"[ReactOnRailsPro] Timed out trying to connect to the Node Renderer."\
53+
" Retrying #{available_retries} more times..."
5554
end
55+
available_retries -= 1
5656
next
5757
rescue StandardError => e
5858
raise ReactOnRailsPro::Error, "Can't connect to NodeRenderer renderer: #{path}.\n"\
@@ -142,6 +142,10 @@ def create_connection
142142
pool_size: ReactOnRailsPro.configuration.renderer_http_pool_size,
143143
pool_timeout: ReactOnRailsPro.configuration.renderer_http_pool_timeout,
144144
warn_timeout: ReactOnRailsPro.configuration.renderer_http_pool_warn_timeout,
145+
146+
# https://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html#attribute-i-read_timeout
147+
# https://github.com/bpardee/persistent_http/blob/master/lib/persistent_http/connection.rb#L168
148+
read_timeout: ReactOnRailsPro.configuration.ssr_timeout,
145149
force_retry: true,
146150
url: ReactOnRailsPro.configuration.renderer_url
147151
)

lib/react_on_rails_pro/server_rendering_pool/node_rendering_pool.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ def eval_js(js_code, render_options, send_bundle: false)
5252
ReactOnRailsPro::ServerRenderingPool::ProRendering
5353
.set_request_digest_on_render_options(js_code, render_options)
5454

55+
# In case this method is called with simple, raw JS, not depending on the bundle, next line
56+
# is needed.
57+
@bundle_update_utc_timestamp ||= bundle_utc_timestamp
58+
5559
# TODO: Remove the request_digest. See https://github.com/shakacode/react_on_rails_pro/issues/119
5660
# From the request path
5761
# path = "/bundles/#{@bundle_update_utc_timestamp}/render"

packages/node-renderer/src/master.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const cluster = require('cluster');
66
const log = require('./shared/log');
77
const { buildConfig, logSanitizedConfig } = require('./shared/configBuilder');
88
const restartWorkers = require('./master/restartWorkers');
9+
const errorReporter = require('./shared/errorReporter');
910

1011
const MILLISECONDS_IN_MINUTE = 60000;
1112

@@ -29,7 +30,10 @@ module.exports = function masterRun(runningConfig) {
2930
if (worker.isScheduledRestart) {
3031
log.info('Restarting worker #%d on schedule', worker.id);
3132
} else {
32-
log.warn('Worker #%d died UNEXPECTEDLY :(, restarting', worker.id);
33+
// TODO: Track last rendering request per worker.id
34+
// TODO: Consider blocking a given rendering request if it kills a worker more than X times
35+
const msg = `Worker ${worker.id} died UNEXPECTEDLY :(, restarting`;
36+
errorReporter.notify(msg);
3337
}
3438
// Replace the dead worker:
3539
cluster.fork();

packages/node-renderer/src/worker.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* @module worker
44
*/
55

6+
// Set next value to true to test timeouts
7+
// const TESTING_TIMEOUTS = false
8+
69
const path = require('path');
710
const cluster = require('cluster');
811
const express = require('express');
@@ -27,6 +30,17 @@ const errorReporter = require('./shared/errorReporter');
2730
const tracing = require('./shared/tracing');
2831
const { lock, unlock } = require('./shared/locks');
2932

33+
// Uncomment next 2 functions for testing timeouts
34+
// function sleep(ms) {
35+
// return new Promise((resolve) => {
36+
// setTimeout(resolve, ms);
37+
// });
38+
// }
39+
//
40+
// function getRandomInt(max) {
41+
// return Math.floor(Math.random() * Math.floor(max));
42+
// }
43+
3044
function setHeaders(headers, res) {
3145
Object.keys(headers).forEach((key) => res.set(key, headers[key]));
3246
}
@@ -106,6 +120,16 @@ module.exports = function run(config) {
106120
return;
107121
}
108122

123+
// if(TESTING_TIMEOUTS && getRandomInt(2) === 1) {
124+
// console.log(
125+
// 'ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ');
126+
// console.log(`Sleeping, to test timeouts`);
127+
// console.log(
128+
// 'ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ');
129+
//
130+
// await sleep(100000);
131+
// }
132+
109133
const { renderingRequest } = req.body;
110134
const { bundleTimestamp } = req.params;
111135
const { bundle: providedNewBundle, ...assetsToCopyObj } = req.files;

packages/node-renderer/src/worker/handleRenderRequest.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,6 @@ module.exports = async function handleRenderRequest({
208208
error,
209209
'Caught top level error in handleRenderRequest',
210210
);
211-
log.error(msg);
212211
errorReporter.notify(msg);
213212
return Promise.reject(error);
214213
}

0 commit comments

Comments
 (0)