[ruby/irb] Make save-history extension safe for concurrent use

This makes the save-history extension check for modifications to
the history file before saving it.  If the history file was modified
after the history was loaded and before it was saved, append only
the new history lines to the history file.

This can result in more lines in the history file than SAVE_HISTORY
allows.  However, that will be fixed the next time irb is run and
the history is saved.

Fixes [Bug #13654]

https://github.com/ruby/irb/commit/041ef53845
This commit is contained in:
Jeremy Evans 2021-03-02 12:17:23 -08:00 коммит произвёл git
Родитель 182cde8dfb
Коммит 14e1739ff3
2 изменённых файлов: 51 добавлений и 5 удалений

Просмотреть файл

@ -81,6 +81,8 @@ module IRB
end
}
end
@loaded_history_lines = history.size
@loaded_history_mtime = File.mtime(history_file)
end
end
@ -105,12 +107,20 @@ module IRB
raise
end
open(history_file, "w:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
if File.exist?(history_file) && @loaded_history_mtime &&
File.mtime(history_file) != @loaded_history_mtime
history = history[@loaded_history_lines..-1]
append_history = true
end
open(history_file, "#{append_history ? 'a' : 'w'}:#{IRB.conf[:LC_MESSAGES].encoding}", 0600) do |f|
hist = history.map{ |l| l.split("\n").join("\\\n") }
begin
hist = hist.last(num) if hist.size > num and num > 0
rescue RangeError # bignum too big to convert into `long'
# Do nothing because the bignum should be treated as inifinity
unless append_history
begin
hist = hist.last(num) if hist.size > num and num > 0
rescue RangeError # bignum too big to convert into `long'
# Do nothing because the bignum should be treated as inifinity
end
end
f.puts(hist)
end

Просмотреть файл

@ -127,6 +127,37 @@ module TestIRB
INPUT
end
def test_history_concurrent_use
omit "Skip Editline" if /EditLine/n.match(Readline::VERSION)
IRB.conf[:SAVE_HISTORY] = 1
assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) do |history_file|
exit
5
exit
EXPECTED_HISTORY
1
2
3
4
INITIAL_HISTORY
5
exit
INPUT
assert_history(<<~EXPECTED_HISTORY2, <<~INITIAL_HISTORY2, <<~INPUT2)
exit
EXPECTED_HISTORY2
1
2
3
4
INITIAL_HISTORY2
5
exit
INPUT2
File.utime(File.atime(history_file), File.mtime(history_file) + 2, history_file)
end
end
private
def assert_history(expected_history, initial_irb_history, input)
@ -143,6 +174,11 @@ module TestIRB
io = TestInputMethod.new
io.class::HISTORY.clear
io.load_history
if block_given?
history = io.class::HISTORY.dup
yield IRB.rc_file("_history")
io.class::HISTORY.replace(history)
end
io.class::HISTORY.concat(input.split)
io.save_history