diff options
author | Matthew Draper <matthew@trebex.net> | 2017-11-17 00:21:13 +1030 |
---|---|---|
committer | Matthew Draper <matthew@trebex.net> | 2017-11-17 00:21:13 +1030 |
commit | b5cd7d5b511361062f67ebad45ab38393970fc47 (patch) | |
tree | 6eb51c6321f59ac44f9686a4cdadc42aeceb19fd /guides | |
parent | 4f7ab453e4afbf2302291d320967f38735de2e31 (diff) | |
download | rails-b5cd7d5b511361062f67ebad45ab38393970fc47.tar.gz rails-b5cd7d5b511361062f67ebad45ab38393970fc47.tar.bz2 rails-b5cd7d5b511361062f67ebad45ab38393970fc47.zip |
Add more detail on how the framework is actually configured by default
Diffstat (limited to 'guides')
-rw-r--r-- | guides/source/threading_and_code_execution.md | 72 |
1 files changed, 67 insertions, 5 deletions
diff --git a/guides/source/threading_and_code_execution.md b/guides/source/threading_and_code_execution.md index b35b2fdee4..1c7d61a29c 100644 --- a/guides/source/threading_and_code_execution.md +++ b/guides/source/threading_and_code_execution.md @@ -54,6 +54,12 @@ In a default Rails application, the Executor callbacks are used to: * return acquired Active Record connections to the pool * constrain internal cache lifetimes +Prior to Rails 5.0, some of these were handled by separate Rack middleware +classes (such as `ActiveRecord::ConnectionAdapters::ConnectionManagement`), or +directly wrapping code with methods like +`ActiveRecord::Base.connection_pool.with_connection do`. The Executor replaces +these with a single more abstract interface. + ### Wrapping application code If you're writing a library or component that will invoke application code, you @@ -89,7 +95,16 @@ thread, `wrap` is a no-op. If it's impractical to physically wrap the application code in a block (for example, the Rack API makes this problematic), you can also use the `run!` / -`complete!` pair. +`complete!` pair: + +```ruby +Thread.new do + execution_context = Rails.application.executor.run! + # your code here +ensure + execution_context.complete! if execution_context +end +``` ### Concurrency @@ -112,6 +127,12 @@ Rails.application.reloader.wrap do end ``` +The Reloader is only suitable where a long-running framework-level process +repeatedly calls into application code, such as for a web server or job queue. +Rails automatically wraps web requests and Active Job workers, so you'll rarely +need to invoke the Reloader for yourself. Always consider whether the Executor +is a better fit for your use case. + ### Callbacks Before entering the wrapped block, the Reloader will check whether the running @@ -141,14 +162,55 @@ and `after_class_unload` callbacks. Only long-running "top level" processes should invoke the Reloader, because if it determines a reload is needed, it will block until all other threads have -completed and left any Executor block. +completed any Executor invocations. If this were to occur in a "child" thread, with a waiting parent inside the Executor, it would cause an unavoidable deadlock: the reload must occur before the child thread is executed, but it cannot be safely performed while the parent -thread is mid-execution. - -Child threads should use the Executor instead. +thread is mid-execution. Child threads should use the Executor instead. + +Framework Behavior +------------------ + +The Rails framework components use these tools to manage their own concurrency +needs too. + +`ActionDispatch::Executor` and `ActionDispatch::Reloader` are Rack middlewares +that wraps the request with a supplied Executor or Reloader, respectively. They +are automatically included in the default application stack. The Reloader will +ensure any arriving HTTP request is served with a freshly-loaded copy of the +application if any code changes have occurred. + +Active Job also wraps its job executions with the Reloader, loading the latest +code to execute each job as it comes off the queue. + +Action Cable uses the Executor instead: because a Cable connection is linked to +a specific instance of a class, it's not possible to reload for every arriving +websocket message. Only the message handler is wrapped, though; a long-running +Cable connection does not prevent a reload that's triggered by a new incoming +request or job. Instead, Action Cable uses the Reloader's `before_class_unload` +callback to disconnect all its connections. When the client automatically +reconnects, it will be speaking to the new version of the code. + +The above are the entry points to the framework, so they are responsible for +ensuring their respective threads are protected, and deciding whether a reload +is necessary. Other components only need to use the Executor when they spawn +additional threads. + +### Configuration + +The Reloader only checks for file changes when `cache_classes` is false and +`reload_classes_only_on_change` is true (which is the default in the +`development` environment). + +When `cache_classes` is true (in `production`, by default), the Reloader is only +a pass-through to the Executor. + +The Executor always has important work to do, like database connection +management. When `cache_classes` and `eager_load` are both true (`production`), +no autoloading or class reloading will occur, so it does not need the Load +Interlock. If either of those are false (`development`), then the Executor will +use the Load Interlock to ensure constants are only loaded when it is safe. Load Interlock -------------- |