sasa1977 / con_cache

ets based key/value cache with row level isolated writes and ttl support
MIT License
910 stars 71 forks source link

Problem with ConCache.update on the second update with ttl: :no_update #21

Closed slashmili closed 8 years ago

slashmili commented 8 years ago

Hi,

I am using con_cache version 0.10.0

When I run these commands I face an error :

iex(1)> options = [ttl_check: :timer.seconds(1),ttl: :timer.seconds(1)]
[ttl_check: 1000, ttl: 1000]
iex(2)> gen_server_options = []
[]
iex(3)> {:ok, cache} = ConCache.start_link(options, gen_server_options)
{:ok, #PID<0.315.0>}
iex(4)> ConCache.update(cache, :a, fn(_old) -> {:ok, %ConCache.Item{value: 1, ttl: :timer.seconds(10)}} end)
:ok
iex(5)> ConCache.get(cache, :a)
1
iex(6)> ConCache.update(cache, :a, fn(value) -> {:ok, %ConCache.Item{value: value + 1, ttl: :no_update}} end)
:ok
iex(7)> ConCache.get(cache, :a)
2
** (EXIT from #PID<0.311.0>) an exception was raised:
    ** (FunctionClauseError) no function clause matching in ConCache.Owner.store_ttl/3
        (con_cache) lib/con_cache/owner.ex:153: ConCache.Owner.store_ttl(%ConCache.Owner{current_time: 1, max_time: 65535, monitor_ref: #Reference<0.0.1.237>, on_expire: #Function<0.74405522/1 in ConCache.Owner.start_ttl_loop/1>, pending: 159802, pending_ttl_sets: #HashDict<[a: :no_update]>, ttl_check: 1000, ttls: 163899}, :a, :no_update)
        (elixir) lib/enum.ex:610: anonymous fn/3 in Enum.each/2
        (elixir) lib/enum.ex:1478: anonymous fn/3 in Enum.reduce/3
        (elixir) lib/hash_dict.ex:188: HashDict.do_reduce_each/4
        (elixir) lib/enum.ex:1477: Enum.reduce/3
        (elixir) lib/enum.ex:609: Enum.each/2
        (con_cache) lib/con_cache/owner.ex:113: ConCache.Owner.apply_pending_ttls/1
        (con_cache) lib/con_cache/owner.ex:182: ConCache.Owner.handle_info/2
        (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
        (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

iex(8)> [error] GenServer #PID<0.315.0> terminating
** (FunctionClauseError) no function clause matching in ConCache.Owner.store_ttl/3
    (con_cache) lib/con_cache/owner.ex:153: ConCache.Owner.store_ttl(%ConCache.Owner{current_time: 1, max_time: 65535, monitor_ref: #Reference<0.0.1.237>, on_expire: #Function<0.74405522/1 in ConCache.Owner.start_ttl_loop/1>, pending: 159802, pending_ttl_sets: #HashDict<[a: :no_update]>, ttl_check: 1000, ttls: 163899}, :a, :no_update)
    (elixir) lib/enum.ex:610: anonymous fn/3 in Enum.each/2
    (elixir) lib/enum.ex:1478: anonymous fn/3 in Enum.reduce/3
    (elixir) lib/hash_dict.ex:188: HashDict.do_reduce_each/4
    (elixir) lib/enum.ex:1477: Enum.reduce/3
    (elixir) lib/enum.ex:609: Enum.each/2
    (con_cache) lib/con_cache/owner.ex:113: ConCache.Owner.apply_pending_ttls/1
    (con_cache) lib/con_cache/owner.ex:182: ConCache.Owner.handle_info/2
    (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: :check_purge
State: %ConCache.Owner{current_time: 1, max_time: 65535, monitor_ref: #Reference<0.0.1.237>, on_expire: #Function<0.74405522/1 in ConCache.Owner.start_ttl_loop/1>, pending: 159802, pending_ttl_sets: #HashDict<[a: :no_update]>, ttl_check: 1000, ttls: 163899}

Something strange is going on, I wanted to reproduce the same problem in con_cache tests but the same code works on the test

diff --git a/test/con_cache_test.exs b/test/con_cache_test.exs
index 9f6f60a..c6113f8 100644
--- a/test/con_cache_test.exs
+++ b/test/con_cache_test.exs
@@ -200,6 +200,16 @@ defmodule ConCacheTest do
       end)
   end

+  test "first update with ttl and second update without touching ttl" do
+    with_cache(
+      [ttl_check: :timer.seconds(1),ttl: :timer.seconds(1)],
+      fn(cache) ->
+        ConCache.update(cache, :a, fn(_old) -> {:ok, %ConCache.Item{value: 1, ttl: :timer.seconds(1)}} end)
+        ConCache.update(cache, :a, fn(value) -> {:ok, %ConCache.Item{value: value + 1, ttl: :no_update}} end)
+        assert ConCache.get(cache, :a) == 2
+      end)
+  end
+
   defp test_renew_ttl(cache, fun) do
     ConCache.put(cache, :a, 1)
     :timer.sleep(50)
mix test
....................................

Finished in 5.4 seconds (0.2s on load, 5.2s on tests)
36 tests, 0 failures

Randomized with seed 698014
sasa1977 commented 8 years ago

:no_update support has been merged, but the new hex package has not yet been released. Could you please try pointing your dependency to the GitHub master and see if it works? If you need this feature now, I could release 0.11.0, but I'd like to confirm first it works for you with the current master.

slashmili commented 8 years ago

I'm trying to write a counter which gets expired after a time period, I doesn't work as I expected.

This is the test case that I expect to work but it doesn't.

  test "second update shouldn't clear key's orignial ttl" do
    update_fn = fn(value) ->
      case value do
        nil -> {:ok, 1}
        _   -> {:ok, %ConCache.Item{value: value + 1, ttl: :no_update}}
      end
    end
    with_cache(
      [ttl_check: 10,ttl: 50],
      fn(cache) ->
        ConCache.update(cache, :a, &update_fn.(&1))
        assert ConCache.get(cache, :a) == 1
        ConCache.update(cache, :a, &update_fn.(&1))
        assert ConCache.get(cache, :a) == 2
        :timer.sleep(40)
        refute ConCache.get(cache, :a)
      end)
  end

It works if I change my update_fn to:

    update_fn = fn(value) ->
      case value do
        nil -> {:ok, %ConCache.Item{value: value + 1, ttl: 50}}
        _   -> {:ok, %ConCache.Item{value: value + 1, ttl: :no_update}}
      end
    end

But I suppose the first one should also work, right? I expect during update if I return {:ok, 1} ConCache would honor the configured ttl and uses that one. However looks like that it creates an item in cache without ttl

slashmili commented 8 years ago

It's more related to ConCache.update so I close this issue.

I'll update you if :no_cache works properly later