zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

ownership.ex (5755B)


      1 defmodule DBConnection.OwnershipError do
      2   defexception [:message]
      3 
      4   def exception(message), do: %DBConnection.OwnershipError{message: message}
      5 end
      6 
      7 defmodule DBConnection.Ownership do
      8   @moduledoc """
      9   A DBConnection pool that requires explicit checkout and checkin
     10   as a mechanism to coordinate between processes.
     11 
     12   ## Options
     13 
     14     * `:ownership_mode` - When mode is `:manual`, all connections must
     15       be explicitly checked out before by using `ownership_checkout/2`.
     16       Otherwise, mode is `:auto` and connections are checked out
     17       implicitly. `{:shared, owner}` mode is also supported so
     18       processes are allowed on demand. On all cases, checkins are
     19       explicit via `ownership_checkin/2`. Defaults to `:auto`.
     20     * `:ownership_timeout` - The maximum time that a process is allowed to own
     21       a connection, default `120_000`. This timeout exists mostly for sanity
     22       checking purposes and can be increased at will, since DBConnection
     23       automatically checks in connections whenever there is a mode change.
     24     * `:ownership_log` - The `Logger.level` to log ownership changes, or `nil`
     25       not to log, default `nil`.
     26 
     27   There are also two experimental options, `:post_checkout` and `:pre_checkin`
     28   which allows a developer to configure what happens when a connection is
     29   checked out and checked in. Those options are meant to be used during tests,
     30   and have the following behaviour:
     31 
     32     * `:post_checkout` - it must be an anonymous function that receives the
     33       connection module, the connection state and it must return either
     34       `{:ok, connection_module, connection_state}` or
     35       `{:disconnect, err, connection_module, connection_state}`. This allows
     36       the developer to change the connection module on post checkout. However,
     37       in case of disconnects, the return `connection_module` must be the same
     38       as the `connection_module` given. Defaults to simply returning the given
     39       connection module and state.
     40 
     41     * `:pre_checkin` - it must be an anonymous function that receives the
     42       checkin reason (`:checkin`, `{:disconnect, err}` or `{:stop, err}`),
     43       the connection module and the connection state returned by `post_checkout`.
     44       It must return either `{:ok, connection_module, connection_state}` or
     45       `{:disconnect, err, connection_module, connection_state}` where the connection
     46       module is the module given to `:post_checkout` Defaults to simply returning
     47       the given connection module and state.
     48 
     49   ## Callers lookup
     50 
     51   When checking out, the ownership pool first looks if there is a connection
     52   assigned to the current process and then checks if there is a connection
     53   assigned to any of the processes listed under the `$callers` process
     54   dictionary entry. The `$callers` entry is set by default for tasks from
     55   Elixir v1.8.
     56 
     57   You can also pass the `:caller` option on checkout with a pid and that
     58   pid will be looked up first, instead of `self()`, and then we fall back
     59   to `$callers`.
     60   """
     61 
     62   alias DBConnection.Ownership.Manager
     63   alias DBConnection.Holder
     64 
     65   @doc false
     66   defdelegate child_spec(args), to: Manager
     67 
     68   @doc false
     69   defdelegate disconnect_all(pool, interval, opts), to: Manager
     70 
     71   @doc false
     72   def checkout(pool, callers, opts) do
     73     case Manager.proxy_for(callers, opts) do
     74       {caller, pool} -> Holder.checkout(pool, [caller], opts)
     75       nil -> Holder.checkout(pool, callers, opts)
     76     end
     77   end
     78 
     79   @doc """
     80   Explicitly checks a connection out from the ownership manager.
     81 
     82   It may return `:ok` if the connection is checked out.
     83   `{:already, :owner | :allowed}` if the caller process already
     84   has a connection, `:error` if it could be not checked out or
     85   raise if there was an error.
     86   """
     87   @spec ownership_checkout(GenServer.server(), Keyword.t()) ::
     88           :ok | {:already, :owner | :allowed}
     89   def ownership_checkout(manager, opts) do
     90     with {:ok, pid} <- Manager.checkout(manager, opts) do
     91       case Holder.checkout(pid, [self()], opts) do
     92         {:ok, pool_ref, _module, _idle_time, _state} ->
     93           Holder.checkin(pool_ref)
     94 
     95         {:error, err} ->
     96           raise err
     97       end
     98     end
     99   end
    100 
    101   @doc """
    102   Changes the ownership mode.
    103 
    104   `mode` may be `:auto`, `:manual` or `{:shared, owner}`.
    105 
    106   The operation will always succeed when setting the mode to
    107   `:auto` or `:manual`. It may fail with reason `:not_owner`
    108   or `:not_found` when setting `{:shared, pid}` and the
    109   given pid does not own any connection. May return
    110   `:already_shared` if another process set the ownership
    111   mode to `{:shared, _}` and is still alive.
    112   """
    113   @spec ownership_mode(GenServer.server(), :auto | :manual | {:shared, pid}, Keyword.t()) ::
    114           :ok | :already_shared | :not_owner | :not_found
    115   defdelegate ownership_mode(manager, mode, opts), to: Manager, as: :mode
    116 
    117   @doc """
    118   Checks a connection back in.
    119 
    120   A connection can only be checked back in by its owner.
    121   """
    122   @spec ownership_checkin(GenServer.server(), Keyword.t()) ::
    123           :ok | :not_owner | :not_found
    124   defdelegate ownership_checkin(manager, opts), to: Manager, as: :checkin
    125 
    126   @doc """
    127   Allows the process given by `allow` to use the connection checked out
    128   by `owner_or_allowed`.
    129 
    130   It may return `:ok` if the connection is checked out.
    131   `{:already, :owner | :allowed}` if the `allow` process already
    132   has a connection. `owner_or_allowed` may either be the owner or any
    133   other allowed process. Returns `:not_found` if the given process
    134   does not have any connection checked out.
    135   """
    136   @spec ownership_allow(GenServer.server(), owner_or_allowed :: pid, allow :: pid, Keyword.t()) ::
    137           :ok | {:already, :owner | :allowed} | :not_found
    138   defdelegate ownership_allow(manager, owner, allow, opts), to: Manager, as: :allow
    139 end