PeriodicTimer de EventMachine, le cron simplifié

EventMachine est une des meilleures librairies Ruby, tout en étant une des plus sous-estimées et mal connues.

J'ai donc commencé à regarder un peu dans quels cas utiliser EventMachine. La première application que j'en ai trouvé c'est tout simplement le PeriodicTimer.

Son utilisation la plus simple est le remplacement d'une cronjob.

Je m'explique : une cronjob est vraiment une très bonne chose. On peut définir très précisément l'heure à laquelle on souhaite qu'une tâche s'exécute. Celle-ci s'exécute alors à ce moment précis. Cron est vraiment un outil magnifique. Cependant, dans le cas d'une tâche récurrente qui doit être effectuée régulièrement, l'utilisation d'un cronjob peut avoir un gros inconvénient.

Cron n'a aucune gestion de queue. Ainsi on peut se retrouver très simplement avec une quantité astronomique de tâches essayant de s'exécuter en même temps. Votre « load » explose et il faut redémarrer votre serveur. Bien sûr cela n'arrive que si vous lancez une tâche plus fréquemment que son temps d'exécution.

La seule solution serait d'avoir une petite application - comme : jobq - qui gére pour vous un système de queue. Bien sûr, si le fait d'avoir une queue ne sert strictement à rien, cette solution peut être un peu overkill.

L'avantage de PeriodicTimer est que les tâches sont exécutées après un temps défini. Il n'y a aucune gestion d'heure de lancement. Il y a juste un lancement de la tâche après X secondes une fois que la précédente tâche est finie et qu'aucune autre tâche ne soit en cours d'exécution. Cela entraîne effectivement un glissement progressif de l'heure de lancement. Mais globalement, ça ne pose aucun problème. L'important est que la tâche soit exécutée régulièrement. C'est le cas, par exemple dans le cas de récupération de statistique.

Voici un exemple de code qui utilise PeriodicTimer

require 'eventmachine'
require 'timeout'
EventMachine.run {
 EventMachine::PeriodicTimer.new(10) do
   puts "#{Time.now} : I am 10"
   sleep 10
 end

 EventMachine::PeriodicTimer.new(1) do
   puts Time.now
 end
}

Voici la sortie qui est effectuée si on lance l'application pendant 50s.

$ ruby em_periodic.rb
Fri Mar 26 16:07:39 +0100 2010
Fri Mar 26 16:07:40 +0100 2010
Fri Mar 26 16:07:41 +0100 2010
Fri Mar 26 16:07:42 +0100 2010
Fri Mar 26 16:07:43 +0100 2010
Fri Mar 26 16:07:44 +0100 2010
Fri Mar 26 16:07:45 +0100 2010
Fri Mar 26 16:07:46 +0100 2010
Fri Mar 26 16:07:47 +0100 2010
Fri Mar 26 16:07:48 +0100 2010 : I am 10
Fri Mar 26 16:07:58 +0100 2010
Fri Mar 26 16:07:59 +0100 2010
Fri Mar 26 16:08:00 +0100 2010
Fri Mar 26 16:08:01 +0100 2010
Fri Mar 26 16:08:02 +0100 2010
Fri Mar 26 16:08:03 +0100 2010
Fri Mar 26 16:08:04 +0100 2010
Fri Mar 26 16:08:05 +0100 2010
Fri Mar 26 16:08:06 +0100 2010
Fri Mar 26 16:08:08 +0100 2010
Fri Mar 26 16:08:08 +0100 2010 : I am 10
Fri Mar 26 16:08:18 +0100 2010
Fri Mar 26 16:08:19 +0100 2010
Fri Mar 26 16:08:20 +0100 2010
Fri Mar 26 16:08:21 +0100 2010
Fri Mar 26 16:08:22 +0100 2010
Fri Mar 26 16:08:23 +0100 2010
Fri Mar 26 16:08:25 +0100 2010
Fri Mar 26 16:08:26 +0100 2010
Fri Mar 26 16:08:27 +0100 2010
Fri Mar 26 16:08:28 +0100 2010
Fri Mar 26 16:08:28 +0100 2010 : I am 10

