Skip to content

Fix jekyll serve --detach with jekyll-sass-converter 3.x#9304

Merged
jekyllbot merged 2 commits intojekyll:masterfrom
ntkme:daemon
Feb 21, 2023
Merged

Fix jekyll serve --detach with jekyll-sass-converter 3.x#9304
jekyllbot merged 2 commits intojekyll:masterfrom
ntkme:daemon

Conversation

@ntkme
Copy link
Copy Markdown
Contributor

@ntkme ntkme commented Feb 14, 2023

This is a 🐛 bug fix.

Summary

This PR fixes the issue that Process.fork + Process.detach would not detach correctly and may crash ruby when subprocess of sass-embedded (part of jekyll-sass-converter) is still running. The problem is that sass-embedded runs a child process and registers an at_exit hook to stop the child process, and the at_exit hook ran when main process exits but also get inherited by the forked process and would run again when forked process exits.

This PR changes it so that any at_exit hook would not run when main process exits, but then would only run when the forked process exits.

Context

@ntkme ntkme changed the title Use Process.daemon instead of Process.fork to detach Explicitly exit without running at_exit hooks when --detach Feb 14, 2023
@ntkme ntkme changed the title Explicitly exit without running at_exit hooks when --detach Explicitly exit main pid without running at_exit hooks when --detach Feb 14, 2023
@ntkme
Copy link
Copy Markdown
Contributor Author

ntkme commented Feb 19, 2023

@ashmaroli This is a bug related to jekyll-sass-converter 3.x upgrade. Would you mind take a look?

@ashmaroli
Copy link
Copy Markdown
Member

Hello @ntkme,
Is it possible to add a test regarding this? (I am on a Windows machine and therefore cannot test this manually).
Also, curious about the effect when user locks to older version of the sass-converter that doesn't involve sass-embedded.

@ntkme
Copy link
Copy Markdown
Contributor Author

ntkme commented Feb 19, 2023

Is it possible to add a test regarding this? (I am on a Windows machine and therefore cannot test this manually).

Because this involves forking, we will have to write a test to actually launch a real Jekyll process, capture and parse the pid from stdout, and then check main pid has stopped with exit code 0 and new pid is running.

Also, curious about the effect when user locks to older version of the sass-converter that doesn't involve sass-embedded.

After forking, the old main process will exit normally as it has nothing more to do. The old version does not have any at_exit hook so this PR basically has no impact to it.

@ashmaroli
Copy link
Copy Markdown
Member

Because this involves forking, we will have to write a test to actually launch a real Jekyll process, capture and parse the pid from stdout, and then check main pid has stopped with exit code 0 and new pid is running.

Yes @ntkme, that is the basic idea. Will you be able to translate that into code? If the current implementation (of the lib code) doesn't have a suitable interface for testing, you may extract the entire conditional branch into a new (preferably private) method for easier testing.

@ntkme ntkme changed the title Explicitly exit main pid without running at_exit hooks when --detach Fix jekyll serve --detach with jekyll-sass-converter 3.x Feb 19, 2023
@ntkme
Copy link
Copy Markdown
Contributor Author

ntkme commented Feb 19, 2023

@ashmaroli Test added. Also fixed another issue discovered in test when --detach is issued from non-tty, that stdio inherited from main process would cause a deadlock on test process reading as the file descriptors remains open in the forked process after main process exits.

Comment on lines +277 to +280
# Detach the process from controlling terminal
$stdin.reopen("/dev/null", "r")
$stdout.reopen("/dev/null", "w")
$stderr.reopen("/dev/null", "w")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to switch this out with the Open3 module?

Copy link
Copy Markdown
Contributor Author

@ntkme ntkme Feb 20, 2023

Choose a reason for hiding this comment

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

If we want to have a simpler or cleaner implementation we have to change the semantics of how this work.

Currently, --detach runs these steps:

  1. Build the site.
  2. Fork into child process and start server.
  3. Print the child process PID.
  4. Exit main process.

We have several options but they would change the semantics:

The simplest solution is to use Process.daemon. It is a single call that does fork and detach stdio then continue rest of the code in forked process. However:

  • If we call Process.daemon on main, the problem is that we cannot get and print PID in main process. A typical practice of using this method would be adding a --pid-file option to let forked process to write its PID into a file.
  • We can also call Process.daemon in the forked process to use it for detach stdio, the problem is that it would fork again and change PID, which is why I had to manually do that by reopen stdio on /dev/null. For reference, here is CRuby's implementation in rb_daemon for doing the same thing: https://github.com/ruby/ruby/blob/976cc3852b00560e48fe89b9c5ab77761e1d8f3b/process.c#L7111-L7115

Therefore, if we stick with fork based implementation, this PR is the best we can do unless we agree to change the semantics of how this option works today.

Another option would be replace the fork with a real ruby process that just runs webrick, with two different possible implementations:

  1. Serialize webrick options and start webrick with a ruby one-liner using Process.spawn (or open3). Problem is that we have some Ruby code that customize webrick behavior which makes serialization difficult.
  2. Spawn a Jekyll process with --skip-initial-build with the same ARGV that was originally used, so that it just start the server. Problem is that today we don't keep original ARGV after processing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A huge thank you for giving me a detailed, well-thought out reply, @ntkme 🙏 🙇

Since I don't intend to change much about this, especially since this is particularly unfamiliar territory for me, the current implementation here will do.

@ashmaroli
Copy link
Copy Markdown
Member

LGTM!
Requesting a third set of 👀 from either @mattr- or @parkr for final review.

@ntkme ntkme requested a review from parkr February 21, 2023 21:48
pid = re.match(output)["pid"].to_i
assert pid > 1

Process.kill 9, pid
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This will raise if the process has already exited but that's fine since we'd likely want a test failure anyway.

@parkr
Copy link
Copy Markdown
Member

parkr commented Feb 21, 2023

@jekyllbot: merge +bug

@jekyllbot jekyllbot merged commit 16a1e5c into jekyll:master Feb 21, 2023
jekyllbot added a commit that referenced this pull request Feb 21, 2023
github-actions bot pushed a commit that referenced this pull request Feb 21, 2023
なつき: Fix `jekyll serve --detach` with jekyll-sass-converter 3.x (#9304)

Merge pull request 9304
@ntkme ntkme deleted the daemon branch February 21, 2023 23:00
@jekyll jekyll locked and limited conversation to collaborators Feb 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: serve --detach doesn't detach

4 participants