[#103135] [Ruby master Feature#17768] Proposal: Downward assignments — mame@...

Issue #17768 has been reported by mame (Yusuke Endoh).

10 messages 2021/04/01

[#103162] [Ruby master Feature#17773] Alias `Numeric#zero?` and `Float#zero?` as `Numeric#empty?` and `Float#empty?` — sawadatsuyoshi@...

Issue #17773 has been reported by sawa (Tsuyoshi Sawada).

9 messages 2021/04/02

[#103241] [Ruby master Bug#17777] 2.6.7 fails to build on macOS: implicit declaration of function 'rb_native_mutex_destroy' is invalid in C99 — eregontp@...

Issue #17777 has been reported by Eregon (Benoit Daloze).

17 messages 2021/04/05

[#103280] [Ruby master Bug#17781] Resolv::DNS RequestID table allocations are never freed, causing DNS lookups to eventually hang — supermathie@...

Issue #17781 has been reported by supermathie (Michael Brown).

9 messages 2021/04/07

[#103305] [Ruby master Feature#17785] Allow named parameters to be keywords — marcandre-ruby-core@...

Issue #17785 has been reported by marcandre (Marc-Andre Lafortune).

21 messages 2021/04/08

[#103310] [Ruby master Feature#17786] Proposal: new "ends" keyword — jzakiya@...

Issue #17786 has been reported by jzakiya (Jabari Zakiya).

13 messages 2021/04/08

[#103317] [Ruby master Bug#17787] Four AIX build issues with xlc compiler and ruby-3.0.1 — lamont@...

Issue #17787 has been reported by lamont (Lamont Granquist).

9 messages 2021/04/08

[#103342] [Ruby master Feature#17790] Have a way to clear a String without resetting its capacity — jean.boussier@...

Issue #17790 has been reported by byroot (Jean Boussier).

14 messages 2021/04/09

[#103386] [Ruby master Bug#17793] `shorten-64-to-32` error for 32-bit Android due to `struct stat` definition — xtkoba+ruby@...

Issue #17793 has been reported by xtkoba (Tee KOBAYASHI).

8 messages 2021/04/11

[#103400] [Ruby master Feature#17795] `before_fork` and `after_fork` callback API — jean.boussier@...

Issue #17795 has been reported by byroot (Jean Boussier).

42 messages 2021/04/12

[#103434] [Ruby master Bug#17799] Seg fault in rb_class_clear_method_cache — stanhu@...

Issue #17799 has been reported by stanhu (Stan Hu).

14 messages 2021/04/13

[#103481] [Ruby master Feature#17808] Feature Request: JS like splat of Object properties as named method parameters — brad.krane@...

Issue #17808 has been reported by Lithium (Brad Krane).

8 messages 2021/04/16

[#103556] [Ruby master Bug#17820] `Errno::EINVAL` from `Process.kill` with available signal on Windows — alex.wayfer@...

Issue #17820 has been reported by AlexWayfer (Alexander Popov).

9 messages 2021/04/22

[#103591] [Ruby master Bug#17827] Monitor is not fiber safe — samuel@...

Issue #17827 has been reported by ioquatix (Samuel Williams).

11 messages 2021/04/25

[#103593] [Ruby master Misc#17828] Deprecate use of master and slave — yyoshida.at.work@...

Issue #17828 has been reported by [email protected] (Yasuhiro Yoshida).

10 messages 2021/04/26

[#103596] [Ruby master Feature#17830] Add Integer#previous and Integer#prev — rafasoaresms@...

Issue #17830 has been reported by rafasoares (Rafael Soares).

9 messages 2021/04/26

[#103631] [Ruby master Feature#17837] Add support for Regexp timeouts — sam.saffron@...

Issue #17837 has been reported by sam.saffron (Sam Saffron).

45 messages 2021/04/27

[ruby-core:103482] [Ruby master Feature#17795] Around `Process.fork` callbacks API

From: ivo.anjo@...
Date: 2021-04-16 13:28:12 UTC
List: ruby-core #103482
Issue #17795 has been updated by ivoanjo (Ivo Anjo).


At Datadog, we use our monkey patching (mentioned in the description) to restart background threads that are used to periodically gather metrics. We use this so that we can transparently gather (profiling) metrics on all processes in Ruby apps that employ forking.

Thus, "passive" solutions -- checking the pid / fork level -- don't work for our use case. Hence why we're really really interested in the Fork callbacks / Make Kernel.fork a delegator solution.

----------------------------------------
Feature #17795: Around `Process.fork` callbacks API
https://bugs.ruby-lang.org/issues/17795#change-91580

* Author: byroot (Jean Boussier)
* Status: Open
* Priority: Normal
----------------------------------------
Replaces: https://bugs.ruby-lang.org/issues/5446

### Context

Ruby code in production is very often running in a forking setup (puma, unicorn, etc), and it is common some types of libraries to need to know when the Ruby process was forked. For instance:

  - Most database clients, ORMs or other libraries keeping a connection pool might need to close connections before the fork happens.
  - Libraries relying on some kind of dispatcher thread might need to restart the thread in the forked children, and clear any internal buffer (e.g. statsd clients, newrelic_rpm).

**This need is only for forking the whole ruby process, extensions doing a `fork(2) + exec(2)` combo etc are not a concern, this aim at only catching `kernel.fork`, `Process.fork` and maybe `Process.daemon`.**.
The use case is for forks that end up executing Ruby code.

### Current solutions

Right now this use case is handled in several ways.

#### Rely on the integrating code to call a `before_fork` or `after_fork` callback.

Some libraries simply rely on documentation and require the user to use the hooks provided by their forking server.

Examples:

  - Sequel: http://sequel.jeremyevans.net/rdoc/files/doc/fork_safety_rdoc.html
  - Rails's Active Record: https://devcenter.heroku.com/articles/concurrency-and-database-connections#multi-process-servers
  - ScoutAPM (it tries to detect popular forking setup and register itself): https://github.com/scoutapp/scout_apm_ruby/blob/fa83793b9e8d2f9a32c920f59b57d7f198f466b8/lib/scout_apm/environment.rb#L142-L146
  - NewRelic RPM (similarly tries to register to popular forking setups): https://www.rubydoc.info/github/newrelic/rpm/NewRelic%2FAgent:after_fork


#### Continuously check `Process.pid`

Some libraries chose to instead keep the process PID in a variable, and to regularly compare it to `Process.pid` to detect forked children.
Unfortunately `Process.pid` is relatively slow on Linux, and these checks tend to be in tight loops, so it's not uncommon when using these libraries
to spend `1` or `2%` of runtime in `Process.pid`.

Examples:

  - Rails's Active Record used to check `Process.pid` https://github.com/Shopify/rails/blob/411ccbdab2608c62aabdb320d52cb02d446bb39c/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L946, it still does but a bit less: https://github.com/rails/rails/pull/41850
  - the `Typhoeus` HTTP client: https://github.com/typhoeus/typhoeus/blob/a345545e5e4ac0522b883fe0cf19e5e2e807b4b0/lib/typhoeus/pool.rb#L34-L42
  - Redis client: https://github.com/redis/redis-rb/blob/6542934f01b9c390ee450bd372209a04bc3a239b/lib/redis/client.rb#L384
  - Some older versions of NewRelic RPM: https://github.com/opendna/scoreranking-api/blob/8fba96d23b4d3e6b64f625079c184f3a292bbc12/vendor/gems/ruby/1.9.1/gems/newrelic_rpm-3.7.3.204/lib/new_relic/agent/harvester.rb#L39-L41

#### Continuously check `Thread#alive?`

Similar to checking `Process.pid`, but for the background thread use case. `Thread#alive?` is regularly checked, and if the thread is dead, it is assumed that the process was forked.
It's much less costly than a `Process.pid`, but also a bit less reliable as the thread could have died for other reasons. It also delays re-creating the thread to the next check rather than immediately upon forking.

Examples:

  - `statsd-instrument`: https://github.com/Shopify/statsd-instrument/blob/0445cca46e29aa48e9f1efec7c72352aff7ec931/lib/statsd/instrument/batched_udp_sink.rb#L63

#### Decorate `Kernel.fork` and `Process.fork`

Another solution is to prepend a module in `Process` and `Kernel`, to decorate the fork method and implement your own callback. It works well, but is made difficult by `Kernel.fork`.


Examples:

  - Active Support: https://github.com/rails/rails/blob/9aed3dcdfea6b64c18035f8e2622c474ba499846/activesupport/lib/active_support/fork_tracker.rb
  - `dd-trace-rb`: https://github.com/DataDog/dd-trace-rb/blob/793946146b4709289cfd459f3b68e8227a9f5fa7/lib/ddtrace/profiling/ext/forking.rb
  - To some extent, `nakayoshi_fork` decorates the `fork` method: https://github.com/ko1/nakayoshi_fork/blob/19ef5efc51e0ae51d7f5f37a0b785309bf16e97f/lib/nakayoshi_fork.rb

### Proposals

I see two possible features to improve this situation:

####  Fork callbacks

One solution would be for Ruby to expose a callback API for these two events, similar to `Kernel.at_exit`.

Most implementations of this functionnality in other languages ([C's `pthread_atfork`](https://man7.org/linux/man-pages/man3/pthread_atfork.3.html), [Python's `os.register_at_fork`](https://docs.python.org/3/library/os.html#os.register_at_fork)) expose 3 callbacks:

  - `prepare` or `before` executed in the parent process before the `fork(2)`
  - `parent` or `after_in_parent` executed in the parent process after the `fork(2)`
  - `child` or `after_in_child` executed in the child process after the `fork(2)`

A direct translation of such API in Ruby could look like `Process.at_fork(prepare: Proc, parent: Proc, child: Proc)` if inspired by `pthread_atfork`.

Or alternatively each callback could be exposed idependently: `Process.before_fork {}`, `Process.after_fork_parent {}`, `Process.after_fork_child {}`.

Also note that similar APIs don't expose any way to unregister callbacks, and expect users to use weak references or to not hold onto objects that should be garbage collected.

Pseudo code:

```ruby
module Process
  @prepare = []
  @parent = []
  @child = []

  def self.at_fork(prepare: nil, parent: nil, child: nil)
    @prepare.unshift(prepare) if prepare # prepare callbacks are executed in reverse registration order
    @parent << parent if parent
    @child << child if child
  end

  def self.fork
    @prepare.each(&:call)
    if pid = Primitive.fork
      @parent.each(&:call) # We could consider passing the pid here.
    else
      @child.each(&:call)
    end
  end
end

```

#### Make `Kernel.fork` a delegator

A simpler change would be to just make `Kernel.fork` a delegator to `Process.fork`. This would make it much easier to prepend a module on `Process` for each library to implement its own callback.

Proposed patch: https://github.com/ruby/ruby/pull/4361



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:[email protected]?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>

In This Thread