Giter Site home page Giter Site logo

Disable memoize globally? about memoize HOT 9 CLOSED

melpon avatar melpon commented on June 17, 2024
Disable memoize globally?

from memoize.

Comments (9)

melpon avatar melpon commented on June 17, 2024 4
defmodule MyMemoize do
  defmacro __using__(_) do
    quote do
      if Mix.env() == :benchmark do
        use Memoize
      else
        import MyMemoize,
          only: [defmemo: 1, defmemo: 2, defmemo: 3, defmemop: 1, defmemop: 2, defmemop: 3]
    end
  end

  defmacro defmemo(call, expr_or_opts \\ nil) do
    {_opts, expr} = resolve_expr_or_opts(expr_or_opts)
    quote do
      def(unquote(call), unquote(expr))
    end
  end

  defmacro defmemop(call, expr_or_opts \\ nil) do
    {_opts, expr} = resolve_expr_or_opts(expr_or_opts)
    quote do
      defp(unquote(call), unquote(expr))
    end
  end

  defmacro defmemo(call, _opts, expr) do
    quote do
      def(unquote(call), unquote(expr))
    end
  end

  defmacro defmemop(call, _opts, expr) do
    quote do
      defp(unquote(call), unquote(expr))
    end
  end

  defp resolve_expr_or_opts(expr_or_opts) do
    cond do
      expr_or_opts == nil ->
        {[], nil}

      # expr_or_opts is expr
      Keyword.has_key?(expr_or_opts, :do) ->
        {[], expr_or_opts}

      # expr_or_opts is opts
      true ->
        {expr_or_opts, nil}
    end
  end
end

defmodule Foo do
  use MyMemoize
  # It will not be memoized if Mix.env() is :benchmark
  defmemo foo() do
    do_foo()
  end
end

from memoize.

tylerj avatar tylerj commented on June 17, 2024 2

I decided to implement a DummyCacheStrategy that I use in my test env, which in the read/3 function immediately invalidates everything in its tab before returning :ok. I think this is the equivalent of never storing the result in the cache. Seems to be working well so far.

I found that the expires_in: 0 option wasn't reliable enough for my needs, since it was only invalidating during a read after at least 1ms had passed since the value was cached. This would result in intermittent test failures in my test suite, which needs to completely disable the cache.

config :memoize, cache_strategy: MyApp.DummyCacheStrategy
defmodule MyApp.DummyCacheStrategy do
  @behaviour Memoize.CacheStrategy

  @ets_tab __MODULE__

  def init(opts) do
    :ets.new(@ets_tab, [:public, :set, :named_table, {:read_concurrency, true}])
    opts
  end

  def tab(_), do: @ets_tab
  def cache(_, _, _), do: nil

  def read(_, _, _) do
    invalidate()
    :ok
  end

  def invalidate() do
    :ets.select_delete(@ets_tab, [{{:_, {:completed, :_, :_}}, [], [true]}])
  end

  def invalidate(_), do: invalidate()
  def garbage_collect(), do: invalidate()
end

from memoize.

johnknott avatar johnknott commented on June 17, 2024 1

Thank you very much! :)

I'll give it a whirl.

from memoize.

kanes115 avatar kanes115 commented on June 17, 2024 1

I think that such dummy strategy (as suggested by @tylerj) should be a part of the library and suggested as a way to go about testing. Most of the time that will be the behaviour the tester expects. To say more - it is confusing that the user can set "expires_in" to 0 and that is not exactly true (which is fine outside of the tests realm as requiring serialization would have performance implications).

from memoize.

melpon avatar melpon commented on June 17, 2024
if Mix.env() == :benchmark do
  def foo() do
    do_foo()
  end
else
  defmemo foo() do
    do_foo()
  end
end

from memoize.

cblavier avatar cblavier commented on June 17, 2024

Just a slightly fixed version of @melpon solution:

defmodule MyApp.Memoize do
  defmacro __using__(_) do
    if Mix.env() == :test do
      quote do
        import MyApp.Memoize,
          only: [defmemo: 1, defmemo: 2, defmemo: 3, defmemop: 1, defmemop: 2, defmemop: 3]
      end
    else
      quote do
        use Memoize
      end
    end
  end

  defmacro defmemo(call, expr_or_opts \\ nil) do
    {_opts, expr} = resolve_expr_or_opts(expr_or_opts)

    quote do
      def(unquote(call), unquote(expr))
    end
  end

  defmacro defmemop(call, expr_or_opts \\ nil) do
    {_opts, expr} = resolve_expr_or_opts(expr_or_opts)

    quote do
      defp(unquote(call), unquote(expr))
    end
  end

  defmacro defmemo(call, _opts, expr) do
    quote do
      def(unquote(call), unquote(expr))
    end
  end

  defmacro defmemop(call, _opts, expr) do
    quote do
      defp(unquote(call), unquote(expr))
    end
  end

  defp resolve_expr_or_opts(expr_or_opts) do
    cond do
      expr_or_opts == nil -> {[], nil}
      Keyword.has_key?(expr_or_opts, :do) -> {[], expr_or_opts}
      true -> {expr_or_opts, nil}
    end
  end
end

from memoize.

paulstatezny avatar paulstatezny commented on June 17, 2024

Thank you @cblavier!

I think it would be really useful to add this into the library, perhaps as a config flag?

config :my_app, Memoize, caching_enabled: false

I don't think the particular mechanism matters, but in a standard web app scenario I imagine this would be a very common need.

By "standard web app scenario", I mean a web application that does CRUD and has integration tests. The Ecto Sandbox adapter prevents data from "bleeding over" between concurrently running tests, but when you use Memoize there is bleed-over. This cost me a few hours of debugging before I realized Memoize was the cause.

@melpon By the way, this is a reasoned suggestion, not a complaint. Thanks for sharing this useful library!

from memoize.

paulstatezny avatar paulstatezny commented on June 17, 2024

If the configuration route is undesirable, instructions about this scenario could perhaps be added to the README?

from memoize.

melpon avatar melpon commented on June 17, 2024

I implemented global :expires_in option since 1.4.0.

config :memoize,
  cache_strategy: Memoize.CacheStrategy.Default

config :memoize, Memoize.CacheStrategy.Default,
  expires_in: 0 # invalidate immediately

However, this is not exactly the same as disabling the cache. All concurrent calls by many processes are serialized.

from memoize.

Related Issues (15)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.