Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Allow for setpgid to be configurable #148

Merged
merged 1 commit into from
Jan 17, 2025

Conversation

HeyNonster
Copy link
Contributor

@HeyNonster HeyNonster commented Jan 17, 2025

It seems that Process.setpgid moves worker process out of the foreground process group of the controlling terminal, breaking debuggers or otherwise reading from STDIN.

An example of the problem with slightly truncated ps -l output:

# With Process.setpgid(pid, pid)
ps -l
  UID   PID  PPID  RSS WCHAN   S   TTY           TIME CMD
  503 73338 67944 92432 -      S+   ttys001    0:00.79 pitchfork monitor -
  503 73343 73338 13808 -      S    ttys001    0:00.00 pitchfork (gen:0) worker[0] - requests: 0, waiting

# Without Process.setpgid(pid, pid)
$ ps -l
  UID   PID  PPID  RSS WCHAN   S   TTY           TIME CMD
  503 73358 67944 93264 -      S+   ttys001    0:00.81 pitchfork monitor -
  503 73363 73358 13248 -      S+   ttys001    0:00.00 pitchfork (gen:0) worker[0] - requests: 0, waiting

The S column is the state of the process. You will notice, without setpgid both pitchfork processes are in S+ state.

From man ps, PROCESS STATE CODES section:

S interruptible sleep (waiting for an event to complete)
+ is in the foreground process group

From General Terminal Interface, 11.1.4 Terminal Access Control

If a process is in the foreground process group of its controlling terminal, read operations shall be allowed, as described in Input Processing and Reading Data. Any attempts by a process in a background process group to read from its controlling terminal cause its process group to be sent a SIGTTIN signal unless one of the following special cases applies: if the reading process is ignoring the SIGTTIN signal or the reading thread is blocking the SIGTTIN signal, or if the process group of the reading process is orphaned, the read() shall return -1, with errno set to [EIO] and no signal shall be sent. The default action of the SIGTTIN signal shall be to stop the process to which it is sent. See <signal.h>.

When the worker process is not in the foreground process group we get an Errno::EIO exception

[1, 7] in ~/Code/experiments/pitchfork_debugger/app/controllers/foo_controller.rb
     1| class FooController < ApplicationController
     2|   def index
=>   3|     require 'debug';debugger
     4|
     5|     head :ok
     6|   end
     7| end
=>#0    FooController#index at ~/Code/experiments/pitchfork_debugger/app/controllers/foo_controller.rb:3
  #1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at $(Gem)/actionpack-8.0.1/lib/action_controller/metal/basic_implicit_render.rb:8
  # and 100 frames (use `bt' command for all frames)
#<Thread:0x00000001168d9638@DEBUGGER__::SESSION@server /debug-1.10.0/lib/debug/session.rb:179 run> terminated with exception (report_on_exception is true):

/reline-0.6.0/lib/reline/io/ansi.rb:214:in 'IO#readpartial': Input/output error (Errno::EIO)

This commit adds a setpgid configuration option that allows you to set setpgid to false, if needed, so that you can open debugging sessions. This is probably only useful in development.

I believe it's safe to stop calling setpgid because, as far as I can tell, Pitchfork does not use the pgid of a process for anything (yet).

@HeyNonster HeyNonster force-pushed the nony--configurable-setpgid branch 3 times, most recently from 2538595 to cc7e109 Compare January 17, 2025 12:52
@HeyNonster HeyNonster marked this pull request as ready for review January 17, 2025 12:53
@HeyNonster
Copy link
Contributor Author

The smallest way I can reproduce the problem:

# Bad
ruby -ve "if pid = Process.fork;Process.setpgid(pid, pid);Process.wait;else;gets;end"

# Works
ruby -ve "if pid = Process.fork;Process.wait;else;gets;end"

@@ -79,6 +79,7 @@ class Configurator
:client_body_buffer_size => Pitchfork::Const::MAX_BODY,
:before_service_worker_ready => nil,
:before_service_worker_exit => nil,
:setpgid => false,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may want to use a different key/method name here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I don't know. At least it's pretty clear which syscall is involved. But people may think they're meant to give a numeric ID or something.

