Project

General

Profile

Bug #17669 » reproducible.rb

yoheimuta (Yohei Yoshimuta), 03/02/2021 09:48 AM

 
require 'logger'
require 'timeout'

# @logger is one of examples that includes a MonitorMixin somehow,
# which state can be broken unexpectedly by timeout.
@logger = Logger.new("/tmp/tmp.txt")

# mon_info_util is just a utility function composing monitor state information,
# which is only used to be logged for clarity.
def mon_info_util
logdev = @logger.instance_variable_get(:@logdev)
"mon_owned=#{logdev.mon_owned?}, mon_locked=#{logdev.mon_locked?}, mon_owner=#{logdev.instance_variable_get(:@mon_owner)}"
end

# repeat_logging_until_timeout represents a bug-reproducible function.
# It repeats writing a log until a timeout interrupts the operation.
def repeat_logging_until_timeout
# this timout block could reproduce a deadlock-ish situation caused by a timeout
Timeout.timeout(1) do
while true do
@logger.info "test"
end
end

rescue Timeout::Error => e
# check the monitor state
m = e.backtrace.join("\n")
p "#{e.message}\n#{m}\n"
p "main: #{mon_info_util}"

# tha fact that mon_owned? returns true even after finishing @logger.info is the root problem.
logdev = @logger.instance_variable_get(:@logdev)
if logdev.mon_owned?
# if you remove this comment out, calling mon_exit explicitly allows the other thread to write a log successfully.
# logdev.mon_exit

t = Thread.new do
p "t: #{mon_info_util}"
# @logger.info is blocked forever before writing because of the monitor locked by the other thread.
@logger.info "new thread writing from logger"
end
sleep 5
@logger.info "t's backtrace is #{t.backtrace.join("\n")}"
raise e
end
end

# do an endless loop until the inside function succeeds to reproduce the bug.
while true do
repeat_logging_until_timeout
end
(1-1/3)