├── .gitignore ├── .ruby-version ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── tribe.rb └── tribe │ ├── actable.rb │ ├── actor.rb │ ├── actor_state.rb │ ├── benchmark.rb │ ├── benchmark │ └── throughput.rb │ ├── dedicated_actor.rb │ ├── event.rb │ ├── exceptions.rb │ ├── future.rb │ ├── mailbox.rb │ ├── registry.rb │ ├── root.rb │ ├── safe_set.rb │ └── version.rb ├── test ├── actable_exception_test.rb ├── actable_future_test.rb ├── actable_initialization_test.rb ├── actable_linking_test.rb ├── actable_perform_test.rb ├── actable_shutdown_test.rb ├── actable_spawn_test.rb ├── actable_timer_test.rb ├── actor_state_test.rb ├── actor_test.rb ├── dedicted_actor_test.rb ├── event_test.rb ├── future_test.rb ├── mailbox_test.rb ├── register_test.rb ├── root_test.rb ├── safe_set_test.rb ├── test_helper.rb └── tribe_test.rb └── tribe.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.4.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.2 4 | before_install: gem install bundler -v 1.10.5 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'coveralls', :require => false 4 | 5 | gemspec 6 | 7 | gem 'workers', :git => 'https://github.com/chadrem/workers.git' 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/chadrem/workers.git 3 | revision: d9bc2b9ef8e0d189a6eaa60bd0360319204a3dad 4 | specs: 5 | workers (0.6.1) 6 | 7 | PATH 8 | remote: . 9 | specs: 10 | tribe (0.6.5) 11 | workers (~> 0.6.1) 12 | 13 | GEM 14 | remote: https://rubygems.org/ 15 | specs: 16 | coveralls (0.8.21) 17 | json (>= 1.8, < 3) 18 | simplecov (~> 0.14.1) 19 | term-ansicolor (~> 1.3) 20 | thor (~> 0.19.4) 21 | tins (~> 1.6) 22 | docile (1.1.5) 23 | json (2.1.0) 24 | json (2.1.0-java) 25 | minitest (5.10.3) 26 | rake (10.5.0) 27 | simplecov (0.14.1) 28 | docile (~> 1.1.0) 29 | json (>= 1.8, < 3) 30 | simplecov-html (~> 0.10.0) 31 | simplecov-html (0.10.2) 32 | term-ansicolor (1.6.0) 33 | tins (~> 1.0) 34 | thor (0.19.4) 35 | tins (1.16.3) 36 | 37 | PLATFORMS 38 | java 39 | ruby 40 | 41 | DEPENDENCIES 42 | bundler (~> 1.10) 43 | coveralls 44 | minitest 45 | rake (~> 10.0) 46 | tribe! 47 | workers! 48 | 49 | BUNDLED WITH 50 | 1.16.0 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Chad Remesch 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tribe [![Build Status](https://travis-ci.org/chadrem/tribe.svg)](https://travis-ci.org/chadrem/tribe) [![Coverage Status](https://coveralls.io/repos/chadrem/tribe/badge.svg?branch=master&service=github)](https://coveralls.io/github/chadrem/tribe?branch=master) 2 | 3 | Tribe is a Ruby gem that implements the [actor model](http://en.wikipedia.org/wiki/Actor_model) in an event-driven way. 4 | 5 | Tribe focuses on high performance, low latency, a simple API, and flexibility. 6 | It's goal is to support at least one million actors running on a small group of threads. 7 | It is built on top of the [Workers](https://github.com/chadrem/workers) gem. 8 | 9 | ## Contents 10 | 11 | - [Installation](#installation) 12 | - [Actors](#actors) 13 | - [Root](#root) 14 | - [Handlers](#handlers) 15 | - [Messages](#messages) 16 | - [Registries](#registries) 17 | - [Futures](#futures) 18 | - [Non-blocking](#non-blocking) 19 | - [Blocking](#blocking) 20 | - [Timeouts](#timeouts) 21 | - [Performance](#performance-summary) 22 | - [Forwarding](#forwarding) 23 | - [Timers](#timers) 24 | - [Linking](#linking) 25 | - [Supervisors](#supervisors) 26 | - [Blocking code](#blocking-code) 27 | - [Debugging](#debugging) 28 | - [Benchmarks](#benchmarks) 29 | - [Ruby's main thread](#rubys-main-thread) 30 | - [Contributing](#contributing) 31 | 32 | ## Installation 33 | 34 | Add this line to your application's Gemfile: 35 | 36 | gem 'tribe' 37 | 38 | And then execute: 39 | 40 | $ bundle 41 | 42 | Or install it yourself as: 43 | 44 | $ gem install tribe 45 | 46 | ## Actors 47 | 48 | Actors are the building blocks of your application. 49 | There are three ways to create an actor class: 50 | 51 | - Inherit from ````Tribe::Actor```` (uses the shared thread pool). 52 | - Inherit from ````Tribe::DedicatedActor```` (uses a dedicated thread). 53 | - Mixin ````Tribe::Actable```` and call the ````init_actable```` in your constructor. 54 | 55 | #### Root 56 | 57 | A well designed application organizes its actors in a tree like structure. 58 | To encourage this, Tribe has a special built-in actor known as the root actor. 59 | You should use the root actor to spawn all of your application specific actors. 60 | 61 | ```Ruby 62 | class MyActor < Tribe::Actor 63 | private 64 | # Your code goes here. 65 | end 66 | 67 | Tribe.root.spawn!(MyActor) 68 | ``` 69 | 70 | #### Command Handlers 71 | 72 | Command handlers are how you customize your actors. 73 | They are private methods that are prefixed with "on_" and they define the commands your actor knows how to handle. 74 | They accept one argument, an instance of ````Tribe::Event```` that shouuld always be named ````event````. 75 | 76 | A few command handlers are built into every actor to handle system specific events. They are: 77 | 78 | - ````on_initialize````: This handler takes the place of Ruby's ````initialize````. It is the first event processsed by all actors. 79 | - ````on_exception````: This handler will be called whenever an exception occurs. You can access the exception through ````event.data```` in case you want to print it, log it, etc. An exception inside of an actor will result in that actor's death. 80 | - ````on_shutdown````: This handler will be called whenever an actor is asked to shutdown cleanly. 81 | - ````on_child_died````: This handler gives an actor a chance to spawn a replacement child. You can access a reference to the child through ````event.data````. If the actor is a supervisor, it will continue to live otherwise it will die too. 82 | - ````on_child_shutdown````: This handler is similar to ````on_child_died````, but for when a child is shutdown cleanly. 83 | - ````on_parent_died````: This handler is also similar to ````on_child_died```` except for the parent actor. Child actors die when their parent dies. 84 | 85 | You should never call the built in command handlers yourself. 86 | They are reserved for the actor system and calling them yourself could result in unexpected behavior. 87 | 88 | ## Messages 89 | 90 | Messages are the most basic type of communication. They are sent using using two methods: 91 | 92 | - ````message!````: This method is used to tell one actor to send another actor a message. A reference to the source actor is included in the message in case the destination actor wants to respond. Usually it is used when your actor code wants to message another actor. 93 | - ````direct_message!````: This method is used to directly message an actor. Usually it is used when non-actor code wants to message an actor. No source actor is associated with the message. 94 | 95 | Since messages are fire-and-forget, both of the above methods always return ````nil````. 96 | 97 | Messages can include data that you want to pass between actors. It is best practice to treat data as owned by only one actor at a time. By doing this, you prevent race conditions and the need to create locks for your data. 98 | 99 | ```Ruby 100 | # Create your custom actor class. 101 | class MyActor < Tribe::Actor 102 | private 103 | def on_my_custom(event) 104 | puts "Received a custom event (#{event.inspect})." 105 | end 106 | 107 | def on_shutdown(event) 108 | puts "MyActor (#{identifier}) is shutting down." 109 | end 110 | end 111 | 112 | # Create some named actors that are children of the root actor. 113 | 100.times do |i| 114 | Tribe.root.spawn!(MyActor, :name => "my_actor_#{i}") 115 | end 116 | 117 | # Send an event to each actor. 118 | 100.times do |i| 119 | actor = Tribe.registry["my_actor_#{i}"] 120 | actor.direct_message!(:my_custom, 'hello world') 121 | end 122 | 123 | # Shutdown the actors. 124 | 100.times do |i| 125 | actor = Tribe.registry["my_actor_#{i}"] 126 | actor.shutdown! 127 | end 128 | ``` 129 | 130 | ## Registries 131 | 132 | Registries hold references to named actors so that you can easily find them. 133 | You don't have to create your own since there is a global one called ````Tribe.registry````. 134 | The Root actor is named 'root' and stored in the default registry. 135 | 136 | ```Ruby 137 | actor = Tribe.root.spawn!(Tribe::Actor, :name => 'some_actor') 138 | 139 | if actor == Tribe.registry['some_actor'] 140 | puts 'Successfully found some_actor in the registry.' 141 | end 142 | ``` 143 | 144 | ## Futures 145 | 146 | Messages are limited in that they are one way (fire-and-forget). 147 | Many times you'll be interested in receiving a response and this is when futures become useful. 148 | To send a future you use ````future!```` instead of ````message!````. 149 | It will return a ````Future```` object (instead of ````nil````) that will give you access to the result when it becomes available. 150 | 151 | #### Non-blocking API 152 | 153 | Non-blocking futures are asynchronous and use callbacks. 154 | No waiting for a result is involved and the actor will continue to process other events. 155 | 156 | ```Ruby 157 | class ActorA < Tribe::Actor 158 | private 159 | def on_start(event) 160 | friend = registry['actor_b'] 161 | future = future!(friend, :compute, 10) 162 | 163 | future.success do |result| 164 | puts "ActorA (#{identifier}) future result: #{result}" 165 | end 166 | end 167 | 168 | def on_shutdown(event) 169 | puts "MyActor (#{identifier}) is shutting down." 170 | end 171 | end 172 | 173 | class ActorB < Tribe::Actor 174 | private 175 | def on_shutdown(event) 176 | puts "MyActor (#{identifier}) is shutting down." 177 | end 178 | def on_compute(event) 179 | return factorial(event.data) 180 | end 181 | 182 | def factorial(num) 183 | return 1 if num <= 0 184 | return num * factorial(num - 1) 185 | end 186 | end 187 | 188 | actor_a = Tribe.root.spawn!(ActorA, :name => 'actor_a') 189 | actor_b = Tribe.root.spawn!(ActorB, :name => 'actor_b') 190 | 191 | actor_a.direct_message!(:start) 192 | 193 | # Shutdown the actors. 194 | sleep(3) 195 | actor_a.shutdown! 196 | actor_b.shutdown! 197 | ``` 198 | 199 | #### Blocking API 200 | 201 | Blocking futures are synchronous. 202 | The actor won't process any other events until the future has a result. 203 | 204 | ```Ruby 205 | class ActorA < Tribe::Actor 206 | private 207 | def on_start(event) 208 | friend = registry['actor_b'] 209 | future = future!(friend, :compute, 10) 210 | 211 | wait!(future) # The current thread will sleep until a result is available. 212 | 213 | if future.success? 214 | puts "ActorA (#{identifier}) future result: #{future.result}" 215 | else 216 | puts "ActorA (#{identifier}) future failure: #{future.result}" 217 | end 218 | end 219 | end 220 | 221 | class ActorB < Tribe::Actor 222 | private 223 | def on_compute(event) 224 | return factorial(event.data) 225 | end 226 | 227 | def factorial(num) 228 | return 1 if num <= 0 229 | return num * factorial(num - 1) 230 | end 231 | end 232 | 233 | actor_a = Tribe.root.spawn!(ActorA, :name => 'actor_a') 234 | actor_b = Tribe.root.spawn!(ActorB, :name => 'actor_b') 235 | 236 | actor_a.direct_message!(:start) 237 | 238 | sleep(3) 239 | 240 | actor_a.shutdown! 241 | actor_b.shutdown! 242 | ``` 243 | 244 | #### Timeouts 245 | 246 | Futures can be confgured to timeout after a specified number of seconds. 247 | When a timeout occurs, the result of the future will be a ````Tribe::FutureTimeout```` exception. 248 | 249 | ```Ruby 250 | class ActorA < Tribe::Actor 251 | private 252 | def on_start(event) 253 | friend = registry['actor_b'] 254 | future = future!(friend, :compute, 10) 255 | future.timeout = 2 256 | 257 | wait!(future) # The current thread will sleep until a result is available. 258 | 259 | if future.success? 260 | puts "ActorA (#{identifier}) future result: #{future.result}" 261 | else 262 | puts "ActorA (#{identifier}) future failure: #{future.result}" 263 | end 264 | end 265 | end 266 | 267 | class ActorB < Tribe::Actor 268 | private 269 | def on_compute(event) 270 | sleep(4) # Force a timeout. 271 | return event.data * 2 272 | end 273 | end 274 | 275 | actor_a = Tribe.root.spawn!(ActorA, :name => 'actor_a') 276 | actor_b = Tribe.root.spawn!(ActorB, :name => 'actor_b') 277 | 278 | actor_a.direct_message!(:start) 279 | 280 | sleep(6) 281 | 282 | actor_a.shutdown! 283 | actor_b.shutdown! 284 | ``` 285 | 286 | #### Performance Summary 287 | 288 | Below you will find a summary of performance recommendations for futures: 289 | 290 | - Use ````message!```` unless you really need ````future!```` since futures have overhead. 291 | - If you use ````future!````, prefer the non-blocking API over the blocking one. 292 | - If you use ````future!```` with the blocking API, the actor calling ````wait!```` will create a temporary thread. Since threads are a a finite resource, you should be careful to not create more of them than your operating system can simultaneously support. There is no such concern with the non-blocking API. 293 | 294 | ## Forwarding 295 | 296 | Messages and futures can be forwarded to other actors. 297 | This lets you build routers that delegate work to other actors. 298 | 299 | ```Ruby 300 | # Create your router class. 301 | class MyRouter < Tribe::Actor 302 | private 303 | def on_initialize(event) 304 | @processors = 100.times.map { spawn!(MyProcessor) } 305 | end 306 | 307 | def on_process(event) 308 | forward!(@processors[rand(100)]) 309 | end 310 | end 311 | 312 | # Create your processor class. 313 | class MyProcessor < Tribe::Actor 314 | private 315 | def on_process(event) 316 | puts "MyProcessor (#{identifier}) received a process event (#{event.inspect})." 317 | end 318 | end 319 | 320 | # Create the router. 321 | router = Tribe.root.spawn!(MyRouter, :name => 'router') 322 | 323 | # Send an event to the router and it will forward it to a random processor. 324 | 100.times do |i| 325 | router.direct_message!(:process, i) 326 | end 327 | 328 | # Shutdown the router. 329 | sleep(3) 330 | router.shutdown! 331 | ``` 332 | 333 | ## Timers 334 | 335 | Actors can create timers to perform some work in the future. 336 | Both one-shot and periodic timers are provided. 337 | 338 | ```Ruby 339 | class MyActor < Tribe::Actor 340 | private 341 | def on_initialize(event) 342 | timer!(1, :timer, 'hello once') 343 | periodic_timer!(1, :periodic_timer, 'hello many times') 344 | end 345 | 346 | def on_timer(event) 347 | puts "MyActor (#{identifier}) ONE-SHOT: #{event.data}" 348 | end 349 | 350 | def on_periodic_timer(event) 351 | puts "MyActor (#{identifier}) PERIODIC: #{event.data}" 352 | end 353 | end 354 | 355 | # Create some named actors. 356 | 10.times do |i| 357 | Tribe.root.spawn!(MyActor, :name => "my_actor_#{i}") 358 | end 359 | 360 | # Sleep in order to observe the timers. 361 | sleep(10) 362 | 363 | # Shutdown the actors. 364 | 10.times do |i| 365 | actor = Tribe.registry["my_actor_#{i}"] 366 | actor.shutdown! 367 | end 368 | ``` 369 | 370 | ## Linking 371 | 372 | Linking allows actors to group together so that they all live or die together. 373 | Such linking is useful for breaking up complex problems into multiple smaller units. 374 | To create a linked actor you use the ````spawn!```` method. 375 | By default, if a linked actor dies, it will cause its parent and children to die too. 376 | You an override this behavior by using supervisors. 377 | 378 | ```Ruby 379 | # Create some linked actors. 380 | top = Tribe::Actor.new 381 | middle = top.spawn!(Tribe::Actor) 382 | bottom = middle.spawn!(Tribe::Actor) 383 | 384 | # Force an exception on the middle actor (it has a parent and a child). 385 | middle.perform! { raise 'uh oh' } 386 | 387 | # Wait. 388 | sleep(3) 389 | 390 | # All actors died together. 391 | puts "Top: #{top.alive?}: #{top.exception.class}" 392 | puts "Middle: #{middle.alive?}: #{middle.exception.class}" 393 | puts "Bottom: #{bottom.alive?}: #{bottom.exception.class}" 394 | ``` 395 | 396 | ## Supervisors 397 | 398 | A failure in a linked actor will cause all associated actors (parent and children) to die. 399 | Supervisors can be used to block the failure from propogating. 400 | You then have the option to re-spawn the failed actor. 401 | They are created by passing ````{:supervise => true}```` as a third argument to ````spawn!````. 402 | You can then detect dead children by overriding ````on_child_died````. 403 | 404 | ```Ruby 405 | # Create some linked actors. 406 | top = Tribe::Actor.new 407 | middle = top.spawn!(Tribe::Actor, {}, {:supervise => true}) 408 | bottom = middle.spawn!(Tribe::Actor) 409 | 410 | # Force an exception on the middle actor (it has a parent and a child). 411 | middle.perform! { raise 'uh oh' } 412 | 413 | # Wait. 414 | sleep(3) 415 | 416 | # Top actor lives because it's a supervisor. The other two die. 417 | puts "Top: #{top.alive?}: #{top.exception.class}" 418 | puts "Middle: #{middle.alive?}: #{middle.exception.class}" 419 | puts "Bottom: #{bottom.alive?}: #{bottom.exception.class}" 420 | ``` 421 | 422 | #### Logging exceptions 423 | 424 | It is common practice to log actor exceptions or print them to stdout. 425 | This is easily accomplished with the ````on_exception```` handler in a base class: 426 | 427 | ```Ruby 428 | class MyBaseActor < Tribe::Actor 429 | private 430 | def on_exception(event) 431 | e = event.data[:exception] 432 | puts "#{e.class.name}: #{e.message}:\n#{e.backtrace.join("\n")}" 433 | end 434 | end 435 | 436 | class CustomActor < MyBaseActor 437 | end 438 | 439 | actor = Tribe.root.spawn!(CustomActor) 440 | actor.perform! { raise 'goodbye' } 441 | ``` 442 | 443 | Note that you should be careful to make sure ````on_exception```` never raises an exception itself. 444 | If it does, this second exception will be ignored. 445 | Thus it is best to limit the use of ````on_exception```` to logging exceptions in a common base class. 446 | 447 | ## Blocking code 448 | 449 | Occassionally you will have a need to execute blocking code in one of your actors. 450 | The most common cases of blocking code are network IO, disk IO, database queries, and the ````sleep```` function. 451 | 452 | Actors have a convenient method named ````blocking!```` that you should use to wrap such code. 453 | Under the hood this method is expanding and contracting the thread pool to compensate for the blocked thread. 454 | This will prevent thread pool starvation. 455 | 456 | The ````blocking!```` method is designed to work with dedicated and non-dedicated actors. 457 | By using this method in all of your actors, you will make it easy to convert between the two types. 458 | 459 | An actor's ````wait!```` method (used with futures) already calls ````blocking!```` for you. 460 | 461 | If for some reason Ruby can't create a new thread, Ruby will raise a ````ThreadError```` and your actor will die. 462 | Most modern operating systems can support many thousands of simultanous threads so refer to your operating system documentation as you may need to increase the limits. 463 | 464 | To support in the tens of thousands, hundreds of thousands, or potentially millions of actors, you will need to use non-blocking actors. 465 | 466 | ```Ruby 467 | class MyActor < Tribe::Actor 468 | private 469 | def on_start(event) 470 | blocking! do 471 | sleep 6 472 | end 473 | end 474 | 475 | end 476 | 477 | # Print the default pool size. 478 | puts "Pool size (before): #{Workers.pool.size}" 479 | 480 | # Spawn some actors that go to sleep for a bit. 481 | 100.times do 482 | actor = Tribe.root.spawn!(MyActor) 483 | actor.direct_message!(:start) 484 | end 485 | 486 | # Wait for all of the actors to sleep. 487 | sleep(2) 488 | 489 | # The pool size is increased by 100 threads. 490 | puts "Pool size (during): #{Workers.pool.size}" 491 | 492 | # Wait for all of the actors to stop sleeping. 493 | sleep(10) 494 | 495 | # The pool size is back to the default size. 496 | puts "Pool size (after): #{Workers.pool.size}" 497 | ``` 498 | 499 | ## Logging 500 | 501 | Tribe provides a shared instance of ````Logger```` for your convenience: 502 | 503 | ```Ruby 504 | Tribe.logger 505 | ``` 506 | 507 | Every actor also has access to it through the ````logger```` convenience method. 508 | This local instance of the logger is wrapped in a proxy for your convenience. 509 | This way your code can assume the logger exists even if ````Tribe.logger```` is set to ````nil````. 510 | 511 | ```Ruby 512 | class MyActor < Tribe::Actor 513 | private 514 | def on_initialize(event) 515 | logger.debug("hello world.") 516 | end 517 | end 518 | 519 | actor = MyActor.new 520 | actor.perform! { raise 'uh oh' } 521 | ``` 522 | 523 | By default, the logger will log to STDOUT. 524 | You should change this to a file in your application. 525 | 526 | ## Debugging 527 | 528 | Tribe is written in pure Ruby so it will work with all existing debuggers that support Ruby & threads. 529 | [Byebug](https://github.com/deivid-rodriguez/byebug) is commonly used with MRI Ruby 2.X and will let you set breakpoints. 530 | 531 | The most common problem you will encounter with actors is that they die due to exceptions. 532 | You can access the exception by calling the ````exception```` method on the actor: 533 | 534 | ```Ruby 535 | actor = Tribe::Actor.new 536 | actor.perform! { raise 'goodbye' } 537 | sleep(3) 538 | e = actor.exception 539 | puts "#{e.class.name}: #{e.message}:\n#{e.backtrace.join("\n")}" 540 | ``` 541 | 542 | ## Benchmarks 543 | 544 | Please see the [performance](https://github.com/chadrem/tribe/wiki/Performance) wiki page for more information. 545 | 546 | ## Ruby's main thread 547 | 548 | Please read [Ruby's main thread](https://github.com/chadrem/workers#rubys-main-thread) on the Workers gem homepage. Tribe is asynchrous and it is your responsibility to keep the main thread from exiting. 549 | 550 | ## Contributing 551 | 552 | Bug reports and pull requests are welcome on GitHub at https://github.com/chadrem/tribe. 553 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList['test/**/*_test.rb'] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "tribe" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /lib/tribe.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | require 'logger' 3 | require 'thread' 4 | 5 | require 'workers' 6 | 7 | require 'tribe/version' 8 | require 'tribe/safe_set' 9 | require 'tribe/exceptions' 10 | require 'tribe/event' 11 | require 'tribe/mailbox' 12 | require 'tribe/actor_state' 13 | require 'tribe/actable' 14 | require 'tribe/actor' 15 | require 'tribe/dedicated_actor' 16 | require 'tribe/registry' 17 | require 'tribe/future' 18 | require 'tribe/root' 19 | 20 | module Tribe 21 | @lock = Monitor.new 22 | 23 | class << self 24 | attr_reader :lock 25 | end 26 | 27 | def self.registry 28 | @lock.synchronize do 29 | @registry ||= Tribe::Registry.new 30 | end 31 | end 32 | 33 | def self.root 34 | @lock.synchronize do 35 | @root ||= Tribe::Root.new(:name => 'root', :permit_root => true) 36 | end 37 | end 38 | 39 | def self.logger 40 | @lock.synchronize do 41 | @logger 42 | end 43 | end 44 | 45 | def self.logger=(val) 46 | @lock.synchronize do 47 | @logger = val 48 | end 49 | end 50 | end 51 | 52 | Tribe.logger = Logger.new(STDOUT) 53 | Tribe.logger.level = Logger::DEBUG 54 | -------------------------------------------------------------------------------- /lib/tribe/actable.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | module Actable 3 | include Workers::Helpers 4 | 5 | private 6 | 7 | # 8 | # Initialization method. 9 | # Notes: Call this in your constructor. 10 | # 11 | 12 | def init_actable(options = {}) 13 | # Symbols aren't GCed in JRuby so force string names. 14 | if options[:name] && !options[:name].is_a?(String) 15 | raise Tribe::ActorNameError.new('Name must be a string.') 16 | end 17 | 18 | @_actable = Tribe::ActorState.new 19 | 20 | @_actable.dedicated = options[:dedicated] || false 21 | @_actable.pool = @_actable.dedicated ? Workers::Pool.new(:size => 1) : (options[:pool] || Workers.pool) 22 | @_actable.mailbox = Tribe::Mailbox.new(@_actable.pool) 23 | @_actable.registry = options[:registry] || Tribe.registry 24 | @_actable.logger = Workers::LogProxy.new(options[:logger] || Tribe.logger) 25 | @_actable.scheduler = options[:scheduler] || Workers.scheduler 26 | @_actable.name = options[:name] 27 | @_actable.parent = options[:parent] 28 | @_actable.children = Tribe::SafeSet.new 29 | @_actable.supervisees = Tribe::SafeSet.new 30 | @_actable.timers = Tribe::SafeSet.new 31 | 32 | @_actable.registry.register(self) 33 | 34 | direct_message!(:__initialize__) 35 | end 36 | 37 | # 38 | # Thread safe public methods. 39 | # Notes: These are the methods that actors use to communicate with each other. 40 | # Actors should avoid sharing mutable state in order to remain thread safe. 41 | # Methods with a ! are designed for asynchronous communication. 42 | # 43 | 44 | public 45 | 46 | def deliver_event!(event) 47 | @_actable.mailbox.push(event) do 48 | process_events 49 | end 50 | 51 | nil 52 | end 53 | 54 | def direct_message!(command, data = nil, src = nil) 55 | deliver_event!(Tribe::Event.new(command, data, src)) 56 | 57 | nil 58 | end 59 | 60 | def shutdown! 61 | direct_message!(:__shutdown__) 62 | end 63 | 64 | def perform!(&block) 65 | direct_message!(:__perform__, block) 66 | end 67 | 68 | def spawn!(klass, actor_options = {}, spawn_options = {}) 69 | actor_options[:parent] = self 70 | 71 | child = nil 72 | 73 | if spawn_options[:no_raise_on_failure] 74 | begin 75 | child = klass.new(actor_options) 76 | rescue Exception 77 | return false 78 | end 79 | else 80 | child = klass.new(actor_options) 81 | end 82 | 83 | @_actable.children.add(child) 84 | 85 | if spawn_options[:supervise] 86 | @_actable.supervisees.add(child) 87 | end 88 | 89 | child 90 | end 91 | 92 | def alive? 93 | @_actable.mailbox.alive? 94 | end 95 | 96 | def dead? 97 | !alive? 98 | end 99 | 100 | def name 101 | @_actable.name 102 | end 103 | 104 | def identifier 105 | @_actable.name ? "#{object_id}:#{@_actable.name}" : object_id 106 | end 107 | 108 | def exception 109 | @_actable.exception 110 | end 111 | 112 | def registry 113 | @_actable.registry 114 | end 115 | 116 | def pool 117 | @_actable.pool 118 | end 119 | 120 | def logger 121 | @_actable.logger 122 | end 123 | 124 | # 125 | # Private command handlers. 126 | # Notes: These methods are designed to be overriden in order to respond to actor system events. 127 | # 128 | 129 | private 130 | 131 | def on_initialize(event) 132 | end 133 | 134 | def on_exception(event) 135 | end 136 | 137 | def on_shutdown(event) 138 | end 139 | 140 | def on_child_died(event) 141 | end 142 | 143 | def on_child_shutdown(event) 144 | end 145 | 146 | def on_parent_died(event) 147 | end 148 | 149 | # 150 | # Private event system methods. 151 | # Notes: These methods are designed to be overriden by advanced users only. Overriding is very rare! 152 | # 153 | 154 | private 155 | 156 | # All system commands are prefixed with an underscore. 157 | def process_events 158 | while (event = @_actable.mailbox.obtain_and_shift) 159 | event_handler(event) 160 | end 161 | 162 | rescue Exception => exception 163 | cleanup_handler(exception) 164 | exception_handler(exception) 165 | ensure 166 | @_actable.mailbox.release do 167 | process_events 168 | end 169 | 170 | nil 171 | end 172 | 173 | def event_handler(event) 174 | case event.command 175 | when :__initialize__ 176 | initialize_handler(event) 177 | when :__shutdown__ 178 | cleanup_handler 179 | shutdown_handler(event) 180 | when :__perform__ 181 | perform_handler(event) 182 | when :__child_died__ 183 | child_died_handler(event.data[0], event.data[1]) 184 | when :__child_shutdown__ 185 | child_shutdown_handler(event.data) 186 | when :__parent_died__ 187 | parent_died_handler(event.data[0], event.data[1]) 188 | when :initialize, :shutdown, :perform, :child_died, :child_shutdown, :parent_died 189 | raise ActorReservedCommand.new("Reserved commands are not allowed (command=#{event.command}).") 190 | else 191 | custom_event_handler(event) 192 | end 193 | end 194 | 195 | def custom_event_handler(event) 196 | result = nil 197 | @_actable.active_event = event 198 | 199 | begin 200 | result = send("on_#{event.command}", event) 201 | rescue Exception => e 202 | result = e 203 | raise 204 | ensure 205 | if event.future && @_actable.active_event 206 | event.future.result = result 207 | end 208 | @_actable.active_event = nil 209 | end 210 | 211 | nil 212 | end 213 | 214 | def initialize_handler(event) 215 | on_initialize(event) 216 | end 217 | 218 | def exception_handler(exception) 219 | if @_actable.parent 220 | @_actable.parent.direct_message!(:__child_died__, [self, exception]) 221 | end 222 | 223 | @_actable.children.each { |c| c.direct_message!(:__parent_died__, [self, exception]) } 224 | @_actable.children.clear 225 | @_actable.supervisees.clear 226 | 227 | log_exception_handler(exception) 228 | on_exception(Event.new(:exception, {:exception => exception})) 229 | 230 | nil 231 | end 232 | 233 | def log_exception_handler(exception) 234 | logger.error("EXCEPTION: #{exception.message}\n#{exception.backtrace.join("\n")}\n--") 235 | end 236 | 237 | def shutdown_handler(event) 238 | if @_actable.parent 239 | @_actable.parent.direct_message!(:__child_shutdown__, self) 240 | end 241 | 242 | @_actable.children.each { |c| c.shutdown! } 243 | @_actable.children.clear 244 | @_actable.supervisees.clear 245 | 246 | on_shutdown(Event.new(:shutdown, {})) 247 | 248 | nil 249 | end 250 | 251 | def perform_handler(event) 252 | event.data.call 253 | 254 | nil 255 | end 256 | 257 | def cleanup_handler(exception = nil) 258 | @_actable.exception = exception 259 | @_actable.pool.shutdown if @_actable.dedicated 260 | @_actable.mailbox.kill 261 | @_actable.registry.unregister(self) 262 | @_actable.timers.each { |t| t.cancel } if @_actable.timers 263 | 264 | nil 265 | end 266 | 267 | def child_died_handler(child, exception) 268 | @_actable.children.delete(child) 269 | supervising = !!@_actable.supervisees.delete?(child) 270 | 271 | on_child_died(Event.new(:child_died, {:child => child, :exception => exception})) 272 | 273 | if !supervising 274 | raise Tribe::ActorChildDied.new("#{child.identifier} died.") 275 | end 276 | 277 | nil 278 | end 279 | 280 | def child_shutdown_handler(child) 281 | @_actable.children.delete(child) 282 | @_actable.supervisees.delete(child) 283 | 284 | on_child_shutdown(Event.new(:child_shutdown, {:child => child})) 285 | 286 | nil 287 | end 288 | 289 | def parent_died_handler(parent, exception) 290 | on_parent_died(Event.new(:parent_died, {:parent => parent, :exception => exception})) 291 | raise Tribe::ActorParentDied.new("#{parent.identifier} died.") 292 | 293 | nil 294 | end 295 | 296 | # 297 | # Private API methods. 298 | # Notes: Use these methods internally in your actor. 299 | # 300 | 301 | private 302 | 303 | def message!(dest, command, data = nil) 304 | event = Tribe::Event.new(command, data, self) 305 | 306 | dest.deliver_event!(event) 307 | 308 | nil 309 | end 310 | 311 | def future!(dest, command, data = nil) 312 | event = Tribe::Event.new(command, data, self) 313 | event.future = future = Tribe::Future.new(self) 314 | 315 | dest.deliver_event!(event) 316 | 317 | future 318 | end 319 | 320 | def timer!(delay, command, data = nil) 321 | timer = Workers::Timer.new(delay, :scheduler => @_actable.scheduler) do 322 | @_actable.timers.delete(timer) 323 | direct_message!(command, data) 324 | end 325 | 326 | @_actable.timers.add(timer) 327 | 328 | timer 329 | end 330 | 331 | def periodic_timer!(delay, command, data = nil) 332 | timer = Workers::PeriodicTimer.new(delay, :scheduler => @_actable.scheduler) do 333 | direct_message!(command, data) 334 | unless alive? 335 | @_actable.timers.delete(timer) 336 | timer.cancel 337 | end 338 | end 339 | 340 | @_actable.timers.add(timer) 341 | 342 | timer 343 | end 344 | 345 | def forward!(dest) 346 | dest.deliver_event!(@_actable.active_event) 347 | @_actable.active_event = nil 348 | 349 | nil 350 | end 351 | 352 | # Wrap blocking code using this method to automatically expand/contract the pool. 353 | # This way you avoid potential thread starvation. Not needed for dedicated actors 354 | # since they already have their own thread. 355 | def blocking! 356 | if @_actable.dedicated 357 | yield 358 | else 359 | pool.expand(1) 360 | begin 361 | yield 362 | ensure 363 | pool.contract(1) 364 | end 365 | end 366 | end 367 | 368 | def wait!(future) 369 | blocking! do 370 | future.wait 371 | end 372 | end 373 | end 374 | end 375 | -------------------------------------------------------------------------------- /lib/tribe/actor.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class Actor 3 | include Tribe::Actable 4 | 5 | def initialize(options = {}) 6 | init_actable(options) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/tribe/actor_state.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class ActorState 3 | attr_accessor :dedicated 4 | attr_accessor :mailbox 5 | attr_accessor :registry 6 | attr_accessor :scheduler 7 | attr_accessor :timers 8 | attr_accessor :name 9 | attr_accessor :pool 10 | attr_accessor :active_event 11 | attr_accessor :parent 12 | attr_accessor :children 13 | attr_accessor :supervisees 14 | attr_accessor :exception 15 | attr_accessor :logger 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/tribe/benchmark.rb: -------------------------------------------------------------------------------- 1 | require 'tribe/benchmark/throughput' 2 | -------------------------------------------------------------------------------- /lib/tribe/benchmark/throughput.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | module Benchmark 3 | module Throughput 4 | MAX_INCR = 100000 5 | ACTOR_COUNT = 100000 6 | COUNTERS = 20 7 | 8 | def self.run 9 | ACTOR_COUNT.times do |i| 10 | actor = Tribe.registry["actor_#{i}"] 11 | actor.shutdown! if actor 12 | end 13 | 14 | $start_time = Time.now.utc 15 | $finished = 0 16 | $lock = Mutex.new 17 | 18 | ACTOR_COUNT.times do |i| 19 | MyActor.new(:name => "actor_#{i}") 20 | end 21 | 22 | COUNTERS.times do |i| 23 | Tribe.registry["actor_#{rand(ACTOR_COUNT)}"].direct_message!(:do_stuff, MyData.new("data_#{i}")) 24 | end 25 | 26 | $lock.synchronize do 27 | puts 'Please wait...' 28 | end 29 | end 30 | 31 | def self.stop 32 | end 33 | 34 | class MyData 35 | def initialize(name) 36 | @name = name 37 | @counter = 0 38 | @start_time = Time.now 39 | end 40 | 41 | def increment 42 | @counter += 1 43 | 44 | if @counter >= MAX_INCR 45 | $lock.synchronize do 46 | $finished += 1 47 | 48 | if $finished == COUNTERS 49 | puts "\nFinished! Rate=#{(COUNTERS * MAX_INCR).to_f / (Time.now.utc - $start_time).to_f } msgs/sec\n" 50 | end 51 | end 52 | 53 | return false 54 | end 55 | 56 | return true 57 | end 58 | end 59 | 60 | class MyActor < Tribe::Actor 61 | private 62 | def on_do_stuff(event) 63 | if event.data.increment 64 | Tribe.registry["actor_#{rand(ACTOR_COUNT)}"].direct_message!(:do_stuff, event.data) 65 | end 66 | end 67 | 68 | def on_exception(event) 69 | puts concat_e("MyActor (#{identifier}) died.", event.data) 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/tribe/dedicated_actor.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class DedicatedActor < Tribe::Actor 3 | private 4 | 5 | def initialize(options = {}) 6 | options[:dedicated] = true 7 | 8 | super(options) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/tribe/event.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class Event 3 | 4 | attr_accessor :command 5 | attr_accessor :data 6 | attr_accessor :source 7 | attr_accessor :future 8 | 9 | def initialize(command, data, source = nil) 10 | @command = command 11 | @data = data 12 | @source = source 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tribe/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class TribeError < RuntimeError; end 3 | 4 | class ActorShutdownError < TribeError; end 5 | class ActorNameError < TribeError; end 6 | class ActorChildDied < TribeError; end 7 | class ActorParentDied < TribeError; end 8 | class ActorReservedCommand < TribeError; end 9 | 10 | class FutureError < TribeError; end 11 | class FutureNoResult < TribeError; end 12 | class FutureTimeout < TribeError; end 13 | 14 | class RegistryError < TribeError; end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tribe/future.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class Future 3 | def initialize(actor = nil) 4 | @state = :initialized 5 | @lock = Mutex.new 6 | @condition = ConditionVariable.new 7 | @result = nil 8 | @success_callback = nil 9 | @failure_callback = nil 10 | @actor = actor 11 | @timer = nil 12 | @timeout = nil 13 | 14 | return nil 15 | end 16 | 17 | def finished? 18 | @lock.synchronize do 19 | return @state == :finished 20 | end 21 | end 22 | 23 | def timeout? 24 | @lock.synchronnize do 25 | return @state == :finished && @result.is_a?(Tribe::FutureTimeout) 26 | end 27 | end 28 | 29 | def result=(val) 30 | @lock.synchronize do 31 | return unless @state == :initialized 32 | 33 | @timer.cancel if @timer 34 | 35 | @result = val 36 | @state = :finished 37 | @condition.signal 38 | 39 | if val.is_a?(Exception) 40 | if @failure_callback 41 | if @actor 42 | @actor.perform! do 43 | @failure_callback.call(val) 44 | end 45 | else 46 | @failure_callback.call(val) 47 | end 48 | end 49 | else 50 | if @success_callback 51 | if @actor 52 | @actor.perform! do 53 | @success_callback.call(val) 54 | end 55 | else 56 | @success_callback.call(val) 57 | end 58 | end 59 | end 60 | 61 | return nil 62 | end 63 | end 64 | 65 | def result 66 | @lock.synchronize do 67 | raise Tribe::FutureNoResult.new('Result must be set first.') unless @state == :finished 68 | 69 | return @result 70 | end 71 | end 72 | 73 | def wait 74 | @lock.synchronize do 75 | return if @state == :finished 76 | 77 | # The wait can return even if nothing called @conditional.signal, 78 | # so we need to check to see if the condition actually changed. 79 | # See https://github.com/chadrem/workers/issues/7 80 | loop do 81 | @condition.wait(@lock) 82 | break if @state == :finished 83 | end 84 | 85 | return nil 86 | end 87 | end 88 | 89 | def success? 90 | @lock.synchronize do 91 | raise Tribe::FutureNoResult.new('Result must be set first.') unless @state == :finished 92 | 93 | return !@result.is_a?(Exception) 94 | end 95 | end 96 | 97 | def failure? 98 | return !success? 99 | end 100 | 101 | def success(&block) 102 | @lock.synchronize do 103 | case @state 104 | when :initialized 105 | @success_callback = block 106 | when :finished 107 | unless @result.is_a?(Exception) 108 | if @actor 109 | @actor.perform! do 110 | block.call(@result) 111 | end 112 | else 113 | block.call(@result) 114 | end 115 | end 116 | else 117 | raise Tribe::FutureError.new('Invalid state.') 118 | end 119 | 120 | return nil 121 | end 122 | end 123 | 124 | def failure(&block) 125 | @lock.synchronize do 126 | case @state 127 | when :initialized 128 | @failure_callback = block 129 | when :finished 130 | if @result.is_a?(Exception) 131 | if @actor 132 | @actor.perform! do 133 | block.call(@result) 134 | end 135 | else 136 | block.call(@result) 137 | end 138 | end 139 | else 140 | raise Tribe::FutureError.new('Invalid state.') 141 | end 142 | 143 | return nil 144 | end 145 | end 146 | 147 | def timeout 148 | @lock.synchronize do 149 | @timeout 150 | end 151 | end 152 | 153 | def timeout=(val) 154 | raise Tribe::FutureError.new('Timeout may only be set once.') if @timeout 155 | 156 | @timeout = val 157 | 158 | @timer = Workers::Timer.new(val) do 159 | begin 160 | raise Tribe::FutureTimeout.new("Timeout after #{@timeout} seconds.") 161 | rescue Tribe::FutureTimeout => e 162 | self.result = e 163 | end 164 | end 165 | end 166 | end 167 | end 168 | -------------------------------------------------------------------------------- /lib/tribe/mailbox.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class Mailbox 3 | def initialize(pool) 4 | @pool = pool 5 | @messages = [] 6 | @alive = true 7 | @lock = Mutex.new 8 | @owner_thread = nil 9 | end 10 | 11 | def push(event, &block) 12 | @lock.synchronize do 13 | return nil unless @alive 14 | 15 | @messages.push(event) 16 | @pool.perform { block.call } unless @owner_thread 17 | end 18 | 19 | return nil 20 | end 21 | 22 | def obtain_and_shift 23 | @lock.synchronize do 24 | return nil unless @alive 25 | 26 | if @owner_thread 27 | if @owner_thread == Thread.current 28 | return @messages.shift 29 | else 30 | return nil 31 | end 32 | else 33 | @owner_thread = Thread.current 34 | return @messages.shift 35 | end 36 | end 37 | end 38 | 39 | def release(&block) 40 | @lock.synchronize do 41 | return nil unless @owner_thread == Thread.current 42 | 43 | @owner_thread = nil 44 | @pool.perform { block.call } if @alive && @messages.length > 0 45 | end 46 | 47 | return nil 48 | end 49 | 50 | def kill 51 | @lock.synchronize do 52 | @alive = false 53 | @messages.clear 54 | end 55 | 56 | return nil 57 | end 58 | 59 | def alive? 60 | @lock.synchronize do 61 | return @alive 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/tribe/registry.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class Registry 3 | def initialize 4 | @mutex = Mutex.new 5 | @actors_by_name = {} 6 | @actors_by_oid = {} 7 | end 8 | 9 | def register(actor) 10 | @mutex.synchronize do 11 | raise Tribe::RegistryError.new("Actor already exists (#{actor.name}).") if @actors_by_name.key?(actor.name) 12 | 13 | @actors_by_name[actor.name] = actor if actor.name 14 | @actors_by_oid[actor.object_id] = actor 15 | 16 | return nil 17 | end 18 | end 19 | 20 | def unregister(actor) 21 | @mutex.synchronize do 22 | @actors_by_name.delete(actor.name) if actor.name 23 | @actors_by_oid.delete(actor.object_id) 24 | 25 | return nil 26 | end 27 | end 28 | 29 | def [](val) 30 | @mutex.synchronize do 31 | return @actors_by_name[val] 32 | end 33 | end 34 | 35 | def dispose 36 | @mutex.synchronize do 37 | @actors_by_name.clear 38 | @actors_by_oid.clear 39 | 40 | return nil 41 | end 42 | end 43 | 44 | def inspect 45 | @mutex.synchronize do 46 | return "#<#{self.class.to_s}:0x#{(object_id << 1).to_s(16)} oid_count=#{@actors_by_oid.count}, named_count=#{@actors_by_name.count}>" 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/tribe/root.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | class Root < Tribe::DedicatedActor 3 | private 4 | 5 | def initialize(options = {}) 6 | unless options[:permit_root] 7 | raise 'Application code should never create the root actor.' 8 | end 9 | 10 | options.delete(:permit_root) 11 | 12 | super 13 | end 14 | 15 | def child_died_handler(child, exception) 16 | # Let the children die silently since the root actor should live forever. 17 | begin 18 | super 19 | rescue Tribe::ActorChildDied 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/tribe/safe_set.rb: -------------------------------------------------------------------------------- 1 | # Ruby's built in Set class may not be thread safe. 2 | # This class wraps each method to make it so. 3 | # More methods will be wrapped as needed. 4 | 5 | module Tribe 6 | class SafeSet 7 | def initialize 8 | @mutex = Mutex.new 9 | @set = Set.new 10 | 11 | return nil 12 | end 13 | 14 | def add(item) 15 | @mutex.synchronize do 16 | return @set.add(item) 17 | end 18 | end 19 | 20 | def delete(item) 21 | @mutex.synchronize do 22 | return @set.delete(item) 23 | end 24 | end 25 | 26 | def delete?(item) 27 | @mutex.synchronize do 28 | return @set.delete?(item) 29 | end 30 | end 31 | 32 | def each(&block) 33 | @mutex.synchronize do 34 | return @set.each do |item| 35 | yield(item) 36 | end 37 | end 38 | end 39 | 40 | def clear 41 | @mutex.synchronize do 42 | return @set.clear 43 | end 44 | end 45 | 46 | def size 47 | @mutex.synchronize do 48 | return @set.size 49 | end 50 | end 51 | 52 | def to_a 53 | @mutex.synchronize do 54 | return @set.to_a 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/tribe/version.rb: -------------------------------------------------------------------------------- 1 | module Tribe 2 | VERSION = '0.6.5' 3 | end 4 | -------------------------------------------------------------------------------- /test/actable_exception_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ExceptionTestActor < TestActor 4 | attr_reader :success 5 | 6 | private 7 | 8 | def on_divide_by_zero(event) 9 | 0 / 0 10 | end 11 | 12 | def on_exception(event) 13 | @success = true 14 | end 15 | end 16 | 17 | class ActableExceptionTest < Minitest::Test 18 | def test_exception 19 | actor = ExceptionTestActor.new 20 | actor.run 21 | actor.direct_message!(:divide_by_zero) 22 | 23 | poll { actor.dead? } 24 | 25 | assert_equal(:divide_by_zero, actor.events[1].command) 26 | assert_kind_of(ZeroDivisionError, actor.exception) 27 | assert_equal(false, actor.alive?) 28 | assert(actor.success) 29 | ensure 30 | actor.shutdown! 31 | end 32 | end -------------------------------------------------------------------------------- /test/actable_future_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FutureTestParentActor < TestActor 4 | attr_reader :result 5 | attr_reader :child 6 | attr_reader :future 7 | 8 | private 9 | 10 | def on_start_blocking(event) 11 | @child = spawn!(FutureTestChildActor) 12 | @future = future!(@child, :compute, event.data) 13 | 14 | wait!(@future) 15 | shutdown! 16 | end 17 | 18 | def on_start_non_blocking(event) 19 | @child = spawn!(FutureTestChildActor) 20 | @future = future!(@child, :compute, event.data) 21 | 22 | @future.success do |result| 23 | @result = result 24 | shutdown! 25 | end 26 | 27 | @future.failure do |exception| 28 | @result = exception 29 | shutdown! 30 | end 31 | end 32 | 33 | def on_start_non_blocking_delayed(event) 34 | @child = spawn!(FutureTestChildActor, {}, :supervise => true) 35 | @future = future!(@child, :compute, event.data) 36 | 37 | sleep(0.2) 38 | 39 | @future.success do |result| 40 | @result = result 41 | shutdown! 42 | end 43 | 44 | @future.failure do |exception| 45 | @result = exception 46 | shutdown! 47 | end 48 | end 49 | 50 | def on_start_blocking_timeout(event) 51 | @child = spawn!(FutureTestChildActor) 52 | @future = future!(@child, :sleep) 53 | @future.timeout = 0.1 54 | 55 | wait!(@future) 56 | shutdown! 57 | end 58 | 59 | def on_start_non_blocking_timeout(event) 60 | @child = spawn!(FutureTestChildActor) 61 | @future = future!(@child, :sleep) 62 | @future.timeout = 0.1 63 | 64 | @future.success do |result| 65 | @result = result 66 | shutdown! 67 | end 68 | 69 | @future.failure do |exception| 70 | @result = exception 71 | shutdown! 72 | end 73 | end 74 | end 75 | 76 | class FutureTestChildActor < Tribe::Actor 77 | attr_reader :success 78 | 79 | private 80 | 81 | def on_compute(event) 82 | event.data ** 2 83 | end 84 | 85 | def on_sleep(event) 86 | sleep(0.2) 87 | @success = true 88 | rescue Exception => e 89 | puts e.inspect 90 | end 91 | end 92 | 93 | # 94 | # BLocking API tests. 95 | # 96 | 97 | class ActableFutureTest < Minitest::Test 98 | def test_blocking_success 99 | actor = FutureTestParentActor.new 100 | actor.run 101 | actor.direct_message!(:start_blocking, 10) 102 | 103 | poll { actor.dead? } 104 | 105 | assert_equal(100, actor.future.result ) 106 | ensure 107 | actor.shutdown! 108 | end 109 | 110 | def test_blocking_exception 111 | actor = FutureTestParentActor.new 112 | actor.run 113 | actor.direct_message!(:start_blocking, nil) 114 | 115 | poll { actor.dead? } 116 | 117 | assert_kind_of(NoMethodError, actor.future.result) 118 | ensure 119 | actor.shutdown! 120 | end 121 | 122 | def test_blocking_timeout 123 | actor = FutureTestParentActor.new 124 | actor.run 125 | actor.direct_message!(:start_blocking_timeout) 126 | 127 | poll { actor.dead? } 128 | 129 | assert_equal(false, actor.future.success?) 130 | assert_kind_of(Tribe::FutureTimeout, actor.future.result) 131 | ensure 132 | actor.shutdown! 133 | end 134 | 135 | # 136 | # Non-blocking API tests. 137 | # 138 | 139 | def test_non_blocking_success 140 | actor = FutureTestParentActor.new 141 | actor.run 142 | actor.direct_message!(:start_non_blocking, 10) 143 | 144 | poll { actor.dead? } 145 | 146 | assert_equal(100, actor.future.result) 147 | assert_equal(100, actor.result) 148 | ensure 149 | actor.shutdown! 150 | end 151 | 152 | def test_non_blocking_success_delayed 153 | actor = FutureTestParentActor.new 154 | actor.run 155 | actor.direct_message!(:start_non_blocking_delayed, 10) 156 | 157 | poll { actor.dead? } 158 | 159 | assert_equal(100, actor.future.result) 160 | assert_equal(actor.future.result, actor.result) 161 | ensure 162 | actor.shutdown! 163 | actor.child.shutdown! 164 | end 165 | 166 | def test_non_blocking_exception 167 | actor = FutureTestParentActor.new 168 | actor.run 169 | actor.direct_message!(:start_non_blocking, nil) 170 | 171 | poll { actor.dead? } 172 | 173 | assert_kind_of(NoMethodError, actor.future.result) 174 | assert_equal(actor.future.result, actor.result) 175 | ensure 176 | actor.shutdown! 177 | end 178 | 179 | def test_non_blocking_exception_delayed 180 | actor = FutureTestParentActor.new 181 | actor.run 182 | actor.direct_message!(:start_non_blocking_delayed, nil) 183 | 184 | poll { actor.dead? } 185 | 186 | assert_kind_of(NoMethodError, actor.future.result) 187 | assert_equal(actor.future.result, actor.result) 188 | ensure 189 | actor.shutdown! 190 | actor.child.shutdown! 191 | end 192 | 193 | def test_non_blocking_timeout 194 | actor = FutureTestParentActor.new 195 | actor.run 196 | actor.direct_message!(:start_non_blocking_timeout) 197 | 198 | poll { actor.dead? } 199 | 200 | assert_kind_of(Tribe::FutureTimeout, actor.future.result) 201 | assert_equal(actor.future.result, actor.result) 202 | ensure 203 | actor.shutdown! 204 | end 205 | end -------------------------------------------------------------------------------- /test/actable_initialization_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class InitializationTestActor < TestActor 4 | attr_reader :success 5 | 6 | private 7 | 8 | def on_initialize(event) 9 | @success = true 10 | shutdown! 11 | end 12 | end 13 | 14 | 15 | class ActableInitializationTest < Minitest::Test 16 | def test_initialization 17 | actor = InitializationTestActor.new 18 | actor.run 19 | 20 | poll { actor.dead? } 21 | 22 | assert_equal(:__initialize__, actor.events[0].command) 23 | ensure 24 | actor.shutdown! 25 | end 26 | end -------------------------------------------------------------------------------- /test/actable_linking_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ActableLinkingTest < Minitest::Test 4 | def test_parent_and_children_should_both_die 5 | top = Tribe::Actor.new 6 | middle = top.spawn!(Tribe::Actor) 7 | bottom = middle.spawn!(Tribe::Actor) 8 | 9 | middle.perform! { raise 'uh oh' } 10 | 11 | poll { top.dead? } 12 | poll { middle.dead? } 13 | poll { bottom.dead? } 14 | 15 | assert(top.dead?) 16 | assert(middle.dead?) 17 | assert(bottom.dead?) 18 | assert_kind_of(Tribe::ActorChildDied, top.exception) 19 | assert_kind_of(RuntimeError, middle.exception) 20 | assert_kind_of(Tribe::ActorParentDied, bottom.exception) 21 | end 22 | 23 | def test_parent_should_not_die_if_it_is_a_supervisor 24 | top = Tribe::Actor.new 25 | middle = top.spawn!(Tribe::Actor, {}, :supervise => true) 26 | bottom = middle.spawn!(Tribe::Actor) 27 | 28 | middle.perform! { raise 'uh oh' } 29 | 30 | poll { middle.dead? } 31 | poll { bottom.dead? } 32 | poll { top.alive? } 33 | 34 | assert(middle.dead?) 35 | assert(bottom.dead?) 36 | assert(top.alive?) 37 | assert_kind_of(Tribe::ActorParentDied, bottom.exception) 38 | end 39 | end -------------------------------------------------------------------------------- /test/actable_perform_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PerformTestActor < TestActor 4 | end 5 | 6 | class ActablePerformTest < Minitest::Test 7 | def test_perform 8 | success = false 9 | actor = PerformTestActor.new 10 | actor.run 11 | actor.perform! { success = true } 12 | actor.shutdown! 13 | 14 | poll { actor.dead? } 15 | 16 | assert_equal(:__perform__, actor.events[1].command) 17 | assert(success) 18 | ensure 19 | actor.shutdown! 20 | end 21 | 22 | def test_perform_exception 23 | actor = PerformTestActor.new 24 | actor.run 25 | actor.perform! { raise 'uh oh' } 26 | 27 | poll { actor.dead? } 28 | 29 | assert_kind_of(RuntimeError, actor.exception) 30 | assert_equal(false, actor.alive?) 31 | ensure 32 | actor.shutdown! 33 | end 34 | end -------------------------------------------------------------------------------- /test/actable_shutdown_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ShutdownTestActor < TestActor 4 | attr_reader :success 5 | 6 | private 7 | 8 | def on_shutdown(event) 9 | @success = true 10 | end 11 | end 12 | 13 | 14 | class ActableShutdownTest < Minitest::Test 15 | def test_shutdown 16 | actor = ShutdownTestActor.new 17 | actor.run 18 | 19 | assert(actor.alive?) 20 | 21 | actor.shutdown! 22 | 23 | poll { actor.dead? } 24 | 25 | assert_equal(:__shutdown__, actor.events[1].command) 26 | assert(actor.success) 27 | ensure 28 | actor.shutdown! 29 | end 30 | end -------------------------------------------------------------------------------- /test/actable_spawn_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SpawnTestParentActor < TestActor 4 | attr_reader :success 5 | attr_reader :dead_child 6 | attr_reader :child_exception 7 | 8 | private 9 | 10 | def on_child_died(event) 11 | @success = true 12 | @dead_child = event.data[:child] 13 | @child_exception = event.data[:exception] 14 | end 15 | end 16 | 17 | class SpawnTestChildActor < Tribe::Actor 18 | attr_reader :success 19 | attr_reader :dead_parent 20 | attr_reader :parent_exception 21 | 22 | def on_parent_died(event) 23 | @success = true 24 | @dead_parent = event.data[:parent] 25 | @parent_exception = event.data[:exception] 26 | end 27 | end 28 | 29 | class ActablesSpawnTest < Minitest::Test 30 | def test_spawn 31 | parent = SpawnTestParentActor.new 32 | parent.run 33 | child = parent.spawn!(SpawnTestChildActor) 34 | 35 | assert_kind_of(SpawnTestChildActor, child) 36 | ensure 37 | parent.shutdown! 38 | child.shutdown! 39 | end 40 | 41 | def test_child_death_kills_parent 42 | parent = SpawnTestParentActor.new 43 | parent.run 44 | child = parent.spawn!(SpawnTestChildActor) 45 | child.perform! { raise 'uh oh' } 46 | 47 | poll { parent.dead? } 48 | 49 | assert_equal(false, parent.alive?) 50 | assert_equal(false, child.alive?) 51 | assert(parent.success) 52 | assert_kind_of(Tribe::ActorChildDied, parent.exception) 53 | assert_kind_of(RuntimeError, child.exception) 54 | assert_equal(child, parent.dead_child) 55 | assert_equal(child.exception, parent.child_exception) 56 | end 57 | 58 | def test_parent_death_kills_child 59 | parent = SpawnTestParentActor.new 60 | parent.run 61 | child = parent.spawn!(SpawnTestChildActor) 62 | parent.perform! { raise 'uh oh' } 63 | 64 | poll { child.dead? } 65 | 66 | assert_equal(false, parent.alive?) 67 | assert_equal(false, child.alive?) 68 | assert(child.success) 69 | assert_kind_of(RuntimeError, parent.exception) 70 | assert_kind_of(Tribe::ActorParentDied, child.exception) 71 | assert_equal(parent, child.dead_parent) 72 | assert_equal(parent.exception, child.parent_exception) 73 | end 74 | end -------------------------------------------------------------------------------- /test/actable_timer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimerTestActor < TestActor 4 | attr_reader :count 5 | 6 | private 7 | 8 | def on_start_timer(event) 9 | @mode = :once 10 | @timer = timer!(0.01, :fire) 11 | end 12 | 13 | def on_start_periodic_timer(event) 14 | @mode = :periodic 15 | @timer = periodic_timer!(0.01, :fire) 16 | end 17 | 18 | def on_fire(event) 19 | @count ||= 0 20 | @count += 1 21 | shutdown! if @mode == :once || @count > 5 22 | end 23 | end 24 | 25 | class ActableTimerTest < Minitest::Test 26 | def test_timer 27 | actor = TimerTestActor.new 28 | actor.run 29 | actor.direct_message!(:start_timer) 30 | 31 | poll { actor.dead? } 32 | 33 | assert_equal(1, actor.count) 34 | ensure 35 | actor.shutdown! 36 | end 37 | 38 | def test_periodic_timer 39 | actor = TimerTestActor.new 40 | actor.run 41 | actor.direct_message!(:start_periodic_timer) 42 | 43 | poll { actor.dead? } 44 | 45 | assert(actor.count > 5) 46 | ensure 47 | actor.shutdown! 48 | end 49 | end -------------------------------------------------------------------------------- /test/actor_state_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ActorStateTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/actor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ActorTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/dedicted_actor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DedicatedActorTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/event_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class EventTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/future_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FutureTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/mailbox_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MailboxTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/register_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegistryTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/root_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RootTest < Minitest::Test 4 | def test_spawning 5 | child = Tribe.root.spawn!(Tribe::Actor) 6 | ensure 7 | child.shutdown! 8 | end 9 | 10 | def test_spawning_a_child_that_dies_does_not_kill_root 11 | child = Tribe.root.spawn!(Tribe::Actor) 12 | child.perform! { raise 'uh oh' } 13 | 14 | poll { child.dead? } 15 | 16 | assert_equal(false, child.alive?) 17 | assert(Tribe.root.alive?) 18 | ensure 19 | child.shutdown! 20 | end 21 | end -------------------------------------------------------------------------------- /test/safe_set_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SafeSetTest < Minitest::Test 4 | end -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | 4 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 5 | require 'bundler/setup' 6 | require 'tribe' 7 | 8 | class TestActor 9 | include Tribe::Actable 10 | 11 | attr_reader :events 12 | 13 | def initialize(options = {}) 14 | @events = [] 15 | @test_options = options 16 | end 17 | 18 | def run 19 | init_actable(@test_options) 20 | end 21 | 22 | def event_handler(event) 23 | @events << event 24 | super 25 | end 26 | end 27 | 28 | # Poll until seconds pass or the block returns true. 29 | def poll(seconds = 5) 30 | count = seconds * 100 31 | 32 | while count > 0 && !yield 33 | count -= 1 34 | sleep 0.01 35 | end 36 | 37 | raise 'Poll timeout.' unless yield 38 | end 39 | 40 | Tribe.logger = nil 41 | 42 | require 'minitest/autorun' 43 | -------------------------------------------------------------------------------- /test/tribe_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TribeTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::Tribe::VERSION 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /tribe.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'tribe/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "tribe" 8 | spec.version = Tribe::VERSION 9 | spec.authors = ["Chad Remesch"] 10 | spec.email = ["chad@remesch.com"] 11 | 12 | spec.summary = %q{Actors based concurrency library for Ruby.} 13 | spec.homepage = "https://github.com/chadrem/tribe" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.bindir = "exe" 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "workers", "~> 0.6.1" 22 | 23 | spec.add_development_dependency "bundler", "~> 1.10" 24 | spec.add_development_dependency "rake", "~> 10.0" 25 | spec.add_development_dependency "minitest" 26 | end 27 | --------------------------------------------------------------------------------