Honestly I think it's fine, but if you have a better idea let me know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's fine, tbh, but wanted to mention it just in case.

Copy link
Contributor

@byroot byroot left a comment

Choose a reason for hiding this comment

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

LGTM.

cc @etiennebarrie could you merge this please? (unless @HeyNonster whants more time to think about the config name)

@HeyNonster
Copy link
Contributor Author

LGTM.

cc @etiennebarrie could you merge this please? (unless @HeyNonster whants more time to think about the config name)

Nah, it should be fine! :shipit:

Thanks for your help!

It seems that `Process.setpgid` moves worker process out of the
foreground process group of the controlling terminal, breaking debuggers
or reading from `STDIN`.

An example of the problem with slightly truncated `ps -l` output:

```
\# With Process.setpgid(pid, pid)
ps -l
  UID   PID  PPID  RSS WCHAN   S   TTY           TIME CMD
  503 73338 67944 92432 -      S+   ttys001    0:00.79 pitchfork monitor -
  503 73343 73338 13808 -      S    ttys001    0:00.00 pitchfork (gen:0) worker[0] - requests: 0, waiting

\# Without Process.setpgid(pid, pid)
$ ps -l
  UID   PID  PPID  RSS WCHAN   S   TTY           TIME CMD
  503 73358 67944 93264 -      S+   ttys001    0:00.81 pitchfork monitor -
  503 73363 73358 13248 -      S+   ttys001    0:00.00 pitchfork (gen:0) worker[0] - requests: 0, waiting

```

The `S` column is the `state` of the process. You will notice, that
without `setpgid` both pitchfork processes are in `S+` state.

From [man ps](https://man7.org/linux/man-pages/man1/ps.1.html), `PROCESS
STATE CODES` section:

> S interruptible sleep (waiting for an event to complete)
> + is in the foreground process group

From [General Terminal Interface, 11.1.4 Terminal Access Control](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html)

> If a process is in the foreground process group of its controlling terminal, read operations shall be allowed, as described in Input Processing and Reading Data. Any attempts by a process in a background process group to read from its controlling terminal cause its process group to be sent a SIGTTIN signal unless one of the following special cases applies: if the reading process is ignoring the SIGTTIN signal or the reading thread is blocking the SIGTTIN signal, or if the process group of the reading process is orphaned, the read() shall return -1, with errno set to [EIO] and no signal shall be sent. The default action of the SIGTTIN signal shall be to stop the process to which it is sent. See <signal.h>.

When the worker process is not in the foreground process group we get
an `Errno::EIO` exception

```
[1, 7] in ~/Code/experiments/pitchfork_debugger/app/controllers/foo_controller.rb
     1| class FooController < ApplicationController
     2|   def index
=>   3|     require 'debug';debugger
     4|
     5|     head :ok
     6|   end
     7| end
=>#0    FooController#index at ~/Code/experiments/pitchfork_debugger/app/controllers/foo_controller.rb:3
  Shopify#1    ActionController::BasicImplicitRender#send_action(method="index", args=[]) at $(Gem)/actionpack-8.0.1/lib/action_controller/metal/basic_implicit_render.rb:8
  # and 100 frames (use `bt' command for all frames)
▽#<Thread:0x00000001168d9638@DEBUGGER__::SESSION@server /debug-1.10.0/lib/debug/session.rb:179 run> terminated with exception (report_on_exception is true):

/reline-0.6.0/lib/reline/io/ansi.rb:214:in 'IO#readpartial': Input/output error (Errno::EIO)
```

This commit adds a `setpgid` configuration option that allows you to set
`setpgid` to false, if needed, so that you can open debugging sessions.
This is probably only useful in development.
@etiennebarrie etiennebarrie force-pushed the nony--configurable-setpgid branch from cc7e109 to cdfb2f9 Compare January 17, 2025 16:01
@etiennebarrie etiennebarrie merged commit 9a8d1a4 into Shopify:master Jan 17, 2025
10 checks passed
@HeyNonster HeyNonster deleted the nony--configurable-setpgid branch January 17, 2025 16:33
HeyNonster added a commit to HeyNonster/pitchfork that referenced this pull request Jan 20, 2025
I forgot to update the changelog in Shopify#148.
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants