summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2021-03-02 12:17:23 -0800
committergit <svn-admin@ruby-lang.org>2021-03-06 00:18:32 +0900
commit14e1739ff3ec81c9ea87a8aba03393f0bf0433a7 (patch)
treeb06b0ca59362886a320ecb79f13ad37b85b3febb
parent182cde8dfbbc6b9044d9b76c0bfdcf031bcda778 (diff)
downloadruby-14e1739ff3ec81c9ea87a8aba03393f0bf0433a7.tar.gz
[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
-rw-r--r--lib/irb/ext/save-history.rb20
-rw-r--r--test/irb/test_history.rb36
2 files changed, 51 insertions, 5 deletions
diff --git a/lib/irb/ext/save-history.rb b/lib/irb/ext/save-history.rb
index ac358c8ccb..7acaebe36a 100644
--- a/lib/irb/ext/save-history.rb
+++ b/lib/irb/ext/save-history.rb
@@ -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
diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb
index 392a6afa9a..aad89529ea 100644
--- a/test/irb/test_history.rb
+++ b/test/irb/test_history.rb
@@ -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