Improve the example of ActiveSupport::Cache::Store#fetch

The original example has race condition issue that the output of the example isn't conistent, see https://github.com/rails/rails/issues/43588.

The change improves the example by removing unnecessary thread and add detailed comments.

In the new example, there will be race condition only if `ActiveSupport::Cache::MemoryStore` took longer than 0.1 second to extend expiry but that's unlikely to happen.

Close #43588
This commit is contained in:
Jian Weihang 2024-02-20 11:56:16 +08:00
parent 554e71af0b
commit a8bc63af54
No known key found for this signature in database
GPG Key ID: 294AEB9F5C889A31
1 changed files with 31 additions and 15 deletions

View File

@ -405,31 +405,47 @@ module ActiveSupport
# has elapsed. # has elapsed.
# #
# # Set all values to expire after one minute. # # Set all values to expire after one minute.
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute) # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1)
# #
# cache.write('foo', 'original value') # cache.write("foo", "original value")
# val_1 = nil # val_1 = nil
# val_2 = nil # val_2 = nil
# sleep 60 # p cache.read("foo") # => "original value"
# #
# Thread.new do # sleep 1 # wait until the cache expires
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do #
# t1 = Thread.new do
# # fetch does the following:
# # 1. gets an recent expired entry
# # 2. extends the expiry by 2 seconds (race_condition_ttl)
# # 3. regenerates the new value
# val_1 = cache.fetch("foo", race_condition_ttl: 2) do
# sleep 1 # sleep 1
# 'new value 1' # "new value 1"
# end # end
# end # end
# #
# Thread.new do # # Wait until t1 extends the expiry of the entry
# val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do # # but before generating the new value
# 'new value 2' # sleep 0.1
# end #
# val_2 = cache.fetch("foo", race_condition_ttl: 2) do
# # This block won't be executed because t1 extended the expiry
# "new value 2"
# end # end
# #
# cache.fetch('foo') # => "original value" # t1.join
# sleep 10 # First thread extended the life of cache by another 10 seconds #
# cache.fetch('foo') # => "new value 1" # p val_1 # => "new value 1"
# val_1 # => "new value 1" # p val_2 # => "oritinal value"
# val_2 # => "original value" # p cache.fetch("foo") # => "new value 1"
#
# # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
# # We have waited 2 seconds already (sleep(1) + t1.join) thus we need to wait 1
# # more second to see the entry expire.
# sleep 1
#
# p cache.fetch("foo") # => nil
# #
# ==== Dynamic Options # ==== Dynamic Options
# #