grpc_fork_safety 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []