rlimit 0.1.0

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