Tengo (tenía) un código de Ruby que, por razones históricas, es (era) esencialmente
Dir.mktmpdir do |dir| path_list = something_which_creates_files_in(dir) path_list.each(&:delete) endOcasionalmente recibo (obtuve) excepciones de este código:
Errno::ENOENT: No such file or directory @ dir_s_rmdir - /tmp/d2..4w/file.csv : /path/to/source.rb:124:in `unlink' /path/to/source.rb:124:in `delete' /path/to/source.rb:124:in `each' : /usr/lib/ruby/2.5.0/tmpdir.rb:93:in `mktmpdir' : entonces me parece que mi "limpieza" de la lista de rutas al final del bloque no es completamente síncrona, que (algunos de) los archivos aún existen después de que se completa, luego mktmpdir elimina el directorio temporal para que el asíncrono (?) falla el unlink , su objetivo se ha ido. ¿Es esta una interpretación razonable?
Esta es más una pregunta académica que cualquier otra cosa; el comportamiento me desconcierta. Simplemente eliminando la limpieza ( path_list.each(&:delete) ) y dejando la eliminación a Dir.mktmpdir parece detener estas excepciones.
Si hace la diferencia, este es Ruby 2.5 (MRI) ejecutándose en Linux.
Su suposición parece ser correcta.
Si verificara la fuente de File.unlink , podría ver lo siguiente:
static VALUE rb_file_s_unlink(int argc, VALUE *argv, VALUE klass) { return apply2files(unlink_internal, argc, argv, 0); } Aquí unlink_internal es algo trivial (¿solo un envoltorio delgado alrededor de una llamada al sistema?), Pero lo que es interesante es la implementación de apply2files . Allí se podía ver la siguiente convocatoria:
... rb_thread_call_without_gvl(no_gvl_apply2files, aa, RUBY_UBF_IO, 0); ... donde aa es una estructura elegante que contiene, entre otras cosas, un puntero a lo que queremos aplicar; en nuestro caso, unlink .
El nombre de esta función es bastante autodescriptivo, pero la fuente también contiene documentación, por lo que podemos referirnos a ella :
/* * rb_thread_call_without_gvl - permit concurrent/parallel execution. * rb_thread_call_without_gvl2 - permit concurrent/parallel execution * without interrupt process. ... Entonces, por lo que veo (descargo de responsabilidad: sin el análisis realmente cuidadoso del código fuente :)) las eliminaciones dentro del bloque en la pregunta 1) ocurren simultáneamente y 2) sin la "protección" GIL, por lo que las "sorpresas" son más que posibles si uno intenta eliminar los archivos temporales dos veces (la primera vez explícitamente y la segunda vez implícitamente cuando sale el bloque mktmpdir ).