rlimit 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.
@@ -0,0 +1,110 @@
1
+ Ever needed to adjust system limits in Ruby? If so, congratulations!
2
+ You're in an infinitesimally-small minority. But at least now you can do
3
+ it. Unless you're on Windows. Then you're still on your own.
4
+
5
+
6
+ # Installation
7
+
8
+ It's a gem:
9
+
10
+ gem install rlimit
11
+
12
+ If you're the sturdy type that likes to run from git:
13
+
14
+ rake build; gem install pkg/rlimit-<whatever>.gem
15
+
16
+ Or, if you've eschewed the convenience of Rubygems, then you presumably know
17
+ what to do already.
18
+
19
+
20
+ # Usage
21
+
22
+ There are two main methods available to you. To read a resource limit, use
23
+ `RLimit.get`, passing it one of the available `RLimit::<TYPE>` constants
24
+ (more on that later), and optionally one of the symbols `:hard` or `:soft`:
25
+
26
+ >> RLimit.get(RLimit::NOFILE)
27
+ => [1024, 65536]
28
+
29
+ >> RLimit.get(RLimit::NOFILE, :soft)
30
+ => 1024
31
+
32
+ >> RLimit.get(RLimit::NOFILE, :hard)
33
+ => 65536
34
+
35
+ >> RLimit.get(RLimit::CPU)
36
+ => [:unlimited, :unlimited]
37
+
38
+ >> RLimit.get(RLimit::NOFILE, :lolidunno)
39
+ ArgumentError: Unknown limit type :lolidunno
40
+
41
+ >> RLimit.get("ohai!")
42
+ ArgumentError: Invalid rlimit resource specifier "ohai!"
43
+
44
+ A limit value is represented as either a non-negative integer, or the symbol
45
+ `:unlimited`, to indicate that there is no limit on the resource. There are
46
+ two possible return value "patterns", too -- if you don't specify a limit
47
+ type, you get back a two-element array `[<soft>, <hard>]`, whereas if you
48
+ ask for one specific type of limit, you get back a scalar. If you're
49
+ adventurous enough to try and pass an invalid argument, well, you get an
50
+ `ArgumentError` for your troubles.
51
+
52
+ To set a resource limit, you've got `RLimit.set`:
53
+
54
+ # Set just a soft limit
55
+ >> RLimit.set(RLimit::NOFILE, 64)
56
+ => true
57
+
58
+ # Set just a hard limit
59
+ >> RLimit.set(RLimit::NOFILE, nil, 128)
60
+ => true
61
+
62
+ # Set both soft and hard limits
63
+ >> RLimit.set(RLimit::NOFILE, 64, 128)
64
+ => true
65
+
66
+ # Set an unlimited soft limit
67
+ >> RLimit.set(RLimit::CORE, :unlimited)
68
+ => true
69
+
70
+ # Try to increase a hard limit when not root
71
+ >> RLimit.set(RLimit::NOFILE, nil, 1048576)
72
+ RLimit::PermissionDenied: You do not have permission to raise hard limits
73
+
74
+ # Try to increase a soft limit above the hard limit
75
+ >> RLimit.set(RLimit::NOFILE, 128, 64)
76
+ RLimit::HardLimitExceeded: You cannot raise the soft limit above 64
77
+
78
+ Hopefully that should all be fairly self-explanatory. If not, well, there's
79
+ more detailed documentation in the RDoc.
80
+
81
+
82
+ ## Available resource types
83
+
84
+ There is no guaranteed set of resource types which are available on all
85
+ systems. To discover what is available on your system, call
86
+ `RLimit.resources`, this will return an array containing the constants that
87
+ are defined. You should reference `setrlimit`(2) for your system to
88
+ determine exactly what they all mean.
89
+
90
+ If you wish to determine at runtime whether RLimit on your system supports a
91
+ particular resource, you can use `RLimit.supports?("RLIMIT_<res>")` -- or
92
+ just try to work with it anyway and rescue `ArgumentError`....
93
+
94
+
95
+ ## Soft and Hard Limits
96
+
97
+ The "soft" limit for an rlimit is the value that a process is currently
98
+ restricted to; an attempt to exceed that limit will result in some sort of
99
+ failure. However, a process can itself request to increase the value of a limit
100
+ up to the "hard" limit. Only a process owned by `root` (or which has been
101
+ granted the `CAP_SYS_RESOURCE` capability, on systems that support such a
102
+ thing) can increase the hard limit.
103
+
104
+
105
+ # Contributing
106
+
107
+ Bug reports should be sent to the [Github issue
108
+ tracker](https://github.com/mpalmer/rlimit-gem/issues), or
109
+ [e-mailed](mailto:theshed+rlimit@hezmatt.org). Patches can be sent as a
110
+ Github pull request, or [e-mailed](mailto:theshed+rlimit@hezmatt.org).
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ task :default => :test
5
+
6
+ begin
7
+ Bundler.setup(:default, :development)
8
+ rescue Bundler::BundlerError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Run `bundle install` to install missing gems"
11
+ exit e.status_code
12
+ end
13
+
14
+ require 'git-version-bump/rake-tasks'
15
+
16
+ Bundler::GemHelper.install_tasks
17
+
18
+ task :release do
19
+ sh "git release"
20
+ end
21
+
22
+ require 'rdoc/task'
23
+
24
+ RDoc::Task.new do |rd|
25
+ rd.main = "README.md"
26
+ rd.title = 'rlimit'
27
+ rd.markup = "markdown"
28
+ rd.rdoc_files.include("README.md", "lib/**/*.rb")
29
+ end
30
+
31
+ desc "Run guard"
32
+ task :guard do
33
+ require 'guard'
34
+ ::Guard.start(:clear => true)
35
+ while ::Guard.running do
36
+ sleep 0.5
37
+ end
38
+ end
39
+
40
+ require 'rspec/core/rake_task'
41
+ RSpec::Core::RakeTask.new :test do |t|
42
+ t.pattern = "spec/**/*_spec.rb"
43
+ end
@@ -0,0 +1,290 @@
1
+ require 'ffi'
2
+ require 'ffi/tools/const_generator'
3
+
4
+ module RLimit
5
+ # Exception raised when you attempt to raise the soft limit beyond the
6
+ # hard limit. This can happen either because you've specified both soft
7
+ # and hard limits, and `soft > hard`, or because you're just specifying
8
+ # the soft limit, but it is larger than the existing hard limit.
9
+ #
10
+ # If you wish to set the soft limit higher than the hard limit, you'll
11
+ # need to raise the hard limit, too (which you can only do if you're
12
+ # root, or have the CAP_SYS_RESOURCE capability).
13
+ #
14
+ class HardLimitExceeded < ::StandardError; end
15
+
16
+ # You attempted to raise the hard limit on a resource, but you do not have
17
+ # the appropriate permissions (typically you need to be root, or have the
18
+ # CAP_SYS_RESOURCE capability).
19
+ #
20
+ class PermissionDenied < ::StandardError; end
21
+
22
+ # Return an array of strings representing the available RLIMIT_* constants
23
+ # that are available on your system.
24
+ def self.resources
25
+ @resources ||= (self.constants.map { |c| c.to_s } & POSSIBLE_RESOURCES).map { |c| "RLIMIT_#{c}" }
26
+ end
27
+
28
+ # Answer the question, "Does RLimit on my system support resource
29
+ # `<X>`?". You can specify `<X>` as either a string or a symbol, with or
30
+ # without a leading `RLIMIT_`, or as an integer. So any of the following
31
+ # should work (assuming your system supports `RLIMIT_NOFILE`, and
32
+ # `RLIMIT_NOFILE` is `7`):
33
+ #
34
+ # * `RLimit.supports?(:NOFILE)`
35
+ # * `RLimit.supports?("NOFILE")`
36
+ # * `RLimit.supports?(:RLIMIT_NOFILE)`
37
+ # * `RLimit.supports?("RLIMIT_NOFILE")`
38
+ # * `RLimit.supports?(7)`
39
+ #
40
+ def self.supports?(res)
41
+ begin
42
+ !!get(res)
43
+ rescue ArgumentError
44
+ false
45
+ end
46
+ end
47
+
48
+ # Retrieve the limits of the specified resource.
49
+ #
50
+ # `resource` can be any of the resource constants provided under `RLimit`,
51
+ # or a symbol or string form of an `RLIMIT_*` constant (with or without
52
+ # the leading `RLIMIT_`).
53
+ #
54
+ # `limit_type`, if specified, must be one of the symbols `:soft` or `:hard`.
55
+ #
56
+ # On success, this method returns one of the following:
57
+ #
58
+ # * **If `limit_type` is specified:** the return value from this method
59
+ # will be a non-negative integer less than `2**32`, or the symbol
60
+ # `:unlimited` (guess what that means!).
61
+ #
62
+ # * **If `limit_type` is not specified:** the return value will be a
63
+ # two-element array, consisting of the soft limit followed by the hard
64
+ # limit, each of which is either a non-negative integer less than
65
+ # `2**32`, or the symbol `:unlimited`.
66
+ #
67
+ # On error, this method can raise:
68
+ #
69
+ # * `ArgumentError` if `resource` isn't a valid resource specifier, or
70
+ # `limit_type` isn't a valid limit type (`:soft` `:hard`, or
71
+ # unspecified).
72
+ #
73
+ # * `RuntimeError` if one of a couple of "can't happen" events do
74
+ # actually occur.
75
+ #
76
+ def self.get(resource, limit_type = nil)
77
+ resource = res_xlat(resource)
78
+
79
+ rlim = RLimit::FFI::RLimitStruct.new
80
+
81
+ if RLimit::FFI.getrlimit(resource, rlim.pointer) != 0
82
+ raise_errno
83
+ end
84
+
85
+ case limit_type
86
+ when nil then [rlim_xlat(rlim[:rlim_cur]), rlim_xlat(rlim[:rlim_max])]
87
+ when :soft then rlim_xlat(rlim[:rlim_cur])
88
+ when :hard then rlim_xlat(rlim[:rlim_max])
89
+ else raise ArgumentError,
90
+ "Unknown limit type #{limit_type.inspect}"
91
+ end
92
+ end
93
+
94
+ # Change the limits of the specified resource.
95
+ #
96
+ # `resource` can be any of the resource constants provided under `RLimit`,
97
+ # or a symbol or string form of an `RLIMIT_*` constant (with or without
98
+ # the leading `RLIMIT_`).
99
+ #
100
+ # `soft_limit` and `hard_limit` can be a non-negative integer less than
101
+ # `2**32`, the symbol `:unlimited`, or `nil` (meaning "no change"). Not
102
+ # specifying `hard_limit` is equivalent to setting it to `nil` (hence only
103
+ # the soft limit will be modified).
104
+ #
105
+ # Note that you can always *lower* a hard limit, but once lowered, it cannot
106
+ # be raised again unless you have appropriate permissions.
107
+ #
108
+ # On success, this method returns `true`.
109
+ #
110
+ # On error, one of the following exceptions will be raised:
111
+ #
112
+ # * `ArgumentError` if `resource` isn't a valid resource specifier, or
113
+ # either of `soft_limit` or `hard_limit` aren't valid values.
114
+ #
115
+ # * `RLimit::HardLimitExceeded` if you attempt to set the soft limit to
116
+ # a value greater than the hard limit -- either because you set both
117
+ # limits, and `soft_limit > hard_limit`, or else you just tried to set
118
+ # the soft limit, but it was larger than the existing hard limit.
119
+ #
120
+ # * `RLimit::PermissionDenied` if you tried to *raise* the hard limit
121
+ # without having the appropriate permissions to do so.
122
+ #
123
+ # * `RuntimeError` if one of a couple of "can't happen" events do
124
+ # actually occur. That means someone's going to have a bad day.
125
+ #
126
+ def self.set(resource, soft_limit, hard_limit = nil)
127
+ resource = res_xlat(resource)
128
+
129
+ soft_limit = rlim_xlat(soft_limit)
130
+ hard_limit = rlim_xlat(hard_limit)
131
+
132
+ unless hard_limit.nil? or
133
+ hard_limit.is_a?(Integer) or
134
+ hard_limit < 0 or
135
+ hard_limit >= 2**32
136
+ raise ArgumentError,
137
+ "Invalid hard limit value: #{soft_limit.inspect}"
138
+ end
139
+
140
+ rlim = RLimit::FFI::RLimitStruct.new
141
+ rlim[:rlim_cur], rlim[:rlim_max] = self.get(resource)
142
+
143
+ if soft_limit
144
+ rlim[:rlim_cur] = soft_limit
145
+ end
146
+
147
+ if hard_limit
148
+ rlim[:rlim_max] = hard_limit
149
+ end
150
+
151
+ if rlim[:rlim_cur] > rlim[:rlim_max]
152
+ raise RLimit::HardLimitExceeded,
153
+ "You cannot set the soft limit above #{rlim[:rlim_max]}"
154
+ end
155
+
156
+ if RLimit::FFI.setrlimit(resource, rlim.pointer) != 0
157
+ raise_errno
158
+ end
159
+
160
+ true
161
+ end
162
+
163
+ #:nodoc:
164
+ # Handle the translation between :unlimited and RLimit::RLIM_INFINITY
165
+ # Since both are invalid *actual* values, we can use the same method
166
+ # (and logic) to go in both directions. We can also sanity-check
167
+ # integer values here, too. It's the all-in-one party method!
168
+ def self.rlim_xlat(l)
169
+ unless l.nil? or
170
+ l == :unlimited or
171
+ l.is_a?(Integer) or
172
+ l < 0 or
173
+ (l >= 2**32 and l != RLimit::RLIM_INFINITY)
174
+ raise ArgumentError,
175
+ "Invalid soft limit value: #{soft_limit.inspect}"
176
+ end
177
+
178
+ if l == RLimit::RLIM_INFINITY
179
+ :unlimited
180
+ elsif l == :unlimited
181
+ RLimit::RLIM_INFINITY
182
+ else
183
+ l
184
+ end
185
+ end
186
+
187
+ #:nodoc:
188
+ # Take something that may or may not be a valid-looking resource
189
+ # specifier, and turn it into an integer that could well be a valid valid
190
+ # for {get,set}rlimit. Raise all sorts of ArgumentError if we can't work
191
+ # out what's going on.
192
+ def self.res_xlat(r)
193
+ err = "Invalid rlimit resource specifier #{r.inspect}"
194
+
195
+ unless r.is_a? Integer
196
+ r = r.to_s.gsub(/^RLIMIT_/, '').to_sym
197
+ begin
198
+ if self.const_defined?(r)
199
+ r = self.const_get(r)
200
+ else
201
+ raise ArgumentError, err
202
+ end
203
+ rescue NameError
204
+ raise ArgumentError, err
205
+ end
206
+ end
207
+
208
+ unless r.is_a? Integer
209
+ raise ArgumentError, err
210
+ end
211
+
212
+ r
213
+ end
214
+
215
+ #:nodoc:
216
+ # Inspect errno and raise the appropriate exception.
217
+ def self.raise_errno
218
+ case ::FFI.errno
219
+ when Errno::EFAULT::Errno
220
+ raise RuntimeError,
221
+ "getrlimit detected pointer outside of addressable space. WTF?"
222
+ when Errno::EINVAL::Errno
223
+ raise ArgumentError,
224
+ "Invalid rlimit resource specifier #{resource.inspect}"
225
+ when Errno::EPERM::Errno
226
+ raise RLimit::PermissionDenied,
227
+ "You do not have permission to raise hard limits"
228
+ else
229
+ raise RuntimeError,
230
+ "Unknown errno returned: #{::FFI.errno}"
231
+ end
232
+ end
233
+
234
+ # The FFI-related internals of our little shindig. Here be dragons.
235
+ #:nodoc:all
236
+ module FFI #:nodoc:all
237
+ extend ::FFI::Library
238
+ ffi_lib ::FFI::Library::LIBC
239
+
240
+ def self.rlim_t
241
+ @rlim_t ||= ::FFI.find_type(:rlim_t)
242
+ end
243
+
244
+ class RLimitStruct < ::FFI::Struct
245
+ layout :rlim_cur, RLimit::FFI.rlim_t,
246
+ :rlim_max, RLimit::FFI.rlim_t
247
+ end
248
+
249
+ attach_function :setrlimit, [ :int, :pointer ], :int
250
+ attach_function :getrlimit, [ :int, :pointer ], :int
251
+ end
252
+
253
+ #:nodoc:
254
+ # This is the list of all *possible* resources that can be defined; it
255
+ # isn't everything that's available on this system. It's been
256
+ # constructed by grovelling through the manpages for `setrlimit`(2) on a
257
+ # number of different OSes; additions welcomed.
258
+ POSSIBLE_RESOURCES = %w{
259
+ AS
260
+ CORE
261
+ CPU
262
+ DATA
263
+ FSIZE
264
+ LOCKS
265
+ MEMLOCK
266
+ MSGQUEUE
267
+ NICE
268
+ NOFILE
269
+ NPROC
270
+ RSS
271
+ RTPRIO
272
+ RTTIME
273
+ SBSIZE
274
+ SIGPENDING
275
+ STACK
276
+ SWAP
277
+ NPTS
278
+ }
279
+
280
+ cg = ::FFI::ConstGenerator.new("rlimit") do |cg|
281
+ cg.include("sys/resource.h")
282
+ cg.const("RLIM_INFINITY", '%llu')
283
+
284
+ POSSIBLE_RESOURCES.each do |r|
285
+ cg.const("RLIMIT_#{r}", nil, '', r)
286
+ end
287
+ end
288
+
289
+ eval cg.to_ruby
290
+ end
@@ -0,0 +1,31 @@
1
+ require 'git-version-bump'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "rlimit"
5
+
6
+ s.version = GVB.version
7
+ s.date = GVB.date
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+
11
+ s.homepage = "http://theshed.hezmatt.org/rlimit"
12
+ s.summary = "Retrieve and adjust rlimits"
13
+ s.authors = ["Matt Palmer"]
14
+
15
+ s.extra_rdoc_files = ["README.md"]
16
+ s.files = `git ls-files`.split("\n")
17
+
18
+ s.add_runtime_dependency "git-version-bump", "~> 0.10"
19
+ s.add_runtime_dependency "ffi", "~> 1.9"
20
+
21
+ s.add_development_dependency 'bundler'
22
+ s.add_development_dependency 'github-release'
23
+ s.add_development_dependency 'guard-spork'
24
+ s.add_development_dependency 'guard-rspec'
25
+ # Needed for guard
26
+ s.add_development_dependency 'rb-inotify', '~> 0.9'
27
+ s.add_development_dependency 'pry-debugger'
28
+ s.add_development_dependency 'rake'
29
+ s.add_development_dependency 'rdoc'
30
+ s.add_development_dependency 'rspec'
31
+ end