diff --git a/src/crystal/print_buffered.cr b/src/crystal/print_buffered.cr new file mode 100644 index 000000000000..e58423f0f08b --- /dev/null +++ b/src/crystal/print_buffered.cr @@ -0,0 +1,42 @@ +module Crystal + # Prepares an error message, with an optional exception or backtrace, to an + # in-memory buffer, before writing to an IO, usually STDERR, in a single write + # operation. + # + # Avoids intermingled messages caused by multiple threads writing to a STDIO + # in parallel. This may still happen, since writes may not be atomic when the + # overall size is larger than PIPE_BUF, buf it should at least write 512 bytes + # atomically. + def self.print_buffered(message : String, *args, to io : IO, exception = nil, backtrace = nil) : Nil + buf = buffered_message(message, *args, exception: exception, backtrace: backtrace) + io.write(buf.to_slice) + io.flush unless io.sync? + end + + # Identical to `#print_buffered` but eventually calls `System.print_error(bytes)` + # to write to stderr without going through the event loop. + def self.print_error_buffered(message : String, *args, exception = nil, backtrace = nil) : Nil + buf = buffered_message(message, *args, exception: exception, backtrace: backtrace) + System.print_error(buf.to_slice) + end + + private def self.buffered_message(message : String, *args, exception = nil, backtrace = nil) + buf = IO::Memory.new(4096) + + if args.empty? + buf << message + else + System.printf(message, *args) { |bytes| buf.write(bytes) } + end + + if exception + buf << ": " + exception.inspect_with_backtrace(buf) + else + buf.puts + backtrace.try(&.each { |line| buf << " from " << line << '\n' }) + end + + buf + end +end diff --git a/src/fiber.cr b/src/fiber.cr index 55745666c66d..b34a8762037d 100644 --- a/src/fiber.cr +++ b/src/fiber.cr @@ -1,4 +1,5 @@ require "crystal/system/thread_linked_list" +require "crystal/print_buffered" require "./fiber/context" # :nodoc: @@ -147,21 +148,11 @@ class Fiber GC.unlock_read @proc.call rescue ex - io = {% if flag?(:preview_mt) %} - IO::Memory.new(4096) # PIPE_BUF - {% else %} - STDERR - {% end %} if name = @name - io << "Unhandled exception in spawn(name: " << name << "): " + Crystal.print_buffered("Unhandled exception in spawn(name: %s)", name, exception: ex, to: STDERR) else - io << "Unhandled exception in spawn: " + Crystal.print_buffered("Unhandled exception in spawn", exception: ex, to: STDERR) end - ex.inspect_with_backtrace(io) - {% if flag?(:preview_mt) %} - STDERR.write(io.to_slice) - {% end %} - STDERR.flush ensure # Remove the current fiber from the linked list Fiber.inactive(self)