thread-local 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/lib/thread/local.md +81 -0
- data/lib/thread/local.rb +16 -4
- data/lib/thread/local/version.rb +1 -3
- metadata +11 -32
- data/.github/workflows/development.yml +0 -34
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/Gemfile +0 -4
- data/README.md +0 -85
- data/Rakefile +0 -6
- data/thread-local.gemspec +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f4032930cf7762d586307e1bb9ced4d69b2d3f72b5f1165c616df4df6a96d85
|
4
|
+
data.tar.gz: da327e71a8e4b8c7ad46aac6bbb3a8036142a6fee070db816dbec9dcf73861f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56bca34b696ab47635edbd9bfd9dd4099f59a57cccdfd795aae7b5eec739f7fb166282e383b9f546964be0251b3a4cdf08320a399f9e260c85e35cb6e762487a
|
7
|
+
data.tar.gz: 101f513ddd1a8b5900dc7ae68e7398dacd5e80418b8d9e755a130fb719c2658eb1c84dd7c25b3f8d728061bece1fb57b3baee430b7d29100308e87de08099f6a
|
data/lib/thread/local.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Thread::Local
|
2
|
+
|
3
|
+
Implements a standard interface for per-class/module thread local instances.
|
4
|
+
|
5
|
+
## Per-Class Instance
|
6
|
+
|
7
|
+
By default, classes are instantiated using `self.new`:
|
8
|
+
|
9
|
+
~~~ ruby
|
10
|
+
require 'thread/local'
|
11
|
+
|
12
|
+
class MyClass
|
13
|
+
extend Thread::Local
|
14
|
+
end
|
15
|
+
|
16
|
+
p MyClass.instance
|
17
|
+
~~~
|
18
|
+
|
19
|
+
### Extensions
|
20
|
+
|
21
|
+
If you need to change the behaviour of an existing thread local, you can prepend a module to wrap the call to `self.new`:
|
22
|
+
|
23
|
+
~~~ ruby
|
24
|
+
require 'thread/local'
|
25
|
+
|
26
|
+
class MyClass
|
27
|
+
extend Thread::Local
|
28
|
+
|
29
|
+
attr_accessor :data
|
30
|
+
end
|
31
|
+
|
32
|
+
module MyExtension
|
33
|
+
def local
|
34
|
+
instance = super
|
35
|
+
|
36
|
+
# Do something with the thread local instance, e.g.:
|
37
|
+
instance.data = [1, 2, 3]
|
38
|
+
|
39
|
+
return instance
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
MyClass.extend(MyExtension)
|
44
|
+
|
45
|
+
p MyClass.instance
|
46
|
+
~~~
|
47
|
+
|
48
|
+
### Assignment
|
49
|
+
|
50
|
+
Generally, you should avoid assigning to the thread-local instance. You should prefer extensions as outlined above. However, in some situations (e.g. testing) you may prefer to directly assign to the thread local:
|
51
|
+
|
52
|
+
~~~ruby
|
53
|
+
require 'thread/local'
|
54
|
+
|
55
|
+
class MyClass
|
56
|
+
extend Thread::Local
|
57
|
+
end
|
58
|
+
|
59
|
+
# Change the instance for the current thread:
|
60
|
+
MyClass.instance = Object.new
|
61
|
+
|
62
|
+
p MyClass.instance
|
63
|
+
~~~
|
64
|
+
|
65
|
+
## Per-Module Instance
|
66
|
+
|
67
|
+
By default, you cannot instantiate a module. So, you must implement your own `self.new` method:
|
68
|
+
|
69
|
+
~~~ ruby
|
70
|
+
require 'thread/local'
|
71
|
+
|
72
|
+
module MyModule
|
73
|
+
extend Thread::Local
|
74
|
+
|
75
|
+
def self.new
|
76
|
+
Object.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
p MyModule.instance
|
81
|
+
~~~
|
data/lib/thread/local.rb
CHANGED
@@ -20,25 +20,37 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
|
-
|
23
|
+
require_relative "local/version"
|
24
24
|
|
25
25
|
class Thread
|
26
26
|
module Local
|
27
27
|
# Instantiate a new thread-local object.
|
28
|
+
# By default, invokes {new} to generate the instance.
|
29
|
+
# @returns [Object]
|
28
30
|
def local
|
29
31
|
self.new
|
30
32
|
end
|
31
33
|
|
32
34
|
# Get the current thread-local instance. Create it if required.
|
33
|
-
|
35
|
+
# @returns [Object] The thread-local instance.
|
36
|
+
def instance
|
37
|
+
thread = Thread.current
|
34
38
|
name = self.name
|
35
39
|
|
36
40
|
unless instance = thread.thread_variable_get(name)
|
37
|
-
instance = self.local
|
38
|
-
|
41
|
+
if instance = self.local
|
42
|
+
thread.thread_variable_set(name, instance)
|
43
|
+
end
|
39
44
|
end
|
40
45
|
|
41
46
|
return instance
|
42
47
|
end
|
48
|
+
|
49
|
+
# Assigns the current thread-local instance.
|
50
|
+
# @parameter instance [Object] The object that will become the thread-local instance.
|
51
|
+
def instance= instance
|
52
|
+
thread = Thread.current
|
53
|
+
thread.thread_variable_set(self.name, instance)
|
54
|
+
end
|
43
55
|
end
|
44
56
|
end
|
data/lib/thread/local/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thread-local
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: covered
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: bundler
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +25,7 @@ dependencies:
|
|
39
25
|
- !ruby/object:Gem::Version
|
40
26
|
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
28
|
+
name: covered
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
31
|
- - ">="
|
@@ -53,7 +39,7 @@ dependencies:
|
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
42
|
+
name: rspec
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - ">="
|
@@ -66,27 +52,20 @@ dependencies:
|
|
66
52
|
- - ">="
|
67
53
|
- !ruby/object:Gem::Version
|
68
54
|
version: '0'
|
69
|
-
description:
|
55
|
+
description:
|
70
56
|
email:
|
71
|
-
- samuel.williams@oriontransfer.co.nz
|
72
57
|
executables: []
|
73
58
|
extensions: []
|
74
59
|
extra_rdoc_files: []
|
75
60
|
files:
|
76
|
-
-
|
77
|
-
- ".gitignore"
|
78
|
-
- ".rspec"
|
79
|
-
- Gemfile
|
80
|
-
- README.md
|
81
|
-
- Rakefile
|
61
|
+
- lib/thread/local.md
|
82
62
|
- lib/thread/local.rb
|
83
63
|
- lib/thread/local/version.rb
|
84
|
-
|
85
|
-
homepage:
|
64
|
+
homepage: https://github.com/socketry/thread-local
|
86
65
|
licenses:
|
87
66
|
- MIT
|
88
67
|
metadata: {}
|
89
|
-
post_install_message:
|
68
|
+
post_install_message:
|
90
69
|
rdoc_options: []
|
91
70
|
require_paths:
|
92
71
|
- lib
|
@@ -94,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
73
|
requirements:
|
95
74
|
- - ">="
|
96
75
|
- !ruby/object:Gem::Version
|
97
|
-
version: 2.
|
76
|
+
version: 2.5.0
|
98
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
78
|
requirements:
|
100
79
|
- - ">="
|
@@ -102,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
81
|
version: '0'
|
103
82
|
requirements: []
|
104
83
|
rubygems_version: 3.1.2
|
105
|
-
signing_key:
|
84
|
+
signing_key:
|
106
85
|
specification_version: 4
|
107
86
|
summary: Provides a class-level mixin to make thread local state easy.
|
108
87
|
test_files: []
|
@@ -1,34 +0,0 @@
|
|
1
|
-
name: Development
|
2
|
-
|
3
|
-
on: [push, pull_request]
|
4
|
-
|
5
|
-
jobs:
|
6
|
-
test:
|
7
|
-
strategy:
|
8
|
-
matrix:
|
9
|
-
os:
|
10
|
-
- ubuntu
|
11
|
-
- macos
|
12
|
-
|
13
|
-
ruby:
|
14
|
-
- 2.4
|
15
|
-
- 2.5
|
16
|
-
- 2.6
|
17
|
-
- 2.7
|
18
|
-
|
19
|
-
include:
|
20
|
-
- os: 'ubuntu'
|
21
|
-
ruby: '2.6'
|
22
|
-
env: COVERAGE=PartialSummary,Coveralls
|
23
|
-
|
24
|
-
runs-on: ${{matrix.os}}-latest
|
25
|
-
|
26
|
-
steps:
|
27
|
-
- uses: actions/checkout@v1
|
28
|
-
- uses: ruby/setup-ruby@v1
|
29
|
-
with:
|
30
|
-
ruby-version: ${{matrix.ruby}}
|
31
|
-
- name: Install dependencies
|
32
|
-
run: bundle install
|
33
|
-
- name: Run tests
|
34
|
-
run: ${{matrix.env}} bundle exec rspec
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/Gemfile
DELETED
data/README.md
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
# Thread::Local 
|
2
|
-
|
3
|
-
A module to simplify thread-local state.
|
4
|
-
|
5
|
-
## Motivation
|
6
|
-
|
7
|
-
In my own web framework, [utopia](https://github.com/socketry/utopia), I have been struggling with the best way to expose configuration details. I was setting both global variables and modifying `ENV` which made it impossible to have multiple isolated instances of the application in the same process. This in turn makes it hard to implement things like graceful restart in multi-threaded [falcon](https://github.com/socketry/falcon). Such issues also affect application code running in other multi-threaded contexts, which are becoming increasingly common (e.g. JRuby, TruffleRuby).
|
8
|
-
|
9
|
-
Global variables are often not thread-safe and encourage poor programming style. In many cases it is desirable to have thread-local state, but implementing this directly in Ruby is unpleasant at best. This gem provides a best-practice wrapper which can extend existing classes to provide per-thread instances.
|
10
|
-
|
11
|
-
Conceptually, a thread is a container for application state. This works well when servers consider applications to be isolated on a per-thread basis, but this isn't always the case:
|
12
|
-
|
13
|
-
| Server | Application | Thread Safety |
|
14
|
-
|----------------------|------------------|--------------------------|
|
15
|
-
| Falcon Multi-Process | One per process. | Isolated. |
|
16
|
-
| Falcon Multi-Thread | One per thread. | Isolated, Shared State. |
|
17
|
-
| Puma Multi-Thread | One per process. | Reentrant, Shared State. |
|
18
|
-
| Puma Cluster | One per worker. | Reentrant, Shared State. |
|
19
|
-
| Unicorn | One per process. | Isolated. |
|
20
|
-
|
21
|
-
Puma requires applications to be completely thread safe and reentrant, which isn't always easy. However, this gem attempts to provide a model which works in all the above servers, providing isolated, thread-safe, mutable per-thread state.
|
22
|
-
|
23
|
-
## Installation
|
24
|
-
|
25
|
-
```
|
26
|
-
bundle add thread-local
|
27
|
-
```
|
28
|
-
|
29
|
-
## Usage
|
30
|
-
|
31
|
-
In your own class:
|
32
|
-
|
33
|
-
```ruby
|
34
|
-
class MyThing
|
35
|
-
extend Thread::Local
|
36
|
-
end
|
37
|
-
```
|
38
|
-
|
39
|
-
Now, instead of instantiating your class `MyThing.new`, use `MyThing.instance`. It will return a thread-local instance.
|
40
|
-
|
41
|
-
```ruby
|
42
|
-
Thread.new do
|
43
|
-
MyThing.instance
|
44
|
-
# => #<MyObject:0x000055a14ec6be80>
|
45
|
-
end
|
46
|
-
|
47
|
-
Thread.new do
|
48
|
-
MyThing.instance
|
49
|
-
# => #<MyObject:0x000055a14ec597d0>
|
50
|
-
end
|
51
|
-
```
|
52
|
-
|
53
|
-
Use this as a thread-safe alternative to globals.
|
54
|
-
|
55
|
-
## Contributing
|
56
|
-
|
57
|
-
1. Fork it
|
58
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
61
|
-
5. Create new Pull Request
|
62
|
-
|
63
|
-
## License
|
64
|
-
|
65
|
-
Released under the MIT license.
|
66
|
-
|
67
|
-
Copyright, 2020, by [Samuel G. D. Williams](https://www.codeotaku.com).
|
68
|
-
|
69
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
70
|
-
of this software and associated documentation files (the "Software"), to deal
|
71
|
-
in the Software without restriction, including without limitation the rights
|
72
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
73
|
-
copies of the Software, and to permit persons to whom the Software is
|
74
|
-
furnished to do so, subject to the following conditions:
|
75
|
-
|
76
|
-
The above copyright notice and this permission notice shall be included in
|
77
|
-
all copies or substantial portions of the Software.
|
78
|
-
|
79
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
80
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
81
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
82
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
83
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
84
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
85
|
-
THE SOFTWARE.
|
data/Rakefile
DELETED
data/thread-local.gemspec
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
# THE SOFTWARE.
|
22
|
-
|
23
|
-
require_relative 'lib/thread/local/version'
|
24
|
-
|
25
|
-
Gem::Specification.new do |spec|
|
26
|
-
spec.name = "thread-local"
|
27
|
-
spec.version = Thread::Local::VERSION
|
28
|
-
spec.authors = ["Samuel Williams"]
|
29
|
-
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
30
|
-
|
31
|
-
spec.summary = "Provides a class-level mixin to make thread local state easy."
|
32
|
-
spec.license = "MIT"
|
33
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
34
|
-
|
35
|
-
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
36
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
37
|
-
end
|
38
|
-
|
39
|
-
spec.require_paths = ["lib"]
|
40
|
-
|
41
|
-
spec.add_development_dependency "covered"
|
42
|
-
spec.add_development_dependency "bundler"
|
43
|
-
spec.add_development_dependency "rspec"
|
44
|
-
spec.add_development_dependency "rake"
|
45
|
-
end
|