This will be a short description of Jonathan Weiss (@jweiss, https://github.com/jweiss) lecture about EventMaching given on the RuPy conf 2011. Author of the speech is working with Ruby and scalling in Peritor GmbH in Germany.
EventMachine is an event-processing library for doing evented I/O- we can do multiple operations almost simultaneously without the need for threads.
Jonathan claimed that EventMaching best works with many connections, many messages, many request but all of operations should be very short and fast. Complex computations and low level stuff shouldn’t be done in EM.
EventMachine allows you to register callbacks like keyboard events or file watchers.
Sequential style code:
require 'net/http' Net::HTTP.get_print 'www.example.com, '/index.html'
Evented style code:
http == EventMachine::HttpRequest.new('http://www.example.com').get http.errback { puts 'error!' } http.callback { puts http.response }
Use Case
Let’s imagine that we are managing many log files – processing and uploading thousands of them at the same time to Amazon S3. With the sequential HTTP API we would have do it one be one for each log file. So the total time of operation will be the sum of each individial upload times.
Time to upload 3 files to S3 = time of upload1 + time of upload2 + time of upload3
Jonathan said that this can be done with threading, but there’s great chance to break something up. A much better solution would be to use the EventMachine. EM will send log files as soon as they appear, without waiting for response from S3 that file was uploaded. So the total upload time would be the max time of each individual upload times.:
Time to upload 3 files to S3 = MAX(time of upload1, time of upload2, time of upload3).
The Event Loop
The Event Loop is the core of evented programming.
Em.run do ... end
Don’t block the Event :Loop
One operation can block whole loop. It will keep other code from running. For example:
Em.run do... Net::HTTP.get_response(uri) end
Blocking the event loop can happen through I/O handling. For example, the AMQP gem reads all messages on the socket. If you have many messages you are slowing down the main loop. We shouldn’t do anything that will block or take more than a few milliseconds.
Reactor Thread
Jonathan made a helpful list of things that we should obey to write good evented code.
The main loop:
- should be as fast as possible
- handle all I/O
- small operations
- avoid non-evented libraries
- callbacks run until finished, so split workload over multiple iterations if needed
EM.next_tick()
Dangerous or time consuming operations can be handled with EM.next_tick. This means that work can be divided into steps which will be run across next cycles..
work = proc do do_something() EM.next_tick &work end EM.next_tick &work
EM.defer
Run block in a background thread. This is a good solution if we are performing complex, CPU consuming computations and do not want to block the main reactor loop. There are 20 background threads be default.
Jonatham mentioned that EM.next_tick should be used for fast operations, in the main thread, I/O handling and the EM.defer for long running, cpu intensive, probably blocking.
Deferrables
A state machine with callbacks for building evented libraries. There are three states: nil, succeed, failed. Callbacks and errbacks can be added at any time.
- Callback – executed when transitioned to :succeed or already :succeed
- errback – executed when transitioned to :failed or already :failed
EM::Queue
Store data in a specific order. Safely add data to a queue from several threads. When data is retrieved from a queue, it is being executed on the main thread.