From 5e1bdb6c27eaad40b8ba437db5bf90c7624478ac Mon Sep 17 00:00:00 2001 From: Facundo Farias Date: Sat, 12 Sep 2026 12:37:41 +0200 Subject: [PATCH] Explain why `start` did nothing when a process is below its quantity Supervisor#start_processes skips any process type that already has running instances: next if @processes[process] && !@processes[process].empty? so a type running 23 of a configured 25 is skipped entirely and `procodile start ` prints: No processes to start. while `procodile status` simultaneously reports: * worker only has 23 instances (should have 25) Two commands contradicting each other, with no hint that `check_concurrency` is the one that tops the type up. Keep the existing behaviour and message, but follow it with the reason and the remedy when the supervisor is reporting an incorrect quantity for a type the user asked to start. The quantity check is advisory: it is wrapped so a failure to read status cannot turn a successful no-op into an error. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01P89F451YDRDBj5br14i3GD --- lib/procodile/cli.rb | 27 +++++++++++++++++++++++ spec/specs/cli_spec.rb | 49 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 spec/specs/cli_spec.rb diff --git a/lib/procodile/cli.rb b/lib/procodile/cli.rb index 43db7c3..fe5e064 100644 --- a/lib/procodile/cli.rb +++ b/lib/procodile/cli.rb @@ -143,6 +143,7 @@ def dispatch(command) instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag], :port_allocations => @options[:port_allocations]) if instances.empty? puts "No processes to start." + report_processes_below_quantity else instances.each do |instance| puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})" @@ -475,6 +476,32 @@ def self.pid_active?(pid) false end + # `start` only starts process types with no running instances at all (see + # Supervisor#start_processes), so a type running below its configured quantity is + # skipped and the user is told "No processes to start." with no indication that + # anything is wrong. Name the command that actually tops it up. + def report_processes_below_quantity + status = ControlClient.run(@config.sock_path, 'status') + below = processes_below_quantity(status['messages'], process_names_from_cli_option) + return if below.empty? + + verb = below.size == 1 ? 'is' : 'are' + puts "#{below.join(', ')} #{verb} running below the configured quantity." + puts "Run `procodile check_concurrency` to start the missing process(es)." + rescue Procodile::Error + # This is advisory output only -- never turn a successful no-op into a failure. + nil + end + + # Names of process types that the supervisor reports as below their configured + # quantity, optionally narrowed to the types the user asked to start. Instance + # suffixes ("worker.1") are reduced to the type name. + def processes_below_quantity(messages, requested = nil) + below = messages.select { |m| m['type'] == 'incorrect_quantity' }.map { |m| m['process'] } + return below if requested.nil? + below & requested.map { |name| name.split('.', 2).first } + end + def process_names_from_cli_option if @options[:processes] processes = @options[:processes].split(',') diff --git a/spec/specs/cli_spec.rb b/spec/specs/cli_spec.rb new file mode 100644 index 0000000..0b533a5 --- /dev/null +++ b/spec/specs/cli_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' +require 'procodile/cli' + +describe Procodile::CLI do + subject(:cli) { described_class.new } + + let(:worker_below) do + {'type' => 'incorrect_quantity', 'process' => 'worker', 'current' => 23, 'desired' => 25} + end + + let(:web_below) do + {'type' => 'incorrect_quantity', 'process' => 'web', 'current' => 1, 'desired' => 2} + end + + let(:not_running) do + {'type' => 'not_running', 'instance' => 'worker.1', 'status' => 'Failed'} + end + + describe "#processes_below_quantity" do + it "returns every under-quantity process when nothing specific was requested" do + result = cli.send(:processes_below_quantity, [worker_below, web_below], nil) + expect(result).to eq(['worker', 'web']) + end + + it "ignores messages that are not about quantity" do + result = cli.send(:processes_below_quantity, [not_running], nil) + expect(result).to be_empty + end + + it "narrows to the processes the user asked to start" do + result = cli.send(:processes_below_quantity, [worker_below, web_below], ['web']) + expect(result).to eq(['web']) + end + + it "matches a requested instance name against its process type" do + result = cli.send(:processes_below_quantity, [worker_below], ['worker.1']) + expect(result).to eq(['worker']) + end + + it "returns nothing when the requested process is at its quantity" do + result = cli.send(:processes_below_quantity, [worker_below], ['web']) + expect(result).to be_empty + end + + it "returns nothing when there are no messages" do + expect(cli.send(:processes_below_quantity, [], nil)).to be_empty + end + end +end