Actors using the Akka library in JRuby, along with some patterns of use and techniques to help manage concurrency including java.util.concurrent, Futures, and software transactional memory.
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Concurrency with JRuby and the JVM
1. Concurrency using JRuby
and the JVM
Portland Ruby Brigade
October 2, 2012
Alex Kira
@AlexKira
alex.kira@gmail.com
github.com/akira
2. What we will cover
• Background
• Survey of concurrency techniques with
JRuby / JVM
• java.util.concurrent
• Actors (in more detail)
• Futures
• STM
7. semaphore = Mutex.new
counter = 5000
threads = (1..5).collect do
Thread.new do
1000.times do
semaphore.synchronize do
counter -= 1
end
end
end
end
threads.each {|t| t.join }
puts counter This time it’s 0
10. In an OO based program, we use to classes to
encapsulate data
Class
State
Methods
Class Class Class
State State State
Methods Methods Methods
Class
State
Methods
11. Threads, however, clobber your encapsulation
Class
State
Methods
Thread 1
Thread 3
Class Class Class
State State State
Methods Methods Methods
Thread 2
Class
State
Methods
12. Hard to verify correctness
Hard to deal with lock contention
Hard to get right lock granularity
Hard to reason about
Deadlock, starvation, livelock
exception handling
24. juc - Thread Management
• Threads are not reusable - typically we end
up managing our own thread pools at a
certain scale (tasks can outnumber threads)
• Executor framework abstracts away task
execution
25. juc - Thread Management
• ExecutorService
• Few different flavors available
• FixedThreadPool, CachedThreadPool,
ScheduledThreadPool, SingleThread
• ForkJoin
• Dynamically manage thread pool based
on resources
• Threads attempt to steal subtasks
26. juc - Locks
• Going beyond a mutex and thread.join
• Synchronization primitives:
• CountDownLatch, CyclicBarrier
• ReentrantLock, ReadWriteLock
27. juc - Collections
• Efficient data structures that be shared by multiple
threads
• ConcurrentHashMap
• CopyOnWriteArrayList
• CopyOnWriteArraySet
• AtomicInteger, AtomicBoolean,
AtomicReference
• (‘atomic’ gem based on this)
28. juc - Queues
• How can we share data and synchronize
workflow between threads ?
• BlockingQueue - queue and dequeue
operations can trigger blocking depending
on flavor being used
• ArrayBlockingQueue, DelayQueue,
LinkedBlockingQueue, PriorityBlockingQueue,
SynchronousQueue
29. counter = AtomicInteger.new(5000)
executor = Executors.new_fixed_thread_pool(5)
latch = CountDownLatch.new(5)
5.times do
executor.submit do
1000.times do
counter.add_and_get(-1)
end
latch.count_down
end
end
latch.await
puts counter 0 again
executor.shutdown
30. juc
Good to know about. Many of the java
libraries use the java.util.concurrent package
31. Shared Mutable State
We still need to be aware of state that can be
accessed concurrently throughout your
objects
32. What if we can eliminate
locking and not have to deal
with shared state?
33. Actor Model
• Isolate state,
• Don’t allow anyone to access same state
concurrently
• Communicate state via messages
• No longer need explicit locking
34. Formal Definition of Actors
• Originated in1973
• Actor is a unit satisfying:
• Processing
• Storage
• Communication
Resource: http://bit.ly/Hewitt_actor_model
35. Formal Definition of Actors
• When an actor receives message, it can:
• Create actors
• Send messages to actors with address
(sends are asynchronous)
• Designate what to do with next message
• Process one message at a time
Resource: http://bit.ly/Hewitt_actor_model
36. Actor Diagram
Actor Boundaries Actor Boundaries
Thread 1 Thread 2
Messages
Actor Actor
Class Class
State State
Methods Messages Methods
Class Class
State State
Methods Methods
37. Actor Model Libraries (Ruby / JRuby)
• Celluloid (Object based)
• Rubinius actors
• Akka
Tip: Putting aside implementation choice, general concepts can be applied
across libraries
38. We will be using Akka as an example
• http://akka.io/
• Scala / Java API
• Fully featured and highly configurable
• Has been really solid, currently at V2
• Explicit message processing like Erlang
40. Basic Actor Usage with Mikka
# define an actor
class MyActor < Mikka::Actor
def receive(message)
# act on message
# typically based on message type
end
end
41. Basic Actor Usage with Mikka
# Create an actor system to use
# We can have multiple isolated actor systems
actor_system = Mikka.create_actor_system('system')
# create an actor
actor = actor_system.actor_of(Mikka::Props[MyActor],
'OptionalActorName')
# send it a message - Asynchronous
actor << :a_message
actor << AnotherMessage.new(“do some work”)
42. Actor - Addresses in Akka
• An ActorRef is a Proxy for the object
• Can’t get to internal state (more info later)
• Isolates actors from each other
• If actor restarts
• Address stays the same
• Messages are still there
43. Actor - Addresses in Akka
• Optionally name actors for lookup (or pass
them around as references)
• Can lookup / address via tree structure
• Can also put routers behind an address
• RoundRobin / LeastFull
44. ActorRefs
Restart
Actor
ActorRef
Actor
Router Actor
ActorRef
Actor
47. Fault Tolerance
• Erlang Model: Don’t program
defensively, let your actors crash
• Have a supervisor keep track of it -
separates error handling from
business logic
48. Fault Tolerance
• Supervisor watches actor and decides what
to do with failure scenarios:
• Restart it (with fresh state).
• Stop
• Resume
• Escalate failure to its supervisor
49. Supervisors
• In Akka, the Actor that creates children
becomes their supervisor
• Specifies their restart / failure strategy
• Can also “watch” actors to get termination
message
50. Hierarchical Supervision in Akka
Supervisor
A /user/Supervisor
Worker
A A /user/Supervisor/Worker
A A
Child1 Child2
/user/Supervisor/Worker/Child1 /user/Supervisor/Worker/Child2
Relative Lookup:
../Child1
51. Supervisors
• Supervision strategy:
• AllForOne - if a child crashes, take down
all the other children
• OneForOne - if a child crashes, only take
down the specific actor
• Specify duration - let it restart 5 times in
10 minutes, otherwise it terminates
52. Supervision Diagram
5 times in
1 Minute
A 10 times in
20 seconds
* Restart * Restart
* Resume * Resume
* Escalate * Escalate
* Stop * Stop
A A
All For One
One For One
A A A A A A
53. Fault Tolerance - Patterns
• Actor is restarted with initial state on crash
• Error Kernel - identify valuable state
• Keep core simple
• Layer actors around core (Onion Layer)
• For risky actions, use another actor
54. Fault Tolerance - patterns
• Identify failure zones
• Within zones can contain or escalate failure
within zones
• Cascading failure
55. Failure Zones
Zone 3
A
Zone 2
A A
Zone 1
A A A A A
A
56. Actor - pitfalls
• Languages like Erlang enforce certain
guarantees with language level features
• In Ruby this has to be by convention
57. Actor - pitfalls
• Pitfalls to watch when using Ruby:
• Mutating messages
• Leaking underlying actor reference
• Leaking actor state via function closure
• Try not to block in an actor
58. # Pitfalls in Akka (also applies to Celluloid)
class DangerousActor < Mikka::Actor
attr_reader :local_state
def receive(message)
message.balance -= 10 # Mutate message
# leak internal actor ref
# (In Mikka, for actor reference use 'get_self')
response = PlayingWithFireMessage.new(self)
response.on_success = lambda {
@local_state = "bam" # leak state via closure
}
wait_for_something(message) # blocking call
another_actor << response
end
end
59. Actors vs Threads
• Actor != Thread
• Many actors can share a single thread
• Can scale up better than using threads
• Abstract specific configuration and tuning
options from code
60. Actor Model - Intricacies
• Know your message guarantees:
• Once or at most once?
• Message ordering
• Mailbox durability
61. Other Akka Actor Features
• Scheduler - schedule future messages to
actors
• Typed actors - ActiveObjects
• FSM - model actors as FSM
• become - change reactor dynamically
• EventBus - pub/sub across actors
62. Distributed Actors
• Actor model also applies well to
distributed computing
• If all we have is an actor reference, they can
be remote as well as local
• Beware of - “Eight fallacies of distributed
computing”
63. Distributed Akka
• Supports remote actors
• Dynamo style clustering coming in V2.1
• Partitioning / Handoff
• Leader election
• Gossip protocol with Vector clocks
64. Downside to Actors?
• Different programming model & thought process
• Can’t get snapshot of whole system
• State of system = actor state + messages
• Can be harder to test
66. Brief survey of these
• Futures
• Immutable data structures
• STM - software transactional memory
67. Futures
• Object representing a result that is yet to be
computed. (Like an IOU).
• Can end up with result or exception
• Composable futures - multiple operations can be
chained together without blocking
• Great for interacting and composing multiple
services together - non blocking result
Resource: http://bit.ly/composable_futures_akka
68. @system = Mikka.create_actor_system('System')
futures = (0..10).collect do |i|
Mikka::Future.future(@system.dispatcher) do
sleep(rand(10) + 5)
"future #{i}"
end
end
futures = futures.map{ |f| f.map{ |v| " #{v} modified"} }
reduced = Mikka::Future.reduce(futures,
@system.dispatcher) do |r, v|
r+v
end
puts "waiting..."
puts Await.result(reduced, Mikka::Duration["1 minute"])
============================================ Output:
waiting...
future 0 modified future 1 modified......
69. Immutable Data Structures
• Can be safely shared and know that
another thread will not change your copy
• Hamster gem
• https://github.com/harukizaemon/hamster
70. Software Transactional Memory (STM)
• Manage identity via software transaction,
similar to database transactions
• ACI(D)
• Retry functional piece of code if conflict
Resource: http://www.infoq.com/presentations/Value-Identity-State-
Rich-Hickey
71. STM
• Optimistic locking
• If code has side effects they will be
executed multiple times on retry
• Should have pure functional code
Resource: http://www.infoq.com/presentations/Value-Identity-State-
Rich-Hickey
Resource: http://java.ociweb.com/mark/stm/article.html
72. Clojure STM
• Ref - manages a reference
• Ref.deref, Ref.set
• Functional - Ref.alter and Ref.commute
• To change a Ref, have to be inside a
transaction
Resource: http://www.infoq.com/presentations/Value-Identity-State-
Rich-Hickey
Resource: http://java.ociweb.com/mark/stm/article.html
74. john_books = Ref.new(Hamster.set('Lord of the Flies',
'Brave New World'))
larry_books = Ref.new(Hamster.set('The Great Gatsby',
'The Catcher in the Rye'))
jill_books = Ref.new(Hamster.set('Animal Farm'))
def borrow_book(description, lender_list, borrower_list, book)
LockingTransaction.run_in_transaction do
puts "Starting tx #{description}"
raise "Book not available " if !lender_list.deref.include?(book)
# remove book from lender
lender_list.set(lender_list.deref.delete(book))
sleep(1) # add a sleep to mess up tx
# add book to borrower
borrower_list.set(borrower_list.deref.add(book))
end
end
75. Successful Transaction (with retries)
t1 = Thread.new do
borrow_book("John to larry", john_books, larry_books,
'Lord of the Flies')
end
t2 = Thread.new do
borrow_book("John to jill", john_books, jill_books,
'Brave New World')
end
t1.join
t2.join
======================================================================
Output:
Starting tx John to larry
Starting tx John to jill
Starting tx John to jill
Starting tx John to larry
Starting tx John to larry
Starting tx John to larry
John's books: []
Larry's books: ["The Catcher in the Rye", "The Great Gatsby", "Lord of the Flies"]
Jill's books: ["Brave New World", "Animal Farm"]
76. Failed Transaction
t1 = Thread.new do
borrow_book("John to larry", john_books, larry_books,
'Lord of the Flies')
end
t2 = Thread.new do
borrow_book("John to jill", john_books, jill_books,
'Lord of the Flies')
end
t1.join
t2.join
======================================================================
Output:
Starting tx from john to jill
Starting tx from john to larry
Starting tx from john to jill
Starting tx from john to jill
Starting tx from john to jill
Starting tx from john to jill
Starting tx from john to jill
Book not available
John's books: ["Brave New World"]
Larry's books: ["The Catcher in the Rye", "Lord of the Flies", "The Great Gatsby"]
Jill's books: ["Animal Farm"]
77. Phew... We covered:
• Survey of concurrency techniques with the
JVM
• java.util.concurrent
• Actors
• Futures
• STM
78. Combining Techniques
• Can combine these techniques
• Futures can be used standalone or with
actors (actor response can be a future)
• Can use STM or Akka Transactors for
system consensus
• java.util.concurrent for lower level
functionality or Akka extensions
79. Takeaways:
Concurrency is Hard
We need more tools to help
Evaluate and choose the right tool for
the situation
These general strategies can be
applicable across libraries and languages
82. Thank you!
Alex Kira
@AlexKira
alex.kira@gmail.com
github.com/akira
Editor's Notes
\n
actors will be meat of presentation\n
Concurrency is a popular topic lately\n
\n
\n
\n
cr\n
\n
\n
Need to know where to add locks. Use cases might change.\nHard to know what threads will be accessing\n
Need to know where to add locks. Use cases might change.\nHard to know what threads will be accessing\nDifferent dimension\n
IO you have lock ineffient\n
\n
\n
\n
\n
\n
Started out doing Java development\nCorporate\nUnderstandable we don&#x2019;t want to go back\nBeen there done that\n
Reaction is to Java as a language, not the platform\n
\n
mix evented and threaded code\n
\n
many of the libraries use this instead of lower level threads\n
abstract away from thread creation\n
\n
Once you abstract away operations, need a way to join threads\nCountdown latch - allow threads to wait for set of operations to complete\nSet number of operations, thread mark off, and you can wait\nReadWriteLock - separate out readers from writers\n
synchronized in more intelligent manner\n
\n
Top - jruby imports. Can have in a module somewhere\nAtomicInteger - using. Don&#x2019;t have to synchronize\nLatch - once you abstract away threads, don&#x2019;t have thread.join\n
\n
\n
\n
* popularized by erlang\n* adopted by other frameworks such as Celluloid / Akka etc.\n* Original model was from 1970s\n
Thanks to Jesse\n
\n
State encapsulated in these different silos which are actors\nCommunicate asynchronously via their mailboxes\nDoesn&#x2019;t have to be class oriented\nSimplified diagram\n
* A lot of the concepts taken from Erlang and can apply throughout\n* Celluloid is object based - uses fibers to suspend and enable actors\n* Akka and rubinius - message processing based - reactor loop\n
\n
\n
Message processing \n - create other actors \n - send messages to other actors\n - change state\n
- asynch\n
\n
\n
Isolates actor from specific instance. \nCan restart and fail and messages are still there and address is the same\n
\n
Issues with raw threads - exception handling\n
Issues with raw threads - exception handling\n
\n
\n
\n
\n
\n
patterns that I like - \n
Another pattern I like - concept of failure zones\nIndependent failure and recovery\n
\n
\n
Once you send a message, don&#x2019;t change it\nOnly give up actor reference never your internal class\nDon&#x2019;t send behavior\n\n
Celluloid also has an ActorRef method\nAvoid these when language doesn&#x2019;t enforce this\n
\n
\n
Typed actors - similar to celluloid\n
The network is reliable.\nLatency is zero.\nBandwidth is infinite.\nThe network is secure.\nTopology doesn't change.\nThere is one administrator.\nTransport cost is zero.\nThe network is homogeneous.\nprefer more explicit style (erlang) vs object style\n
\n
\n
\n
\n
\n
Note- this is patched code for Mikka. Can be nicer.\nNice thing is there is no blocking! \nCheck if a future is ready\nget first completed\n
every time you do an operation it creates a brand new object\n
Atomic\nConsistency\nIsolatation\n
\n
\n
Reaction is to Java as a language, not the platform\n
Had to use RC1\nNo side effects - you can&#x2019;t use Array without clone\n
\n
\n
advantages and disadvantages - try them out and see\n