eventbox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +0 -0
- data/.appveyor.yml +28 -0
- data/.gitignore +8 -0
- data/.travis.yml +16 -0
- data/.yardopts +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +381 -0
- data/Rakefile +14 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/downloads.md +143 -0
- data/docs/server.md +88 -0
- data/docs/threadpool.md +73 -0
- data/eventbox.gemspec +31 -0
- data/lib/eventbox.rb +270 -0
- data/lib/eventbox/argument_wrapper.rb +76 -0
- data/lib/eventbox/boxable.rb +298 -0
- data/lib/eventbox/event_loop.rb +385 -0
- data/lib/eventbox/object_registry.rb +35 -0
- data/lib/eventbox/sanitizer.rb +342 -0
- data/lib/eventbox/thread_pool.rb +170 -0
- data/lib/eventbox/timer.rb +172 -0
- data/lib/eventbox/version.rb +5 -0
- metadata +156 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 06556e4ddd4d9ccd8cfede0c2163dd164165bdf6585d6a3352a1f44b7d17ff67
|
4
|
+
data.tar.gz: 6532937e73f20e42808a5516699781ed95284fc1cc52e0d160b443239e690fd6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 69dd49a9e166760c2cb7487ef5560ed92a59b68eaf4702dbf08dcab93751ae8bfc2f271f387eae0822b4798f60f8eff7cd644ecf63fd765bf10b42758c37e8ac
|
7
|
+
data.tar.gz: 66d26b6bf6dcac3954273b216261e576cbd62aec7b8c208eec55b84fd87fe3e1a1b452884cf8a97e2ef772c34020d48d0596ed1483e755a630aace852c7a20e1
|
checksums.yaml.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
d<]]�oF�5�t>�
|
data.tar.gz.sig
ADDED
Binary file
|
data/.appveyor.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
build: off
|
2
|
+
|
3
|
+
init:
|
4
|
+
- set PATH=C:/Ruby%ruby_version%/bin;%PATH%
|
5
|
+
- set RUBYOPT=--verbose -Eutf-8 --enable-frozen-string-literal
|
6
|
+
|
7
|
+
install:
|
8
|
+
- ps: |
|
9
|
+
if ($env:RUBYDOWNLOAD -ne $null) {
|
10
|
+
$(new-object net.webclient).DownloadFile("https://github.com/oneclick/rubyinstaller2/releases/download/rubyinstaller-head/rubyinstaller-head-$env:RUBYDOWNLOAD.exe", "$pwd/ruby-setup.exe")
|
11
|
+
cmd /c ruby-setup.exe /verysilent /dir=C:/Ruby$env:ruby_version
|
12
|
+
}
|
13
|
+
- ruby --version
|
14
|
+
- gem --version
|
15
|
+
- gem install bundler --conservative
|
16
|
+
- bundle install
|
17
|
+
|
18
|
+
test_script:
|
19
|
+
- bundle exec rake test TESTOPTS=-v
|
20
|
+
|
21
|
+
environment:
|
22
|
+
matrix:
|
23
|
+
- ruby_version: "head"
|
24
|
+
RUBYDOWNLOAD: x64
|
25
|
+
- ruby_version: "head"
|
26
|
+
RUBYDOWNLOAD: x86
|
27
|
+
- ruby_version: "24-x64"
|
28
|
+
- ruby_version: "24"
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
sudo: false
|
3
|
+
language: ruby
|
4
|
+
cache: bundler
|
5
|
+
matrix:
|
6
|
+
fast_finish: true
|
7
|
+
include:
|
8
|
+
- rvm: 2.3.8
|
9
|
+
- rvm: 2.5.3
|
10
|
+
env: RUBYOPT=--verbose --enable-frozen-string-literal
|
11
|
+
- rvm: jruby-9.2.4.1
|
12
|
+
- rvm: ruby-head
|
13
|
+
env: RUBYOPT=--verbose --enable-frozen-string-literal
|
14
|
+
|
15
|
+
script:
|
16
|
+
- bundle exec rake test TESTOPTS=-v
|
data/.yardopts
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at kanis@comcard.de. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Lars Kanis
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
[![Build Status Linux](https://travis-ci.com/larskanis/eventbox.svg?branch=master)](https://travis-ci.com/larskanis/eventbox)
|
2
|
+
[![Build status Windows](https://ci.appveyor.com/api/projects/status/tq397g0gfke1mcud/branch/master?svg=true)](https://ci.appveyor.com/project/larskanis/eventbox/branch/master)
|
3
|
+
|
4
|
+
# Eventbox
|
5
|
+
|
6
|
+
_Manage multithreading with the safety of event based programming_
|
7
|
+
|
8
|
+
{Eventbox} objects are event based and single threaded from the inside but thread-safe and blocking from the outside.
|
9
|
+
Eventbox enforces a separation of code for event processing and code running blocking operations.
|
10
|
+
Code inside an {Eventbox} object is executed non-concurrently and hence shouldn't do any blocking operations.
|
11
|
+
This is similar to the typical JavaScript programming style.
|
12
|
+
|
13
|
+
On the other hand all blocking operations can be executed in action threads spawned by the {Eventbox.action action} method type.
|
14
|
+
Communication between actions and event processing is done through ordinary method or lambda calls.
|
15
|
+
|
16
|
+
An important task of Eventbox is to avoid race conditions through shared data.
|
17
|
+
Such data races between event scope and external/action scope are avoided through {Eventbox::Sanitizer filters} applied to all inputs and outputs.
|
18
|
+
That way {Eventbox} guarantees stable states while event processing without a need for any locks.
|
19
|
+
|
20
|
+
Eventbox is a model of concurrent computation that is used to build thread-safe objects with arbitrary interfaces.
|
21
|
+
It is kind of [advancement](#comparison-threading-abstractions) of the well known [actor model](https://en.wikipedia.org/wiki/Actor_model) leveraging the possibilities of the ruby language.
|
22
|
+
|
23
|
+
* [API documentation](https://www.rubydoc.info/github/larskanis/eventbox/master)
|
24
|
+
|
25
|
+
|
26
|
+
## Requirements
|
27
|
+
|
28
|
+
* Ruby-2.3 or newer or
|
29
|
+
* JRuby 9.1 or newer
|
30
|
+
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
Add this line to your application's Gemfile:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
gem 'eventbox'
|
38
|
+
```
|
39
|
+
|
40
|
+
And then execute:
|
41
|
+
|
42
|
+
$ bundle
|
43
|
+
|
44
|
+
Or install it yourself as:
|
45
|
+
|
46
|
+
$ gem install eventbox
|
47
|
+
|
48
|
+
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
{Eventbox} is an universal approach to build thread-safe objects.
|
52
|
+
It can therefore be used to build well known multithread abstractions like a Queue class:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require "eventbox"
|
56
|
+
class Queue < Eventbox
|
57
|
+
# Called at Queue.new just like Object#initialize in ordinary ruby classes
|
58
|
+
async_call def init
|
59
|
+
@que = [] # List of values waiting for being fetched by deq
|
60
|
+
@waiting = [] # List of blocking deq calls waiting for new values to be pushed by enq
|
61
|
+
end
|
62
|
+
|
63
|
+
# Push a value to the queue and return the next value by the next waiting deq call
|
64
|
+
async_call def enq(€value) # €-variables are passed through as reference
|
65
|
+
@que << €value # Push a value to the queue
|
66
|
+
if w=@waiting.shift
|
67
|
+
w.yield @que.shift # Let one waiting deq call return with the next value from the queue
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Fetch a value from the queue or suspend the caller until a value has been enqueued
|
72
|
+
yield_call def deq(result)
|
73
|
+
if @que.empty?
|
74
|
+
@waiting << result # Don't return a value now, but enqueue the request as waiting
|
75
|
+
else
|
76
|
+
result.yield @que.shift # Immediately return the next value from the queue
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
It has semantics like ruby's builtin Queue implementation:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
q = Queue.new
|
85
|
+
Thread.new do
|
86
|
+
5.times do |i|
|
87
|
+
q.enq i # Enqueue integers 0 to 5
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
5.times do
|
92
|
+
p q.deq # Fetch and print 5 integers from the queue
|
93
|
+
end
|
94
|
+
|
95
|
+
# It gives the following output:
|
96
|
+
0
|
97
|
+
1
|
98
|
+
2
|
99
|
+
3
|
100
|
+
4
|
101
|
+
```
|
102
|
+
|
103
|
+
Although there are no mutex or condition variables in use, the implementation is guaranteed to be thread-safe.
|
104
|
+
The key feature is the {Eventbox.yield_call} method definition.
|
105
|
+
It divides the single external call into two internal events: The event of the start of call and the event of releasing the call with a return value.
|
106
|
+
In contrast {Eventbox.async_call} defines a method which handles one event only - the start of the call.
|
107
|
+
The external call returns immediately, but can't return a value.
|
108
|
+
|
109
|
+
Seeing curly braces instead of links? Switch to the [API documentation](https://www.rubydoc.info/github/larskanis/eventbox/master).
|
110
|
+
|
111
|
+
The branch in `Queue#deq` shows a typical decision taking in Eventbox:
|
112
|
+
If the call can be processed immediately it yields the result, else wise the result is added to a list to be processes later.
|
113
|
+
It's important to check this list at each event which could signal the ability to complete the enqueued processing.
|
114
|
+
This is done in `Queue#enq` in the above example.
|
115
|
+
|
116
|
+
If you just need a queue it's better to stay at the Queue implementations of the standard library or [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
|
117
|
+
However if you want to cancel items in the queue for example, you need more control about waiting items or waiting callers than common thread abstractions offer.
|
118
|
+
The same if you want to query and visualize the internal state of processing - that means the pending items in the queue.
|
119
|
+
|
120
|
+
|
121
|
+
### Hands on
|
122
|
+
|
123
|
+
The following examples illustrate most important aspects of Eventbox.
|
124
|
+
It is recommended to work them through, in order to fully understand how Eventbox can be used to implement all kind of multi-threaded applications.
|
125
|
+
|
126
|
+
* {file:docs/downloads.md HTTP client} - Understand how to use actions to build a HTTP client which downloads several URLs in parallel.
|
127
|
+
* {file:docs/server.md TCP server} - Understand how to startup and shutdown blocking actions and to combine several Eventbox classes to handle parallel connections.
|
128
|
+
* {file:docs/threadpool.md Thread-pool} - Understand how parallel external requests can be serialized and scheduled.
|
129
|
+
|
130
|
+
|
131
|
+
## Method types
|
132
|
+
|
133
|
+
### Event Scope
|
134
|
+
|
135
|
+
Eventbox offers 3 different types of external callable methods:
|
136
|
+
|
137
|
+
* {Eventbox.yield_call yield_call} defines a blocking or non-blocking method with return value.
|
138
|
+
It is the most flexible call type.
|
139
|
+
* {Eventbox.sync_call sync_call} is a convenience version of `yield_call` for a non-blocking method with return value.
|
140
|
+
* {Eventbox.async_call async_call} is a convenience version of `yield_call` for a non-blocking method without return value.
|
141
|
+
|
142
|
+
They can be defined with `private`, `protected` or `public` visibility.
|
143
|
+
The method body is referred to as "event scope" of a given Eventbox object.
|
144
|
+
Code in the event scope is based on an event driven programming style where events are signaled by method calls or by callback functions.
|
145
|
+
|
146
|
+
The event scope shouldn't be used to do blocking operations.
|
147
|
+
There is no hard criteria for what is considered a blocking operation, but since event scope methods of one object don't run concurrently, it decreases the overall responsiveness of the Eventbox instance.
|
148
|
+
If the processing time of an event scope method or block exceeds the limit of 0.5 seconds, a warning is print to `STDERR`.
|
149
|
+
This limit can be changed by {Eventbox.with_options}.
|
150
|
+
|
151
|
+
Arguments of async, sync and yield calls can be prefixed by a € sign.
|
152
|
+
This marks them as to be passed through as reference, instead of being copied.
|
153
|
+
A €-variable is wrapped and protected within the event scope, but unwrapped when passed to action or external scope.
|
154
|
+
|
155
|
+
In additoin there are accessor methods usable as known from ordinary ruby objects: {Eventbox.attr_reader attr_reader}, {Eventbox.attr_writer attr_writer} and {Eventbox.attr_accessor attr_accessor}.
|
156
|
+
They allow thread-safe access to instance variables.
|
157
|
+
|
158
|
+
Beside {Eventbox.async_call async_call}, {Eventbox.sync_call sync_call} and {Eventbox.yield_call yield_call} methods it's possible to define plain `private` methods, since they are not accessible externally.
|
159
|
+
However any plain `public` or `protected` methods within Eventbox classes are rejected.
|
160
|
+
|
161
|
+
Seeing curly braces instead of links? Switch to the [API documentation](https://www.rubydoc.info/github/larskanis/eventbox/master).
|
162
|
+
|
163
|
+
### Action Scope
|
164
|
+
|
165
|
+
{Eventbox.action Action} methods are very different from the above.
|
166
|
+
They run concurrently to all event scope methods within their own thread.
|
167
|
+
Although actions reside within the same class they don't share instance variables with the event scope.
|
168
|
+
However they can safely call all instance methods.
|
169
|
+
The method body is referred to as "action scope".
|
170
|
+
|
171
|
+
Eventbox doesn't provide specific methods for asynchronous IO, but relies on ruby's builtin methods or gems for this purpose.
|
172
|
+
The intention is, that IO or any other blocking calls are done through {Eventbox.action action} methods.
|
173
|
+
In contrast to event scope, they should not share any data with other actions or threads.
|
174
|
+
Instead a shared-nothing approach is the recommended way to build actions.
|
175
|
+
That means, that all data required for one particular action call, should be passed as arguments, but nothing more.
|
176
|
+
And all data generated by the action should be passed as arguments back to event scope methods and the outcome should be managed there.
|
177
|
+
|
178
|
+
Some data shall just be managed as reference in some scope without being accessed there.
|
179
|
+
Or it is passed through a given scope only.
|
180
|
+
In such cases it can be marked as {Eventbox#shared_object shared_object}.
|
181
|
+
This wrapping is similar to € argument variables, however {Eventbox#shared_object shared_object} it more versatile.
|
182
|
+
It marks objects permanently and wraps them even when they are stored inside of a copied object.
|
183
|
+
|
184
|
+
### External scope
|
185
|
+
|
186
|
+
Code outside of the Eventbox class is referred to as "external scope".
|
187
|
+
|
188
|
+
|
189
|
+
## Block and Proc types
|
190
|
+
|
191
|
+
Similary to the 3 method calls above there are 3 types of proc objects which act as anonymous counterparts of the method call types.
|
192
|
+
|
193
|
+
* {Eventbox#yield_proc yield_proc} allocates a blocking or non-blocking proc object with a return value.
|
194
|
+
It is the most flexible proc type.
|
195
|
+
* {Eventbox#sync_proc sync_proc} is a convenience version of yield_proc for a non-blocking code block with return value.
|
196
|
+
* {Eventbox#async_proc async_proc} is a convenience version of yield_proc for a non-blocking code block without return value.
|
197
|
+
|
198
|
+
These proc objects can be created within event scope, can be passed to external scope and called from there.
|
199
|
+
|
200
|
+
Arguments of async, sync and yield procs can be prefixed by a € sign.
|
201
|
+
In that case, they are passed as reference, equally to €-variables of {Eventbox.async_call async_call}, {Eventbox.sync_call sync_call} and {Eventbox.yield_call yield_call} methods.
|
202
|
+
|
203
|
+
The other way around - Proc objects or blocks which are defined in external or action scope - can be passed to event scope.
|
204
|
+
Such a Proc object is wrapped as a {Eventbox::ExternalProc} object within the event scope.
|
205
|
+
There it can be called as usual - however the execution of the proc doesn't stall the Eventbox object.
|
206
|
+
Instead the event scope method is executed until its end, while the block is executed within the thread which has called the current event scope method.
|
207
|
+
Optionally the block can be called with a completion block as the last argument, which is called with the result of the external proc when it has finished.
|
208
|
+
|
209
|
+
|
210
|
+
## Exceptions
|
211
|
+
|
212
|
+
Eventbox makes use of exceptions in several ways.
|
213
|
+
|
214
|
+
All exceptions raised within the event scope are passed to the internal or external caller, like in ordinary ruby objects.
|
215
|
+
It's also possible to raise deferred exceptions through the result proc of a {Eventbox#yield_call yield_call} or {Eventbox#yield_proc yield_proc}.
|
216
|
+
See the {Eventbox::CompletionProc#raise} method.
|
217
|
+
|
218
|
+
Exceptions raised in the action scope are not automatically passed to a caller, but must be handled appropriately by the Eventbox instance.
|
219
|
+
It is recommended to handle exceptions in actions similar to the following, unless you're very sure, that no exception happens:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
action def start_connection(host, port)
|
223
|
+
sock = TCPSocket.new(host, port)
|
224
|
+
rescue => err
|
225
|
+
conn_failed(err)
|
226
|
+
else
|
227
|
+
conn_succeeded(sock)
|
228
|
+
end
|
229
|
+
|
230
|
+
async_call def conn_succeeded(sock)
|
231
|
+
# handle the success event (e.g. start communication by another action)
|
232
|
+
end
|
233
|
+
|
234
|
+
async_call def conn_failed(error)
|
235
|
+
# handle the failure event (retry or similar)
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
Another use of exceptions is for sending signals to running actions.
|
240
|
+
This is done by {Eventbox::Action#raise}.
|
241
|
+
|
242
|
+
|
243
|
+
## What is safe and what isn't?
|
244
|
+
|
245
|
+
At each transition of the scope all passing objects are sanitized by the {Eventbox::Sanitizer}.
|
246
|
+
It protects the event scope from data races and arbitrates between blocking and event based semantics.
|
247
|
+
This is done by copying or wrapping the objects conveniently as described in the {Eventbox::Sanitizer}.
|
248
|
+
That way event scope methods never get an inconsistent state regardless of the activities of external threads.
|
249
|
+
|
250
|
+
Obviously it's not safe to do things like using `send` to call private methods from external, access instance variables per `instance_variable_set` or use global variables in a multithreading context.
|
251
|
+
Such rough ways of communication with an Eventbox object are surely neither recommended nor supported.
|
252
|
+
Other than these the event scope of an Eventbox instance is pretty well protected against accident mistakes.
|
253
|
+
|
254
|
+
However there's a catch which needs to take note of:
|
255
|
+
It is not restricted to access any constants from event scope.
|
256
|
+
Therefore it's possible to call for example `Thread.new` within the event scope.
|
257
|
+
Unsurprisingly is a very bad idea, since the provided block is called with no synchronization and can therefore lead to all kind of threading issues.
|
258
|
+
It would be safe to call `Thread.new` with an {Eventbox#async_proc async_proc}, however since the proc is not allowed to do any blocking operations, it's recommended to use an {Eventbox.action action} method definition instead to spawn threads.
|
259
|
+
|
260
|
+
Also note that Eventbox doesn't protect external scope or action scope from threading issues.
|
261
|
+
The external scope is recognized as one common space.
|
262
|
+
External libraries and objects must be thread-safe on its own if used from different threads in external or action scope.
|
263
|
+
Protecting them is beyond the scope of Eventbox.
|
264
|
+
|
265
|
+
|
266
|
+
## Time based events
|
267
|
+
|
268
|
+
It's possible to generate a timer event by stating an action with a `sleep` and subsequent call to an {Eventbox.async_call async_call} method.
|
269
|
+
However that's not very convenient.
|
270
|
+
Therefore Eventbox provides a dedicated {Eventbox::Timer timer module} for simple timer functions.
|
271
|
+
It can be included into Eventbox classes by:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
include Eventbox::Timer
|
275
|
+
```
|
276
|
+
|
277
|
+
It offers {Eventbox::Timer#timer_after} and {Eventbox::Timer#timer_every} functions to schedule blocks to be called.
|
278
|
+
|
279
|
+
|
280
|
+
## Derived classes and mixins
|
281
|
+
|
282
|
+
Eventbox classes can be derived like ordinary ruby classes - there are no restrictions specific to Eventbox.
|
283
|
+
Methods of base classes can be called by `super`.
|
284
|
+
All classes of the hierarchy share the same instance variables like ordinary ruby objects.
|
285
|
+
Only {Eventbox.action action} methods use their own variable space.
|
286
|
+
|
287
|
+
It's also possible to mix a module into the Eventbox class.
|
288
|
+
See the description of {Eventbox::Boxable} for how it works.
|
289
|
+
|
290
|
+
|
291
|
+
## When to use Eventbox?
|
292
|
+
|
293
|
+
Eventbox comes into play when things are getting more complicated or more customized.
|
294
|
+
For instance a module which shall distribute work orders to external processes.
|
295
|
+
When it shall visualize the progress and allow cancellation of orders, available abstractions don't fit well to the problem.
|
296
|
+
|
297
|
+
In such a case Eventbox helps to manage a consistent state about these running activities.
|
298
|
+
It also allows to query this state in a natural way, since states can be stored in plain ruby objects (arrays, hashs, etc) instead of specialized thread abstractions.
|
299
|
+
|
300
|
+
While not impossible to implement things per raw threads, mutexes and condition variables, it's pretty hard to do that right.
|
301
|
+
There are no tools to verify correct usage of mutexes or other threading abstractions in ruby.
|
302
|
+
However threading errors are subtle, so you'll probably not notice mistakes, until going to production.
|
303
|
+
|
304
|
+
Due to Eventbox's checks and guaranties it's easier to verify and prove correctness of implementations running on top of it.
|
305
|
+
This was the primary motivation to develop this library.
|
306
|
+
|
307
|
+
|
308
|
+
<a name="comparison-threading-abstractions"></a>
|
309
|
+
## Comparison with other threading abstractions
|
310
|
+
|
311
|
+
### The Actor model
|
312
|
+
|
313
|
+
Eventbox is kind of advancement of the well known [actor model](https://en.wikipedia.org/wiki/Actor_model) leveraging the possibilities of the ruby language.
|
314
|
+
While the actor model uses explicit message passing, Eventbox relies on method calls, closure calls and exceptions, which makes it much more natural to use.
|
315
|
+
Unlike an actor, Eventbox doesn't start a thread per object, but uses the thread of the caller to execute non-blocking code.
|
316
|
+
This makes instantiation of Eventbox objects cheeper than Actor objects.
|
317
|
+
Instead it can create and manage in-object private threads in form of {Eventbox.action actions} to be used for blocking operations.
|
318
|
+
|
319
|
+
Many actor implementations manage an inheritance tree of actor objects.
|
320
|
+
Parent actors are then notified about failures of child actors.
|
321
|
+
In contrast Eventbox objects maintain a list of all running internal actions instead, but are completely independent from each other.
|
322
|
+
Failures are handled object internal - see chapter "Exceptions" above.
|
323
|
+
|
324
|
+
### Internal state
|
325
|
+
|
326
|
+
Eventbox keeps all instance variables in a consistent state as a whole.
|
327
|
+
This is an important difference to [thread-safe collections](https://github.com/ruby-concurrency/concurrent-ruby#thread-safe-value-objects-structures-and-collections) like `Concurrent::Hash`.
|
328
|
+
They mislead the developer to believe that a module is thread-safe when it's just using these classes.
|
329
|
+
|
330
|
+
Unfortunately this is often wrong: They require a lot of experience to avoid mistakes through non-atomic updates, non-atomic test-and-set operations or race-conditions through using several thread-safe objects in combination (although a consistent state is only managed on a per object base).
|
331
|
+
|
332
|
+
### Data races
|
333
|
+
|
334
|
+
Most thread abstractions don't do deeper checks for wrong usage of data.
|
335
|
+
In particular they don't protect from data races like Eventbox does.
|
336
|
+
Ruby doesn't (yet) have mechanisms to bind objects to threads, so that there's no builtin safety.
|
337
|
+
|
338
|
+
### Blocking and non-blocking scope
|
339
|
+
|
340
|
+
Beside this, Eventbox has an explicit specification where blocking and where non-blocking code has to be executed and the compliance is monitored.
|
341
|
+
This ensures that events are processed in time regardless of the current state.
|
342
|
+
Such a specification is not enforced by most other threading abstractions and can quickly lead to delayed reactions in particular situations.
|
343
|
+
|
344
|
+
|
345
|
+
## Comparison with other async libraries
|
346
|
+
|
347
|
+
Eventbox doesn't try to implement IO or other blocking operations on top of a global event loop.
|
348
|
+
Instead it encourages the use of blocking operations and threads for things which should run in parallel, while keeping the management code in safe internal methods written in an event based style.
|
349
|
+
Because IO is done in action threads, the only type of events handled by the event scope are method calls received from actions or external calls.
|
350
|
+
They are processed by a kind of event loop which runs one per Eventbox object.
|
351
|
+
|
352
|
+
This is in contrast to libraries like [async](https://github.com/socketry/async), [EventMachine](https://github.com/eventmachine/eventmachine) or [Celluloid](https://github.com/celluloid/celluloid) which provide dozens of IO wrappers.
|
353
|
+
|
354
|
+
|
355
|
+
## Eventbox performance
|
356
|
+
|
357
|
+
Eventbox is reasonably fast, but far from the performance of low level threading primitives (like implemented in [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) ).
|
358
|
+
It is not written to minimize resource consumption or maximize performance or throughput.
|
359
|
+
Instead it is written to minimize race conditions and implementation complexity in a multithreaded environment.
|
360
|
+
And it is written to act as a solid and consistent foundation for a wide range of concurrent computation problems.
|
361
|
+
|
362
|
+
So if your use case requires raw performance more than implementation safety, Eventbox is probably not the right tool.
|
363
|
+
|
364
|
+
Still there is lots of room for performance improvements in Eventbox, like faster method invocations or copy-on-write objects.
|
365
|
+
If there's a stronger interest in Eventbox performance, it's possible to source relevant parts out to a C extension.
|
366
|
+
The introduction of guilds in ruby will probably be helpful for Eventbox as well.
|
367
|
+
|
368
|
+
|
369
|
+
## Contributing
|
370
|
+
|
371
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/larskanis/eventbox. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
372
|
+
|
373
|
+
|
374
|
+
## License
|
375
|
+
|
376
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
377
|
+
|
378
|
+
|
379
|
+
## Code of Conduct
|
380
|
+
|
381
|
+
Everyone interacting in the Eventbox project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/larskanis/eventbox/blob/master/CODE_OF_CONDUCT.md).
|