domingo, 28 de diciembre de 2008

Crear demonios en ruby

Para crear un demonio hay que duplicar el proceso actual (hacer un fork) y distinguir a cada una de las instancias para la que instancia hija se encargue del trabajo en background que queremos realizar.

En ruby todo este proceso se puede realizar automáticamente utilizando el módulo ruby daemon de la siguiente forma.

require 'daemons'

class Demonio
def initialize
Daemons.daemonize
loop do
f = File.new('/tmp/timestamp', 'a')
f.write("#{Time.now}\n")
f.close
sleep(3)
end
end
end

Demonio.new

viernes, 26 de diciembre de 2008

Redefinir el método de comparación con expresiones regulares

Para mejorar la búsqueda en los datos de los objetos se puede aprovechar que en ruby se puede redefinir el método de comparación con expresiones regulares (=~).

Un ejemplo.

class Persona
attr_accessor :nombre, :apellidos, :direccion

def initialize(nombre, apellidos, direccion)
self.nombre = nombre
self.apellidos = apellidos
self.direccion = direccion
end

def =~(re)
"#{self.nombre} #{self.apellidos} #{self.direccion}" =~ re
end

def self.search(ciudad, input)
query = Regexp.new(input, 'i')
ciudad.select{|p| p =~ query}
end
end



p1 = Persona.new('Jose', 'Gonzalez', 'Sol')
p2 = Persona.new('Francisco', 'Martinez', 'Preciados')
p3 = Persona.new('Fernando', 'Solano', 'Serrano')

Madrid = [p1, p2, p3]

puts Persona.search(Madrid, 'sol').map(&:nombre)

El modificador 'i' del constructor de expresiones regulares hace que no distinga entre mayúsculas y minúsculas.
Concatenando los campos en los que queremos buscar se obtiene un matching más completo.

Con el map final se imprimen los nombres de las personas que hayan coincidido con la búsqueda.

domingo, 14 de diciembre de 2008

Recuperación automática de procesos erlang

La mayor ventaja que proporciona erlang es la gran potencia que tiene para la programación de procesos concurrentes.

Cuando se diseña software que va a ser programado en erlang suele dividirse el trabajo en múltiples procesos, además provee de mecanismos para comprobar fácilmente entre procesos si alguno se ha colgado y restaurarlo o ejecutar cualquier otra acción.

En el siguiente ejemplo.

-module(lib_misc).
-export([link_p/1, start/0, loop/0]).

start() ->
Pid = spawn(fun loop/0),
register(divisor, Pid),
spawn(lib_misc, link_p, [Pid]),
Pid.

loop() ->
receive
{A, B} ->
io:format("~nDivision: ~p", [A/B]),
loop()
end.


link_p(Pid) ->
process_flag(trap_exit, true),
link(Pid),
receive
{'EXIT', Pid, _} ->
NewPid = spawn_link(lib_misc, loop, []),
register(divisor, NewPid),
link_p(NewPid)
end.

Al compilar la librería y arrancarla se ejecutan dos procesos, el principal loop y otro llamado link_p.

El bucle principal simplemente recibe una tupla con dos números y los divide. Tiene al menos un error conocido y es que no comprueba cuando el divisor es cero.

El proceso link_p se enlaza al principal, la tarea de este proceso es esperar a que el principal se cuelgue (por ejemplo, división entre cero).
En el momento en el que el programa principal se ha colgado, recibe el mensaje de salida, restaura el proceso y se enlaza al nuevo.

Hay un detalle más, al iniciar un nuevo proceso el pid del antiguo no nos sirve para mandarle mensajes, así que se registra el átomo divisor para tener siempre una referencia global.

Registrar un nombre global estático no es la mejor solución, ya que no se pueden iniciar múltiples instancias del proceso divisor, debería ser un parámetro, lo he dejado así por que el ejemplo fuera más sencillo.

lunes, 8 de diciembre de 2008

Funciones anónimas recursivas en Erlang

En erlang se pueden definir funciones anónimas con la palabra reservada fun, aunque se pueden guardar en una variable para referenciarlas posteriormente.

La sintaxis es la siguiente:

IsZero = fun(0) -> 1; (N) -> 0 end.

Esta función devuelve 1 (verdad) si el parámetro es 0, falso en cualquier otro caso.