On peut constater que la tâche qui doit s'exécuter toutes les secondes le fait bien. Par contre la tâche qui s'exécute au bout de 10 secondes effectue un lock sur le thread. Ainsi la tâche qui s'exécute toutes les secondes attend que le lock soit libéré. Une fois terminée, l'exécution de la tâche toutes les secondes recommence. La tache qui doit s'exécuter toutes les 10 secondes attendra, quand à elle, 10 nouvelles secondes après avoir été terminée.

C'est là une grande puissance de PeriodicTimer. On a facilement une succession de tâche qui ne se marchent jamais sur les pieds. Désormais, je n'utilise plus que

cette solution pour créer des scripts de récupération de données.

Published on Ven 26 mars 2010 23:10
0 commentaires

Oupsnow 0.5.0 est sortie

Ca y est, j'ai presque pris un cycle de release pas trop mauvais. Ainsi après seulement un mois après la version 0.4.1 de Oupsnow, voici la version 0.5.0. Cette version apporte quelque feature, mais marque surtout un moment de stabilité dans le code.

Les nouveautés

  • Ajout d'un filtre sur la recherche des tickets pour ne voir que les tickets fermé ou non
  • Possibilité d'éditer une milestone pour les admin d'un projet
  • Possibilité de définir une milestone comme actuelle. Par défaut, c'est la première milestone créée
  • Possibilité de récupérer son password par email
  • Possibilité de rester connecter avec un remember_me
  • Ajout d'information concernant le nombre de tickets filtrés ou vues
  • Possibilité pour chaque utilisateur loggé de suivre un ticket. Un utilisateur qui suit un ticket recevra ainsi à chaque mise à jour de ce ticket un email concernant cette modification.
  • Les utilisateurs ne peuvent plus changer leur email.

Enfin comme d'habitude voici mon fichier capistrano de déployement pour faciliter celui-ci.

Ficher de déployement capistrano

set :application, "oupsnow"
set :repository,  "git://github.com/shingara/oupsnow.git"
set :domain, "dev.shingara.fr"

# If you aren't deploying to /u/apps/#{application} on the target
# servers (which is the default), you can specify the actual location
# via the :deploy_to variable:
set :deploy_to, "XXXXXXXXX"

# If you aren't using Subversion to manage your source code, specify
# your SCM below:
# set :scm, :subversion
set :scm, :git
set :git_enable_submodules, 1

set :runner, "xxxx"
set :user, "xxxx"
set :use_sudo, false

set :thin_conf, "/etc/thin/#{domain}.yml"

set :rails_env, "production"                                                                                                                                   

role :app, domain
role :web, domain
role :db,  domain, :primary => true

task :update_config, :roles => [:app] do
  run "ln -s #{shared_path}/config/database.yml #{release_path}/config/database.yml"
  run "ln -s #{shared_path}/config/email.yml #{release_path}/config/email.yml"
  run "ln -s #{shared_path}/config/initializers/errornot.rb #{release_path}/config/initializers/errornot.rb"
  run "cd #{release_path} && echo 'GOOGLE_ANALYTICS=\"XXXXXXXX\"' >> config/environment.rb"
end

namespace :deploy do
  task :start, :roles => [:app] do
    run "thin -C #{thin_conf} start"
  end 

  task :stop, :roles => [:app] do
    run "thin -C #{thin_conf} stop"
  end 

  task :restart, :roles => [:app] do
    run "thin -C #{thin_conf} restart"
  end 
end

task :update_db do
  run "cd #{current_path} && RAILS_ENV=#{rails_env} rake db:update"
end

after "deploy:update_code", :update_config
after "deploy:symlink", :update_db

English translation

Published on Lun 15 mars 2010 21:26
0 commentaires

RSS Follow me on Twitter