views:

122

answers:

5

My question is pretty simple: how is event-driven programming actually achieved?

To elaborate: I have a Rails application, every time a user makes a change on the website, the model writes that "change" to a text file (as JSON.)

What I'd like to do is hook an IRC bot to that "event." (the creation/modification of the text file.)

How is this done in general? It seems like it'd basically be an infinite loop. In pseudocode:

while (I'm Listening)
do
  if (output.txt Is changed)
    process("output.txt")

If this is how event-driven programming is achieved - how does it avoid locking up the CPU? As infinite loops have a tendency to do?

Edit- The IRC server/bot are hosted on a locally maintained box. The Rails application is hosted on a shared server. As of now, the only way I know for my IRC bot to communicate with the Rails app is via an HTTP request to the server (or something similar.) As I stated though, this question is really more general, as I'd like to garner a knowledge of event-driven programming in general.

I apologize if this question is impossibly simple, but my understanding of event driven programming consists of attaching pre-made event handlers to objects with jQuery; which really doesn't help when attaching an IRC bot [written in Ruby] to file I/O.

Thanks, Robbie

+4  A: 

More often, its like this:

while (I'm Waiting to be notified) 
do 
  if (output.txt Is changed) 
    process("output.txt") 

There are ways in the OS (any OS) to wait to be notified without taking CPU time (such as a condition variable.) No event driven loop is sitting there spinning waiting for an event.

Starkey
Agreed. There's no need for an infinitely spinning loop. The monitor should be a simple blocking call that waits for a change.
Dave Sims
+1  A: 

In general the sort of while/listener loop you're describing is created in a thread, for instance a UDP or TCP server waiting for a connection. But that doesn't really sound applicable to your concern. Rails and ActiveRecord define a number of built-in Observer-pattern style hooks on ActiveRecord models that may be of use to you. See here: http://api.rubyonrails.org/classes/ActiveRecord/Observer.html

If you really need to monitor the state of an external file and ActiveRecord or ActiveRecord-style callbacks don't work for you, you might monitor the file with a simplistic Ruby daemon process that manages the blocking while loop. But I'm still not sure why you wouldn't simply use the Rails code that modifies the file to also notify your IRC bot/process etc.

Dave Sims
In this specific instance, the IRC bot is not hosted on the same server.
Robbie
@Robbie — does that mean you're waiting on HTTP requests, anyway?
Matchu
Still, why not notify the bot serially with the same call that modifies the json file? Why does the monitor have to be async?
Dave Sims
Yup, in this particular case the IRC bot can only talk to the server via HTTP. This would only change (if/when) the bot/rails app are hosted on the same VPS.
Robbie
Dave: I'm not sure how I would notify the bot (an Autumn IRC bot, written in Ruby) as it doesn't listen on an external port in any way? (Aside from listening to the IRC channel of course)
Robbie
+1  A: 

A basic model that I am familiar with is done by creating a class which exposes events which can be subscribed to. This can be as simple as a collection of methods.

class Foo
  attr_accessor :event  #obviously not the right way to do it, but it will suffice

  def initialize
    @event = []
  end

  private
  def fire_event
    @event.each { |sub| sub.call }
  end
end

Now clients of this class can pass in a method that they wish to be executed when the event is fired.

f = Foo.new
f.event << lambda { puts 'event was fired' }

When the Foo class calls the fire_event method each procedure in the collection of procedures will be executed. There is no loop constantly checking some condition. When the condition arises, the firing method is called and the procedures are executed.

On a side note the example above is just that; an example. A real world event architecture would be more robust, but the will be similar. This may not even apply to your actual situation, but I am not a web guy and I don't know RoR and JSON and all those other fancy keywords, so I went high level. Hopefully it helps.

Ed Swangren
+2  A: 

I'm not sure of the black magic behind it, but the popular Ruby gem EventMachine has a watch_file method that seems to handle your exact use case. No need to do all the heavy lifting yourself when someone else has already done it for you.

Share and enjoy.

EDIT: Your comment on Dave Sims's answer seems to imply that your Rails app and IRC bot live on different filesystems, and that the JSON file is served over HTTP. If that's the case, you're waiting on HTTP requests, anyway, and so sending an HTTP request as often as possible (though I'd recommend taking some small pauses to avoid upsetting your server) shouldn't block out the CPU, since it will pause while waiting for a response from the server. If the bot script needs to be performing other actions in the meantime, basing it on EventMachine and using the em-http gem to send event-based HTTP requests should take care of that.

I'm still not totally clear on exactly what you're doing, though.

Matchu
Nice find. I'd avoid running this in a thread off of the Rails process. Maybe look into a separate super-simple Ruby process in daemon mode. http://daemons.rubyforge.org/
Dave Sims
I updated the question to clarify my setup. Basically my users can create "rolls" (think dice rolls) and the IRC bot then echo's each new roll to the channel. It's simply a way to interface my Rails app with the "players" (who are sitting in an IRC channel.)
Robbie
So in essence adding some form of timer, (or in this instance waiting on HTTP), will allow the OS to work on other tasks?So while it is an "infinite loop" in the sense that it "never" dies, it won't eat up the CPU because it's not actively doing something on *every single cycle* it can get.
Robbie
+1  A: 

Maybe you should create a DRb server or use a solution which uses this, like delayed_job. You can run the workers on different machines. The connection is made by connecting to a special port on your server. All you need to do is open this port on your firewall for the dedicated machine running the IRC bot. You can put ruby objects on the queue, so every change to the file can be mirrored by putting a ruby object on the queue which also contains this change. The worker will grab this a few seconds later, it will not be instant. But it's still pretty fast, uses low CPU and it is able to handle network outages.

hurikhan77
Awesome, it seems I could also share the Ruby class itself -- instead of messing around with a JSON interpretation of it. Thanks for the answer :).
Robbie
I've marked yours as an accepted answer because I'm now using a distributed ruby implementation, and it's working out great!
Robbie