Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a single write for IO#puts #3183

Closed
wants to merge 1 commit into from
Closed

Conversation

cheald
Copy link
Contributor

@cheald cheald commented Jul 24, 2015

This is a fix for #3182

Avoids interleaved writes across threads:

require 'thread'
8.times.map do
  Thread.new do
    1000.times do
      $stdout.puts "line"
      $stdout.fsync
    end
  end
end.map(&:join)

This can currently produce output like:

lineline


linelineline


lineline

lineline

After patching, output is consistently:

line
line
line
line
line
line
line
line
line
line
line
line

@headius
Copy link
Member

headius commented Nov 2, 2016

This one came up on IRC today.

I'm not opposed to this, but currently we implement it the same way MRI does (nearly line-for-line). I'd like to know if there's a reason it's implemented that way, since there are even specs and tests that check for the separate writes.

@headius
Copy link
Member

headius commented Nov 2, 2016

Oh, I did think of one explanation: zero-allocation puts. If you have to stick the \n on a buffer to do it as one write, you'll probably have to allocate a new buffer.

Our puts logic currently isn't zero-alloc, but it could be.

@enebo
Copy link
Member

enebo commented Nov 2, 2016

@headius I think this has languished because of the fear of the unknown. In most of our optimizations of reducing dyncalls we cannot observe a change in behavior. The behavior @cheald fixes as was pointed out in original report is what MRI does as well (weird interleaved output). So even if this is faster it will change behaviorally. If we think no one will really want this behavior (and that is likely true), then we need to consider test suites mocking to make sure there are two write() calls. The only way of getting around that would be to check if this is being called from builtin version of the method. Seems like this is a ways beneath that point. We could pass that and then remove the cost?

@headius
Copy link
Member

headius commented Nov 2, 2016

The cost of the two dynamic calls should be close to negligible at this point, since we added call-site caching. The only real benefit is performing the write in one go.

An alternative would be to lock the IO around the puts logic, but that would also need to check if write had been replaced (since there's no IO-locking semantics in MRI puts).

@headius
Copy link
Member

headius commented Nov 2, 2016

I don't think we'll do this. If you are using an IO across threads, it guarantees only that its internal logic and buffering will be thread-safe, not that any given call will be atomic (and indeed, even write calls might get turned into multiple native calls). If you want atomicity of groups of writes, including implicit cases like puts, you can introduced a lock or use the JRuby::Synchronized module.

@headius headius closed this Nov 2, 2016
@headius headius added this to the Invalid or Duplicate milestone Nov 2, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants