Skip to content

Conversation

@cjlarose
Copy link
Member

@cjlarose cjlarose commented Sep 19, 2020

Description

My original motivation for extracting code related to the worker processes is the change described in #1875 (comment). I want to be able to create the first worker process (assuming fork_worker is on) by first forking from the master, then immediately execing into a new process definition with a separate memory space.

My proof-of-concept in that comment technically works but it's messy because the logic used by the worker process is woven in together with the logic used by the puma master process in Puma::Cluster. Worker processes implicitly rely on several instance variables used in Puma::Cluster, so for the new process to spin up a new worker, it needs to first spin up a dummy instance of Puma::Cluster.

To make it a little bit cleaner, I pulled out the worker logic into a new Puma::Cluster::Worker class. The initializer lays out everything that's necessary to construct a new worker process. My hope is that it's clear that this refactor is worth having regardless of the original motivation for it because it makes dependencies of the worker process clear and lowers the cognitive overhead of having worker logic and cluster logic in the same class definition.

Your checklist for this pull request

  • I have reviewed the guidelines for contributing to this repository.
  • I have added an entry to History.md if this PR fixes a bug or adds a feature. If it doesn't need an entry to HISTORY.md, I have added [changelog skip] or [ci skip] to the pull request title.
  • I have added appropriate tests if this PR fixes a bug or adds a feature.
  • My pull request is 100 lines added/removed or less so that it can be easily reviewed.
  • If this PR doesn't need tests (docs change), I added [ci skip] to the title of the PR.
  • If this closes any issues, I have added "Closes #issue" to the PR description or my commit messages.
  • I have updated the documentation accordingly.
  • All new and existing tests passed, including Rubocop.

@MSP-Greg
Copy link
Member

@cjlarose

Thank you for your work on this.

cognitive overhead

Nice way of saying... Having recently spent a few weeks away from thinking about anything Puma, I would agree.

Any thoughts on removing the current Puma::Cluster::Worker code from lib/puma/cluster.rb and moving it to lib/puma/cluster/worker.rb (same namespace of Puma::Cluster::Worker) and naming your file lib/puma/cluster/worker0.rb, with a namespace of Puma::Cluster::Worker0, or something like that? Just a thought...

Being unable to run fork locally (hope to fix that soon), I can't really test this, but CI is passing. 👍

Lastly, the gem version/update issue would be helped by this. If autoload was used for gems (both external and std-lib), and maybe Puma files, it might remove issues with "where do we need require statements, and what about CI?", etc

This class represents a worker from the perspective of the puma master
process. It provides methods for controlling the process, but doesn't
contain the logic actually executed by the worker.

In preparation for creating a new class that encapsulates the worker
logic, we're renaming this one to WorkerHandle.
Before, all functionality of the worker processes was defined in the
Cluster class. In preparation for making it possible to start worker
processes outside of the context of a Cluster instance, we move the
worker functionality into a new class.

This has the additional benefit of delineating exactly the dependencies
of the worker processes, namely the Launcher, options hash, and the
pipes used for inter-process communication.
@cjlarose cjlarose force-pushed the extract-worker-class branch from 8009aa1 to bef62c5 Compare September 20, 2020 03:05
@cjlarose
Copy link
Member Author

Any thoughts on removing the current Puma::Cluster::Worker code from lib/puma/cluster.rb and moving it to lib/puma/cluster/worker.rb (same namespace of Puma::Cluster::Worker) and naming your file lib/puma/cluster/worker0.rb, with a namespace of Puma::Cluster::Worker0, or something like that? Just a thought...

I agree that Puma::Cluster is the right namespace for both the new worker class and the existing worker class, but I wasn't crazy about the names Puma::Cluster::Worker and Puma::Cluster::Worker0, especially since the latter represents all workers, not just the first one. I ended up renaming Puma::Cluster::Worker to Puma::Cluster::WorkerHandle and making the new class Puma::Cluster::Worker. What do you think?

@MSP-Greg
Copy link
Member

I was mostly concerned with the namespace issue, and the embedded class in Cluster. As to WorkerHandle, I'm not really sure, as it's kind of an different situation. I can't think of another similar naming issue elsewhere...

JFYI, there are intermittent failures with non MRI jobs in CI, especially on macOS.

AFK. Thanks, Greg

@gingerlime
Copy link
Contributor

I'm a complete outsider to the Puma codebase, but just tried to understand the naming difference you discussed...

So if I understand this correctly the WorkerHandle is the process handle for the worker, which you can then kill, check for liveness etc, and the Worker itself contains the actual logic. Right?

What about renaming Worker -> WorkerLogic? You can then either keep WorkerHandle as is? or maybe rename to WorkerProcHandle just to be more explicit? because "handle" can feel a little vague without context...

apologies if this doesn't make any sense and I'm butting in to a discussion that's just way over my head :) I know naming is hard, but I hope that my outsider's perspective can be helpful to see things with a fresh outlook.

The instance varaible @options can be derived from the @launcher
@nateberkopec nateberkopec added feature waiting-for-review Waiting on review from anyone labels Sep 21, 2020
@nateberkopec
Copy link
Member

I'm kind of shocked that this clean of an extract was possible. I'm not terribly bothered about the current naming. Need to run through it in more detail but in concept I'm 100% behind this.

fork_worker = @options[:fork_worker] && index == 0

@workers = []
if !@options[:fork_worker] || fork_worker
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never realized this conditional was always true 😞

ensure
@worker_write << "t#{Process.pid}\n" rescue nil
@worker_write.close
new_worker = Worker.new index: index,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty delicious interface here 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintenance waiting-for-review Waiting on review from anyone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants