rack-freeze 1.1.0 → 1.2.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 +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
|