Skip to content

EE Counterpart: Puma in GDK and rack server lifecycle event abstractions

Andrew Newdigate requested to merge ee-an-multithreading into master

EE Counterpart for https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22372

What is the purpose of this change

Part of https://gitlab.com/gitlab-org/gitlab-ce/issues/3592

Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/52762

This MR and its accompanying merge requests, is the first step forwards a multithreaded GitLab rails service.

Extends @yorickpeterse's WIP Puma MR, rebasing from master and including the start of GDK support https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1899

How does it do it

It allows the rails server to be started in puma. Since unicorn and puma both implement rack services, this isn't a particularly hard thing to do.

Most of the changes in this MR are related to the non-standard hooks that we use to be notified of unicorn lifecycle events, for example worker being started and stopped. These are not standardised and differ between unicorn, puma and other servers.

To make matters worse, much of this logic is spread out in Omnibus, which makes reasoning about it significantly more difficult. This is largely resolved in this MR - for example prometheus lifecycle code can now be found alongside prometheus initialisation code, instead of being in a different repository.

To resolve this, I've decoupled then lifecycle event sources from listeners with three types of cluster lifecycle events:

Event Where Puma Cluster Unicorn Omnibus Injects Code? Omnibus Allows Injection? LifecycleEvents
Before Fork Master before_fork before_fork Yes Yes before_fork
After Fork Worker on_worker_boot after_fork Yes Yes worker_start
Before Exec Master on_restart before_exec Yes Yes on_master_restart

Now, for example, instead of handling events inside the unicorn before_fork event, application code can register as follows:

 Gitlab::Cluster::LifecycleEvents.on_worker_start do
   # Start a thread....   
 end

This block will then get executed whenever a worker is forked: in other words, the block is executed once per process.

To invoke the events, LifecycleEvents needs to be coupled to the server. This is implementation specific. For example in Puma this is done with:

before_fork do
  Gitlab::Cluster::LifecycleEvents.signal_before_fork
end

on_worker_boot do
  Gitlab::Cluster::LifecycleEvents.signal_worker_start
end

on_restart do
  Gitlab::Cluster::LifecycleEvents.signal_master_restart
end

Whereas in Unicorn, this is done with a different set of methods:

before_exec do |server|
  Gitlab::Cluster::LifecycleEvents.signal_master_restart
end

before_fork do |server, worker|
  Gitlab::Cluster::LifecycleEvents.signal_before_fork
end

after_fork do |server, worker|
  Gitlab::Cluster::LifecycleEvents.signal_worker_start
end

How to merge

  1. The omnibus change needs to be merged concurrently to this MR: omnibus-gitlab!2783 (merged)

  2. The EE change needs to be merged concurrently to this MR: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7943

  3. In order to enable this change in GDK, this GDK change needs to be merged after this is merged.

  4. Note that GDK does not overwrite the gitignored config/unicorn.rb file but it's important that this file needs to be updated as part of this change. Any suggestions about how we going about doing this? One option would be to make a backup of the existing file and overwrite it, or possibly attempt to patch it?

  5. Run gdk with the EXPERIMENTAL_PUMA flag set: eg EXPERIMENTAL_PUMA=1 gdk run or EXPERIMENTAL_PUMA=1 gdk run app etc

Edited by Andrew Newdigate

Merge request reports