Concurrency

A Padrino app handles several requests at once when its web server runs them on threads, processes or fibers. You choose the model by choosing the server. Pick it when you generate the project:

$ padrino g project my_app --server=puma
$ padrino g project my_app --server=falcon

This adds the server gem to the Gemfile. In an existing project, add the gem yourself.

Puma: threads and workers

Puma runs each request on a thread from a pool. It can also fork worker processes, each with its own thread pool. Configure both in config/puma.rb (the generator does not create this file):

# config/puma.rb
threads 5, 5  # min, max threads per process
workers 2     # forked processes; leave out to run a single process

Puma reads this file whether you start it with padrino start -a puma or bundle exec puma. With padrino start, the address comes from -h and -p (default 127.0.0.1:3000), so any bind or port in config/puma.rb is ignored. You can also set the thread pool on the command line:

$ padrino start -a puma -O Threads=1:8

Threads in one process take turns running Ruby code, but they overlap while a request waits on the database or another service. Workers are separate processes, so they can use more than one CPU core.

With two or more workers, Puma loads the app once and then forks the workers, so they inherit any database connection opened while loading. Sequel connects when config/database.rb runs, so close those connections before forking, or the workers share one socket:

# config/puma.rb
before_fork { Sequel::DATABASES.each(&:disconnect) }

Active Record reconnects in forked processes by itself. For Mongoid, follow its documentation on forking servers.

Falcon: fibers

Falcon runs each request in its own fiber on Ruby's fiber scheduler. While one request waits on IO, the others keep running in the same thread.

When the project uses Falcon, the ORM generators add a fiber setting to config/database.rb, both for padrino g project --server=falcon and when you add an ORM later with padrino g component:

ORM Setting
Sequel Sequel.extension :fiber_concurrency
Active Record, MiniRecord ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
Mongoid Mongoid::Config.isolation_level = :fiber (Mongoid 9.1+, Ruby 3.2+)

This keeps database connections and ORM state per request instead of per thread.

padrino start -a falcon runs a single process. To run several, use Falcon's own command:

$ bundle exec falcon serve --bind http://0.0.0.0:3000 --count 4

Fibers don't preempt each other. CPU-heavy work, or a library call that does not yield to the fiber scheduler, holds up every other request in that process.

Thread safety

Each request gets its own instance of your app, so instance variables set in a route, filter or helper belong to that request. Anything shared between requests is shared by every thread: constants, class variables, globals, settings changed at runtime and class-level caches. Don't change them while serving requests, or guard them with a Mutex.

Give the database pool at least as many connections as you have threads: the pool option in Active Record (default 5) or max_connections in Sequel (default 4).

If some code can't be made thread safe, enable :lock makes the app handle one request at a time in each process.

Type to search the guides.