Movatterモバイル変換


[0]ホーム

URL:


Skip to main content
More atrubyonrails.org:

Threading and Code Execution in Rails

After reading this guide, you will know:

  • What code Rails will automatically execute concurrently
  • How to integrate manual concurrency with Rails internals
  • How to wrap all application code
  • How to affect application reloading

1. Automatic Concurrency

Rails automatically allows various operations to be performed at the same time.

When using a threaded web server, such as the default Puma, multiple HTTPrequests will be served simultaneously, with each request provided its owncontroller instance.

Threaded Active Job adapters, including the built-in Async, will likewiseexecute several jobs at the same time. Action Cable channels are managed thisway too.

These mechanisms all involve multiple threads, each managing work for a uniqueinstance of some object (controller, job, channel), while sharing the globalprocess space (such as classes and their configurations, and global variables).As long as your code doesn't modify any of those shared things, it can mostlyignore that other threads exist.

The rest of this guide describes the mechanisms Rails uses to make it "mostlyignorable", and how extensions and applications with special needs can use them.

2. Executor

The Rails Executor separates application code from framework code: any time theframework invokes code you've written in your application, it will be wrapped bythe Executor.

The Executor consists of two callbacks:to_run andto_complete. The Runcallback is called before the application code, and the Complete callback iscalled after.

2.1. Default Callbacks

In a default Rails application, the Executor callbacks are used to:

  • track which threads are in safe positions for autoloading and reloading
  • enable and disable the Active Record query cache
  • 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 middlewareclasses (such asActiveRecord::ConnectionAdapters::ConnectionManagement), ordirectly wrapping code with methods likeActiveRecord::Base.connection_pool.with_connection. The Executor replacesthese with a single more abstract interface.

2.2. Wrapping Application Code

If you're writing a library or component that will invoke application code, youshould wrap it with a call to the executor:

Rails.application.executor.wrapdo# call application code hereend

If you repeatedly invoke application code from a long-running process, youmay want to wrap using theReloader instead.

Each thread should be wrapped before it runs application code, so if yourapplication manually delegates work to other threads, such as viaThread.newor Concurrent Ruby features that use thread pools, you should immediately wrapthe block:

Thread.newdoRails.application.executor.wrapdo# your code hereendend

Concurrent Ruby uses aThreadPoolExecutor, which it sometimes configureswith anexecutor option. Despite the name, it is unrelated.

The Executor is safely re-entrant; if it is already active on the currentthread,wrap is a no-op.

If it's impractical to wrap the application code in a block (forexample, the Rack API makes this problematic), you can also use therun! /complete! pair:

Thread.newdoexecution_context=Rails.application.executor.run!# your code hereensureexecution_context.complete!ifexecution_contextend

2.3. Concurrency

The Executor will put the current thread intorunning mode in theReloadingInterlock. This operation will block temporarily if anotherthread is currently unloading/reloading the application.

3. Reloader

Like the Executor, the Reloader also wraps application code. If the Executor isnot already active on the current thread, the Reloader will invoke it for you,so you only need to call one. This also guarantees that everything the Reloaderdoes, including all its callback invocations, occurs wrapped inside theExecutor.

Rails.application.reloader.wrapdo# call application code hereend

The Reloader is only suitable where a long-running framework-level processrepeatedly 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 rarelyneed to invoke the Reloader for yourself. Always consider whether the Executoris a better fit for your use case.

3.1. Callbacks

Before entering the wrapped block, the Reloader will check whether the runningapplication needs to be reloaded -- for example, because a model's source file hasbeen modified. If it determines a reload is required, it will wait until it'ssafe, and then do so, before continuing. When the application is configured toalways reload regardless of whether any changes are detected, the reload isinstead performed at the end of the block.

The Reloader also providesto_run andto_complete callbacks; they areinvoked at the same points as those of the Executor, but only when the currentexecution has initiated an application reload. When no reload is deemednecessary, the Reloader will invoke the wrapped block with no other callbacks.

3.2. Class Unload

The most significant part of the reloading process is the Class Unload, whereall autoloaded classes are removed, ready to be loaded again. This will occurimmediately before either the Run or Complete callback, depending on thereload_classes_only_on_change setting.

Often, additional reloading actions need to be performed either just before orjust after the Class Unload, so the Reloader also providesbefore_class_unloadandafter_class_unload callbacks.

3.3. Concurrency

Only long-running "top level" processes should invoke the Reloader, because ifit determines a reload is needed, it will block until all other threads havecompleted any Executor invocations.

If this were to occur in a "child" thread, with a waiting parent inside theExecutor, it would cause an unavoidable deadlock: the reload must occur beforethe child thread is executed, but it cannot be safely performed while the parentthread is mid-execution. Child threads should use the Executor instead.

4. Framework Behavior

The Rails framework components use these tools to manage their own concurrencyneeds too.

ActionDispatch::Executor andActionDispatch::Reloader are Rack middlewaresthat wrap requests with a supplied Executor or Reloader, respectively. Theyare automatically included in the default application stack. The Reloader willensure any arriving HTTP request is served with a freshly-loaded copy of theapplication if any code changes have occurred.

Active Job also wraps its job executions with the Reloader, loading the latestcode to execute each job as it comes off the queue.

Action Cable uses the Executor instead: because a Cable connection is linked toa specific instance of a class, it's not possible to reload for every arrivingWebSocket message. Only the message handler is wrapped, though; a long-runningCable connection does not prevent a reload that's triggered by a new incomingrequest or job. Instead, Action Cable uses the Reloader'sbefore_class_unloadcallback to disconnect all its connections. When the client automaticallyreconnects, it will be speaking to the new version of the code.

The above are the entry points to the framework, so they are responsible forensuring their respective threads are protected, and deciding whether a reloadis necessary. Other components only need to use the Executor when they spawnadditional threads.

4.1. Configuration

The Reloader only checks for file changes whenconfig.enable_reloading istrue and so isconfig.reload_classes_only_on_change. These are the defaults in thedevelopment environment.

Whenconfig.enable_reloading isfalse (inproduction, by default), theReloader is only a pass-through to the Executor.

The Executor always has important work to do, like database connectionmanagement. Whenconfig.enable_reloading isfalse andconfig.eager_load istrue (production defaults), no reloading will occur, so it does not need theReloading Interlock. With the default settings in thedevelopment environment, theExecutor will use the Reloading Interlock to ensure code reloading is performed safely.

5. Reloading Interlock

The Reloading Interlock ensures that code reloading can be performed safely in amulti-threaded runtime environment.

It is only safe to perform an unload/reload when no application code is inmid-execution: after the reload, theUser constant, for example, may point toa different class. Without this rule, a poorly-timed reload would meanUser.new.class == User, or evenUser == User, could be false.

The Reloading Interlock addresses this constraint by keeping track of whichthreads are currently running application code, and ensuring that reloadingwaits until no other threads are executing application code.



Back to top
[8]ページ先頭

©2009-2025 Movatter.jp