Creating thread-safe non-deleting unique filenames in ruby/rails <p>I'm building a bulk-file-uploader. Multiple files are uploaded in individual requests, and my UI provides progress and success/fail. Then, once all files are complete, a final request processes/finalizes them. For this to work, I need to create many temporary files that live longer than a single request. Of course I also need to guarantee filenames are unique across app instances.</p> <p>Normally I would use <a href="http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/Tempfile.html" rel="nofollow"><code>Tempfile</code></a> for easy unique filenames, but in this case it won't work because the files need to stick around until another request comes in to further process them. <code>Tempfile</code> auto-unlinks files when they're closed and garbage collected.</p> <p>An earlier question <a href="http://stackoverflow.com/questions/13787746/creating-a-thread-safe-temporary-file-name">here</a> suggests using <code>Dir::Tmpname.make_tmpname</code> but this seems to be undocumented and I don't see how it is thread/multiprocess safe. Is it guaranteed to be so?</p> <p>In c I would open the file <code>O_EXCL</code> which will fail if the file exists. I could then keep trying until I successfully get a handle on a file with a truly unique name. But ruby's <code>File.open</code> doesn't seem to have an "exclusive" option of any kind. If the file I'm opening already exists, I have to either append to it, open for writing at the end, or empty it.</p> <p>Is there a "right" way to do this in ruby?</p> <p>I have worked out a method that I think is safe, but is seems overly complex:</p> <pre class="lang-ruby prettyprint-override"><code># make a unique filename time = Time.now filename = "#{time.to_i}-#{sprintf('%06d', time.usec)}" # make tempfiles (this is gauranteed to find a unique creatable name) data_file = Tempfile.new(["upload", ".data"], UPLOAD_BASE) # but the file will be deleted automatically, which we don't want, so now link it in a stable location count = 1 loop do begin # File.link will raise an exception if the destination path exists File.link(data_file.path, File.join(UPLOAD_BASE, "#{filename}-#{count}.data")) # so here we know we created a file successfully and nobody else will take it break rescue Errno::EEXIST count += 1 end end # now unlink the original tempfiles (they're still writeable until they're closed) data_file.unlink # ... write to data_file and close it ... </code></pre> <p>NOTE: This won't work on Windows. Not a problem for me, but reader beware.</p> <p>In my testing this works reliably. But again, is there a more straightforward way?</p>