grpc_fork_safety 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cf32a7caf75ba7c10da05f6a07eb4fb071d8d1242947be0006d7c80d0789542e
4
+ data.tar.gz: d999967eb9c841a40ea919f507ed9e2c5eaaf17909ca6614ffe5686317b2bc25
5
+ SHA512:
6
+ metadata.gz: 630e682ecaaf7352ae3a7db2db4e3768f348e52fa06ed1d906743ae07caced67af02ec0d231846ea6016983bbe0dff3cb52f065da389c89c0ee8908c6ac2a573
7
+ data.tar.gz: 2f9c820ab4004b05dd5f752ca02557e3e92818dcbd959fe067758203d37dc4b10da04bd76070de1da298fb5ae9cc6507bd611a2830e65c3758f56253f29385df
data/.rubocop.yml ADDED
@@ -0,0 +1,28 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+ SuggestExtensions: false
4
+ NewCops: enable
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Style/IfUnlessModifier:
16
+ Enabled: false
17
+
18
+ Gemspec/RequireMFA:
19
+ Enabled: false
20
+
21
+ Metrics/MethodLength:
22
+ Enabled: false
23
+
24
+ Metrics/AbcSize:
25
+ Enabled: false
26
+
27
+ Metrics/ClassLength:
28
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-07-24
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # GrpcForkSafety
2
+
3
+ Small gem that makes it easier to use the `grpc` gem in a fork safe way.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your gemfile **before the `grpc` gem, or any gem that depend on `grpc`:
8
+
9
+ ```ruby
10
+ gem "grpc_fork_safety"
11
+ gem "grpc"
12
+ gem "some-gem-that-depend-on-grpc"
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ There isn't anything particular to do, the gem will hook itself into Ruby and properly call the GRPC fork hooks when needed.
18
+
19
+ ### `keep_disabled!`
20
+
21
+ However, when a process will need to fork repeatedly and won't need to use GRPC, you can optimize by calling `GrpcForkSafety.keep_disabled!`.
22
+ `grpc` will be enabled again in child process, but stay shutdown in the current process. This is useful for the main process of Puma or Unicorn
23
+ and for the mold process of Pitchfork, e.g.
24
+
25
+ ```ruby
26
+ before_fork do
27
+ GrpcForkSafety.keep_disabled!
28
+ end
29
+ ```
30
+
31
+ If for some reason you need to undo this, you can call `GrpcForkSafety.reenable!`
32
+
33
+ ### Hooks
34
+
35
+ You can also register hooks to be called before GRPC is disabled and after it's re-enabled:
36
+
37
+ ```ruby
38
+ GrpcForkSafety.before_disable do
39
+ ThreadPool.shutdown
40
+ end
41
+
42
+ GrpcForkSafety.after_enable do |in_child|
43
+ unless in_child
44
+ ThreadPool.start
45
+ end
46
+ end
47
+ ```
48
+
49
+ Typically if you have background threads using GRPC, you should make sure to shut them down in `before_disable`.
50
+
51
+ ## Development
52
+
53
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
54
+
55
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
56
+
57
+ ## Contributing
58
+
59
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopify/grpc_fork_safety.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "version"
4
+
5
+ if defined? GRPC::Core # ::GRPC may be loaded by bundler from `grpc.gemspec` so we check `GRPC::Core`
6
+ raise <<~ERROR
7
+ GRPC was loaded before `grpc_fork_safety` preventing to enable fork support
8
+
9
+ You may need to set `require: false` in your Gemfile where `grpc` is listed or
10
+ move `gem "grpc_fork_safety"` before `gem "grpc"`in your Gemfile.
11
+ ERROR
12
+ end
13
+
14
+ ENV["GRPC_ENABLE_FORK_SUPPORT"] = "1"
15
+ require "grpc"
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grpc_fork_safety/init"
4
+
5
+ module GrpcForkSafety
6
+ Error = Class.new(StandardError)
7
+
8
+ class LifeCycle
9
+ def initialize(grpc = ::GRPC, process = ::Process)
10
+ @grpc = grpc
11
+ @process = process
12
+ @shutdown_by = nil
13
+ @keep_disabled = false
14
+ @before_disable = []
15
+ @after_enable = []
16
+ end
17
+
18
+ def before_disable(&block)
19
+ raise Error, "No block given" unless block_given?
20
+
21
+ @before_disable << block
22
+ end
23
+
24
+ def after_enable(&block)
25
+ raise Error, "No block given" unless block_given?
26
+
27
+ @after_enable << block
28
+ end
29
+
30
+ def keep_disabled!
31
+ return if @keep_disabled
32
+
33
+ @keep_disabled = true
34
+ before_fork
35
+ end
36
+
37
+ def keep_disabled?
38
+ @keep_disabled
39
+ end
40
+
41
+ def reenable!
42
+ return unless @keep_disabled
43
+
44
+ @keep_disabled = false
45
+ after_fork
46
+ end
47
+
48
+ def before_fork
49
+ return if @shutdown_by
50
+
51
+ @before_disable.each(&:call)
52
+
53
+ @grpc.prefork
54
+ @shutdown_by = @process.pid
55
+ end
56
+
57
+ def after_fork
58
+ if @shutdown_by.nil?
59
+ # noop
60
+ elsif @shutdown_by == @process.pid # In parent
61
+ unless @keep_disabled
62
+ @grpc.postfork_parent
63
+ @shutdown_by = nil
64
+
65
+ @after_enable.each do |cb|
66
+ cb.call(false)
67
+ end
68
+ end
69
+ else
70
+ @keep_disabled = false
71
+ @shutdown_by = nil
72
+ @grpc.postfork_child
73
+ @after_enable.each do |cb|
74
+ cb.call(true)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ class NoopLifeCycle
81
+ def initialize
82
+ @keep_disabled = false
83
+ end
84
+
85
+ def keep_disabled!
86
+ @keep_disabled = true
87
+ end
88
+
89
+ def keep_disabled?
90
+ @keep_disabled
91
+ end
92
+
93
+ def reenable!
94
+ @keep_disabled = false
95
+ end
96
+
97
+ def before_fork; end
98
+
99
+ def after_fork; end
100
+
101
+ def before_disable; end
102
+
103
+ def after_enable; end
104
+ end
105
+
106
+ GRPC_FORK_SUPPORT = RUBY_PLATFORM.match?(/linux/i)
107
+
108
+ @lifecycle = if GRPC_FORK_SUPPORT
109
+ LifeCycle.new
110
+ else
111
+ NoopLifeCycle.new
112
+ end
113
+
114
+ class << self
115
+ def keep_disabled!
116
+ @lifecycle.keep_disabled!
117
+ end
118
+
119
+ def reenable!
120
+ @lifecycle.keep_disabled = false
121
+ @lifecycle.after_fork
122
+ end
123
+
124
+ def before_disable(&)
125
+ @lifecycle.before_disable(&)
126
+ end
127
+
128
+ def after_enable(&)
129
+ @lifecycle.after_enable(&)
130
+ end
131
+
132
+ def _before_fork_hook
133
+ @lifecycle.before_fork
134
+ end
135
+
136
+ def _after_fork_hook
137
+ @lifecycle.after_fork
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grpc_fork_safety/no_patch"
4
+
5
+ module GrpcForkSafety
6
+ module ProcessExtension
7
+ def _fork
8
+ GrpcForkSafety._before_fork_hook
9
+ pid = super
10
+ GrpcForkSafety._after_fork_hook
11
+ pid
12
+ end
13
+ end
14
+
15
+ class << self
16
+ def patch!
17
+ ::Process.singleton_class.prepend(ProcessExtension)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GrpcForkSafety
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grpc_fork_safety/patch"
4
+
5
+ GrpcForkSafety.patch!
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grpc_fork_safety
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jean Boussier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: grpc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.57.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.57.0
27
+ description:
28
+ email:
29
+ - jean.boussier@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rubocop.yml"
35
+ - ".ruby-version"
36
+ - CHANGELOG.md
37
+ - README.md
38
+ - Rakefile
39
+ - lib/grpc_fork_safety.rb
40
+ - lib/grpc_fork_safety/init.rb
41
+ - lib/grpc_fork_safety/no_patch.rb
42
+ - lib/grpc_fork_safety/patch.rb
43
+ - lib/grpc_fork_safety/version.rb
44
+ homepage: https://github.com/Shopify/grpc_fork_safety
45
+ licenses: []
46
+ metadata:
47
+ allowed_push_host: https://rubygems.org
48
+ homepage_uri: https://github.com/Shopify/grpc_fork_safety
49
+ source_code_uri: https://github.com/Shopify/grpc_fork_safety
50
+ changelog_uri: https://github.com/Shopify/grpc_fork_safety/blob/main/CHANGELOG.md
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 3.1.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.5.11
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Simple gems to facilitate making the grpc gem fork safe
70
+ test_files: []