El problema es que de esta forma no se pueden definir funciones recursivas, ya que al no estar definida la función dentro de su definición, no podemos referenciarla, para solucionarlo se puede pasar un parámetro extra, que sea la propia función.

Factorial = fun(1, F) -> 1; (N, F) -> N * F(N-1, F) end.

En este último caso habría que llamar a la función como Factorial(número, Factorial), lo que queda poco intuitivo, se puede mejorar creando dos funciones en lugar de una.

Fact = fun(1, F) -> 1; (N, F) -> N * F(N-1, F) end.
Factorial = fun(N) -> Fact(N, Fact) end.

domingo, 30 de noviembre de 2008

Terminales virtuales en linux

Regularmente cuando administramos equipos unix necesitamos varios terminales, por ejemplo, en el caso de estar administrando un equipo linux remoto por ssh.

Para poder tener varios terminales podemos loguearnos varias veces y usar cada conexión como un terminal, sin embargo esto está lejos de ser óptimo, una sola conexión es más que suficiente para tener abiertas varios terminales.

El comando screen de linux permite crear terminales virtuales, por lo que este problema lo tendríamos resuelto, su funcionamiento es muy simple.

Ejecutando:

sreen bash, abrimos una instancia nueva de bash en otro terminal.

Ctrl+A ", muestra una lista de los terminales virtuales.

Ctr+A 2, va a la tercera consola virtual (numeradas desde 0).

Ctrl+A Ctrl+A, va a la consola mostrada anteriormente.

Ctrl+A d, deja ejecutando las tareas en background (detach).

Expiration time en fragment caching

Cuando se usa el sistema de caché de rails se pueden elegir donde guardar los items cacheados, la mejor opción es la mayoría de los casos es memcached, pero no siempre tenemos posibilidad de instalar un demonio en la máquina que da hosting.

Otra posibilidad es guardar los items en el sistema de ficheros, para ello hay que añadir en el environment.rb lo siguiente.

config.cache_store = :file_store, '/path_to_cache'

El problema que tiene usar el sistema de ficheros como soporte para la cache es que no soporta la opción de expiración por tiempo, sin embargo memcached sí que lo hace.

Se puede solucionar añadiendo el siguiente parche al environment.rb

module ActiveSupport
module Cache
class FileStore < Store
def read(name, options = nil)
super
timestamp = File.open("#{real_file_path(name)}.timestamp", 'rb') { |f| f.read } rescue nil
if !timestamp or (timestamp and timestamp.to_i > Time.now.to_i)
File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
else
nil
end
end

def write(name, value, options = nil)
super
ensure_cache_path(File.dirname(real_file_path(name)))
if options and options[:expires_in]
File.open("#{real_file_path(name)}.timestamp", "wb+") { |f| f.write(Time.now.to_i + options[:expires_in].to_i) }
end
File.open(real_file_path(name), "wb+") { |f| f.write(value) }
rescue => e
RAILS_DEFAULT_LOGGER.error "Couldn't create cache directory: #{name} (#{e.message})" if RAILS_DEFAULT_LOGGER
end

def delete(name, options = nil)
super
File.delete("#{real_file_path(name)}.timestamp") rescue nil
File.delete(real_file_path(name)) rescue nil
end
end
end
end

Se puede utilizar al igual que la opción expires_in en memcached.

<% cache 'something', :expires_in => 5.minutes do %>
Hello, just saying hello from cache
<% end %>

Lo he desarrollado teniendo en cuenta la implementación de rails 2.1.2, y solo soporta fragment caching.

miércoles, 26 de noviembre de 2008

Fragment caching en rails

Usando memcached y fragment caching se puede acelerar mucho la reconstrucción de las vistas en rails.

Para ello, una vez instalado memcached lo ejecutamos:

memcached -d

Por defecto estará en el puerto 11211.

En el environment.rb hay que configurar la caché para que utilice memcached.


config.cache_store = :mem_cache_store


Tras eso en las vistas podemos hacer lo siguiente:


<% cache 'key', :expires_in => 300 do %>
texto html...
<% end %>


300 es el número de segundos que este trozo estará vigente en caché, si no se indica nada el fragmento no expirará a no ser que lo indiquemos manualmente con expire_fragment.

key es la clave que tiene el fragmento dentro de la vista, es necesario para distinguir los distintos fragmentos.