Tuesday, August 28, 2007

HungryMachine goes Live!


Eddie, Aaron, Val, and Tim have started a new business to help people and businesses develop web applications using ruby on rails. They've already done some great work on the facebook platform with their bookshelf application and their very popular MardiGras receiving over a million users in the last few weeks.



I expect nothing but great things to follow. They are some of the most dedicated and hard workers I've ever worked with. You are sure to see some interesting posts on their blog and you can subscribe to their blog here.

Tuesday, August 07, 2007

Ruby and instance_eval

Tonight I learned a new trick in ruby.
Lets say you have a class that you would like to load with different behavior depending on the environment it's created in. Object inheritance can be used, but sometimes you want something a little more dynamic. Tonight I was trying to allow mongrel esi to work in a multithreaded environment and a single threaded environment to support viewing a new cache status page. I wanted to be able to share most of the code and avoid as much runtime conditional logic as possible. I also wanted to avoid having to duplicate a lot of function signatures over and over again, (get, get_unlocked, set, set_unlocked, etc...).


My first solution



# Wrapper around ruby Mutex to make it possible to enable or disable the Mutex object
# depending on whether or not ESI::Invalidator server is run, usually it runs on port 4000

class CacheLock
def initialize( options = {} )

if options[:locked]
@semaphore = Mutex.new

self.instance_eval do
def synchronize
@semaphore.synchronize {

yield
}
end
end
else
self.instance_eval do

def synchronize
yield
end
end
end
end

end

class Cache
def initialize( options = {} )

@cache = {}
@semaphore = CacheLock.new( options )

end
def cached?( uri, params )
@semaphore.synchronize {

fragment = @cache[cache_key(uri,params)]
fragment and fragment.valid?

}
end
def get( uri, params )

@semaphore.synchronize {
@cache[cache_key(uri,params)]

}
end
def put( uri, params, max_age, body )

@semaphore.synchronize {
@cache[cache_key(uri,params)] = Fragment.new(max_age,body)

}
sweep
end
# run through the cache and dump anything that has expired
def sweep
@semaphore.synchronize {

@cache.reject! {|k,v| !v.valid? }

}
end

def keys(&block)
@semaphore.synchronize {

@cache.each do|key,data|
yield key, data

end
}
end
def update_ttl( key, ttl )

@semaphore.synchronize {
update_ttl_unlocked( key, ttl )

}
end

def update_ttl_unlocked( key, ttl )

@cache[key] = Fragment.new( ttl, @cache[key].body )

end

private
def cache_key( uri, params )

http_x_requested_with = params['HTTP_X_REQUESTED_WITH'] || params["X-Requested-With"]

key = Digest::SHA1.hexdigest("#{uri}-#{http_x_requested_with}")
"#{uri}:%:#{key}"

end
end


A better solution



Okay, this works and it removed the burden of having conditional logic, but I still have the blocks all over my code and the repeated symaphore.synchronize in all my methods. Not to meantion I needed to define a few pesky method_name_unlocked for cases where I need to call a method instead of another and the method is already locked... I decided I should be able to redefine a select set of methods to include semaphore block when needed otherwise just alias_method to define the #{method}_unlocked versions of the methods. Talking through my issues on irc and at long last heres my solution:


# A hash table indexed by cache_key of Fragments.
# the cache is made thread safe if the external invalidator is active otherwise the Mutex is a no op
class Cache

def synchronize_methods( *methods_names )
methods_names.flatten.each do|name|

unlocked_name = "#{name}_unlocked".to_sym
locked_name = "#{name}_locked".to_sym

instance_eval do
def locked_name( *args )
@semaphore.synchronize {

send unlocked_name, args
}
end
end

end
end

def initialize( options = {} )

@cache = {}
synchronized_methods = [:get, :put, :put, :sweep, :keys, :update_ttl]

# define all the unlocked methods
synchronized_methods.each { |name| ESI::Cache.send :alias_method, "#{name}_unlocked", name }

if( options[:locked] )
@semaphore = Mutex.new

synchronize_methods synchronized_methods
end
end
def cached?( uri, params )

fragment = @cache[cache_key(uri,params)]
fragment and fragment.valid?

end
def get( uri, params )
@cache[cache_key(uri,params)]

end
def put( uri, params, max_age, body )

@cache[cache_key(uri,params)] = Fragment.new(max_age,body)

sweep_unlocked
end
# run through the cache and dump anything that has expired
def sweep
@cache.reject! {|k,v| !v.valid? }

end
def keys(&block)
@cache.each do|key,data|

yield key, data
end
end
def update_ttl( key, ttl )

@cache[key] = Fragment.new( ttl, @cache[key].body )

end

private
def cache_key( uri, params )

http_x_requested_with = params['HTTP_X_REQUESTED_WITH'] || params["X-Requested-With"]

key = Digest::SHA1.hexdigest("#{uri}-#{http_x_requested_with}")
"#{uri}:%:#{key}"

end

end

laser flashlight






Laser Flashlight Hack!
- The funniest videos clips are here



And for only $9.99 you too can own a laser flashlight!

Monday, August 06, 2007

ESI Helpers for Rails

Aaron put together an awesome post at revolution on rails blog about a nice ESI plugin he wrote for rails. Hoping to spend a little time this week to finish up some work on getting mongrel-esi backed by memcached.

Reading list