Ruby File.open 'w' Acces concurrent 2

Publié par Yannick Francois Mar 09 juin 2009 21:02:00 GMT

Je viens de tomber sur quelque chose d’étrange dans l’utilisation de l’objet IO de Ruby : La gestion des verrous sur les fichiers. Alors j’ai surement du rater quelque chose, mais voici mon histoire…

require 'test/unit'

DUCK="duck"
ELEPHANT="elephant"
SNAKE="snake"
FILE="test_file"

class TestFileConcurrencyAccess < Test::Unit::TestCase
  def test_concurrency_access
    t1 = Thread.start do
      File.open(FILE, "w") do |file|
        file.puts SNAKE
        file.flush
        sleep 8
        file.puts DUCK
      end
    end
    File.open(FILE,"r") do |f1|
      assert SNAKE, f1.read
    end
    t2 = Thread.start do
      File.open(FILE,"w") do |f|
        sleep 2
        f.puts ELEPHANT
      end
    end
    t2.join
    File.open(FILE,"r") do |f2|
      assert ELEPHANT, f2.read
    end

    t1.join
    File.open(FILE,"r") do |f3|
      assert SNAKE, f3.readline
      assert DUCK, f3.readline
    end

  end
end

fichier source

Le resultat d’execution de ce test donne une erreur sur la dernière lecture:


Loaded suite test_file_concurrency_access
Started
E
Finished in 8.003509 seconds.

1) Error:

test_concurrency_access(TestFileConcurrencyAccess):
EOFError: end of file reached
test_file_concurrency_access.rb:38:in `readline’
test_file_concurrency_access.rb:38:in `test_concurrency_access’
test_file_concurrency_access.rb:36:in `open’
test_file_concurrency_access.rb:36:in `test_concurrency_access’

1 tests, 3 assertions, 0 failures, 1 errors

Mais ce qui me gène (et qui ne ressort pas dans mon test, je l’avoue) c’est que j’aurais pensé que le deuxième File.open ne puisse pas s’executé, et me renvoie une Error du type: “Le fichier est déjà ouvert en écriture par quelqu’un d’autres”… Mais non, et du coup, j’ai un fichier assez moche qui ne ressemble à rien:

elephduck

Alors qu’on pourrait penser obtenir un truc dans le genre:

snake
duck

Avec éventuellement une erreur sur l’écriture d’éléphant dans le fichier.

J’ai également fait un essai en utilisant le code contenu dans chaque thread pour les executer dans deux console différentes, j’arrive au même résultat (c’est du coup dans test/unit, mais au niveau du contenu de FILE à la fin, j’ai là même chose…)
Fichier write_quickly.rb pour l’écriture d’ELEPHANT
Fichier write_slowly.rb pour l’écriture de SNAKE et DUCK

Bref, je suis perplexe. J’avoue ne pas avoir envie d’envoyer cela sur la mailing-liste Ruby-talk, ni sur celle de JRuby d’ailleurs (car j’ai vérifier, j’ai le même problème en JRuby). Du moins tant que je n’ai pas creusé un peu plus.

J’ai sûrement du faire une erreur quelque part, oublier quelque chose, ou faire quelque chose de travers. Si quelqu’un lit cela et à une idée, merci d’en m’en parler, je suis preneur !.

Si c’est un comportement souhaité, j’aimerais comprendre pourquoi, et si c’est un oubli ou une erreur, j’aimerais bien aider à trouver une solution.

ps: Retour au thème Scribbish que je trouve plus lisible, en attendant que je refasse quelque chose…

  1. Avatar
    Bruno Michel about 1 hour plus tard:

    Pour se protéger de ce genre de choses quand on a plusieurs processus, on peut utiliser File::flock, mais pour plusieurs threads, cela ne fonctionnera pas.

  2. Avatar
    Yoko 2 months plus tard:

    Je ne connais pas plus que ça Ruby, mais je pense qu’il se met le plus proche possible du système, c’est à dire un fichier peut être ouvert en lecture deux fois.

    De ce que je vois vite fais soit tu devrais faire un flush après écriture (mais je suis pas sur), soit tu crée une classe qui va sérialiser tes écriture. Tes thread passent par cette classe pour écrire. Cette classe peut très bien exister en standard en Ruby.