rack-freeze 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -2
- data/Rakefile +1 -7
- data/lib/rack/freeze.rb +3 -2
- data/lib/rack/freeze/builder.rb +5 -3
- data/lib/rack/freeze/freezer.rb +9 -19
- data/lib/rack/freeze/version.rb +1 -1
- metadata +2 -3
- data/.simplecov +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bc56dafbfc276a4b560e73d0043a29a42197022
|
4
|
+
data.tar.gz: 7c17bb6e88b2b6cf4183b278a9d5e46bef0fb429
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c42c134837c30103be30583c162d96e9ee70d6815aa09dfd8452a8d968aab7039b86c158854a6b607c4e461d9a2c54e8004cbd9aaa7707f4cadf4f82c95ae585
|
7
|
+
data.tar.gz: ef9492576931fabef0b648e6cb441c537de7fdcca8eefdda294b1f31cd861054ca634319a01f309885ea16d4ff15d6847df290793b7ed3bd57cef081ca03a48b
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ Now all your middleware will be frozen by default.
|
|
32
32
|
|
33
33
|
### What bugs does this fix?
|
34
34
|
|
35
|
-
It guarantees
|
35
|
+
It guarantees as much as is possible, that middleware won't mutate during a request.
|
36
36
|
|
37
37
|
```ruby
|
38
38
|
# This modifies `Rack::Builder#use` and `Rack::Builder#to_app` to generate a frozen stack of middleware.
|
@@ -54,7 +54,39 @@ end
|
|
54
54
|
use NonThreadSafeMiddleware
|
55
55
|
```
|
56
56
|
|
57
|
-
As `NonThreadSafeMiddleware` mutates it's state `@state += 1`, it will raise a `RuntimeError`. In a multi-threaded web-server, unprotected mutation of internal state will lead to undefined behavior.
|
57
|
+
As `NonThreadSafeMiddleware` mutates it's state `@state += 1`, it will raise a `RuntimeError`. In a multi-threaded web-server, unprotected mutation of internal state will lead to undefined behavior. [5 out of 4 dentists agree that multi-threaded programming is hard to get right](http://www.rubyinside.com/does-the-gil-make-your-ruby-code-thread-safe-6051.html).
|
58
|
+
|
59
|
+
### How to write thread-safe middleware?
|
60
|
+
|
61
|
+
There are two options: Don't mutate state, or if you need to for the purposes of performance, implement `#freeze` and use data-structures from [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'concurrent/map'
|
65
|
+
|
66
|
+
# Cache every request based on the path. Don't do this in production :)
|
67
|
+
class CacheEverythingForever
|
68
|
+
def initialize(app)
|
69
|
+
@app = app
|
70
|
+
@cache_all_the_things = Concurrent::Map.new
|
71
|
+
end
|
72
|
+
|
73
|
+
# Because you supply your own implementation of #freeze, Rack::Freeze won't touch this middleware.
|
74
|
+
def freeze
|
75
|
+
return self if frozen?
|
76
|
+
|
77
|
+
@app.freeze
|
78
|
+
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def call(env)
|
83
|
+
# Use the thread-safe `Concurrent::Map` to fetch the value or store it if it doesn't exist already.
|
84
|
+
@cache_all_the_things.fetch_or_store(env[Rack::PATH_INFO]) do
|
85
|
+
@app.call(env)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
58
90
|
|
59
91
|
## Contributing
|
60
92
|
|
data/Rakefile
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
5
|
-
begin
|
6
|
-
require('simplecov/version')
|
7
|
-
task.rspec_opts = %w{--require simplecov} if ENV['COVERAGE']
|
8
|
-
rescue LoadError
|
9
|
-
end
|
10
|
-
end
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
11
5
|
|
12
6
|
task :default => :spec
|
data/lib/rack/freeze.rb
CHANGED
data/lib/rack/freeze/builder.rb
CHANGED
@@ -25,7 +25,11 @@ module Rack
|
|
25
25
|
module Freeze
|
26
26
|
module Builder
|
27
27
|
def use(klass, *args, &block)
|
28
|
-
super(
|
28
|
+
super Freezer.new(klass), *args, &block
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(app)
|
32
|
+
super app.freeze
|
29
33
|
end
|
30
34
|
|
31
35
|
def to_app
|
@@ -33,6 +37,4 @@ module Rack
|
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
36
|
-
|
37
|
-
Builder.prepend(Freeze::Builder)
|
38
40
|
end
|
data/lib/rack/freeze/freezer.rb
CHANGED
@@ -20,28 +20,18 @@
|
|
20
20
|
|
21
21
|
module Rack
|
22
22
|
module Freeze
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# Generate a subclass with a generic #freeze method to freeze all instance variables.
|
29
|
-
def self.[] klass
|
30
|
-
# Check if the class already has a custom implementation of #freeze.. which we assume works correctly.
|
31
|
-
return klass if implements_freeze?(klass)
|
23
|
+
class Freezer
|
24
|
+
def initialize(klass)
|
25
|
+
@klass = klass
|
26
|
+
end
|
32
27
|
|
33
|
-
|
34
|
-
|
35
|
-
# This ensures that all class variables are frozen.
|
36
|
-
self.instance_variables.each do |name|
|
37
|
-
self.instance_variable_get(name).freeze
|
38
|
-
end
|
39
|
-
|
40
|
-
super
|
41
|
-
end
|
28
|
+
def to_s
|
29
|
+
"#{self.class}<#{@klass}>"
|
42
30
|
end
|
43
31
|
|
44
|
-
|
32
|
+
def new(*args, &block)
|
33
|
+
@klass.new(*args, &block).freeze
|
34
|
+
end
|
45
35
|
end
|
46
36
|
end
|
47
37
|
end
|
data/lib/rack/freeze/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-freeze
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -75,7 +75,6 @@ extra_rdoc_files: []
|
|
75
75
|
files:
|
76
76
|
- ".gitignore"
|
77
77
|
- ".rspec"
|
78
|
-
- ".simplecov"
|
79
78
|
- ".travis.yml"
|
80
79
|
- Gemfile
|
81
80
|
- README.md
|