PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/contrib/mongodb/mvar.erl

http://github.com/evanmiller/ChicagoBoss
Erlang | 89 lines | 52 code | 20 blank | 17 comment | 0 complexity | 67df3b93ac227a936d863a609c864c4f MD5 | raw file
  1. % A mvar is a process that holds a value (its content) and provides exclusive access to it. When a mvar terminates it executes it given finalize procedure, which is needed for content that needs to clean up when terminating. When a mvar is created it executes its supplied initialize procedure, which creates the initial content from within the mvar process so if the initial content is another linked dependent process (such as a socket) it will terminate when the mvar terminates without the need for a finalizer. A mvar itself dependently links to its parent process (the process that created it) and thus terminates when its parent process terminates.
  2. -module (mvar).
  3. -export_type ([mvar/1]).
  4. -export ([create/2, new/2, new/1]).
  5. -export ([modify/2, with/2, read/1, write/2]).
  6. -export ([terminate/1]).
  7. -behaviour (gen_server).
  8. -export ([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  9. -type mvar(_) :: pid() | atom().
  10. % Unregistered or registered process holding a value of given paramterized type
  11. -type initializer(A) :: fun (() -> A). % IO throws X
  12. % Creates initial value. Any throw will be caught and re-thrown in the creating process.
  13. -type finalizer(A) :: fun ((A) -> ok). % IO
  14. % Closes supplied value. Any exception will not be caught causing an exit signal to be sent to parent (creating) process.
  15. -spec create (initializer(A), finalizer(A)) -> mvar(A). % IO throws X
  16. % Create new mvar with initial content created from initializer (run within new mvar process so it owns it). Any throw in initializer will be caught and re-thrown in the calling process. Other exceptions will not be caught causing an exit signal to be sent to calling process. When the mvar terminates then given finalizer will be executed against current content. Any exception raised in finalizer (when terminating) will be sent as an exit signal to the parent (calling) process.
  17. create (Initialize, Finalize) ->
  18. Ref = make_ref(),
  19. case gen_server:start_link (?MODULE, {self(), Ref, Initialize, Finalize}, []) of
  20. {ok, Pid} -> Pid;
  21. ignore -> receive {mvar_init_throw, Ref, Thrown} -> throw (Thrown) end end.
  22. -spec new (A, finalizer(A)) -> mvar(A). % IO
  23. % Same as `create` except initial value given directly
  24. new (Value, Finalize) -> create (fun () -> Value end, Finalize).
  25. -spec new (A) -> mvar(A). % IO
  26. % Same as `new/2` except no finalizer
  27. new (Value) -> new (Value, fun (_) -> ok end).
  28. -type modifier(A,B) :: fun ((A) -> {A, B}). % IO throws X
  29. -spec modify (mvar(A), modifier(A,B)) -> B. % IO throws X
  30. % Atomically modify content and return associated result. Any throw is caught and re-thrown in caller. Errors are not caught and will terminate var and send exit signal to parent.
  31. modify (Var, Modify) -> case gen_server:call (Var, {modify, Modify}) of
  32. {ok, B} -> B;
  33. {throw, Thrown} -> throw (Thrown) end.
  34. -spec with (mvar(A), fun ((A) -> B)) -> B. % IO throws X, fun IO throws X
  35. % Execute Procedure with exclusive access to content but don't modify it.
  36. with (Var, Act) -> modify (Var, fun (A) -> {A, Act (A)} end).
  37. -spec read (mvar(A)) -> A. % IO
  38. % Return content
  39. read (Var) -> with (Var, fun (A) -> A end).
  40. -spec write (mvar(A), A) -> A. % IO
  41. % Change content and return previous content
  42. write (Var, Value) -> modify (Var, fun (A) -> {Value, A} end).
  43. -spec terminate (mvar(_)) -> ok. % IO
  44. % Terminate mvar. Its finalizer will be executed. Future accesses to this mvar will fail.
  45. terminate (Var) -> gen_server:call (Var, stop).
  46. % gen_server callbacks %
  47. -type state(A) :: {A, finalizer(A)}.
  48. -spec init ({pid(), reference(), initializer(A), finalizer(A)}) -> {ok, state(A)} | ignore. % IO
  49. % Create initial value using initializer and return it in state with finalizer. Catch throws in initializer and report it to caller via direct send and `ignore` result. `create` will pick this up an re-throw it in caller. An error in initializer will cause process to abort and exit signal being sent to caller.
  50. init ({Caller, ThrowRef, Initialize, Finalize}) -> try Initialize()
  51. of A -> {ok, {A, Finalize}}
  52. catch Thrown -> Caller ! {mvar_init_throw, ThrowRef, Thrown}, ignore end.
  53. -spec handle_call
  54. ({modify, modifier(A,B)}, {pid(), tag()}, state(A)) -> {reply, {ok, B} | {throw, any()}, state(A)};
  55. (stop, {pid(), tag()}, state(A)) -> {stop, normal, ok, state(A)}. % IO
  56. % Modify content and return associated value. Catch any throws and return them to `modify` to be re-thrown. Errors will abort this mvar process and send exit signal to linked owner.
  57. handle_call ({modify, Modify}, _From, {A, X}) -> try Modify (A)
  58. of {A1, B} -> {reply, {ok, B}, {A1, X}}
  59. catch Thrown -> {reply, {throw, Thrown}, {A, X}} end;
  60. % Terminate mvar
  61. handle_call (stop, _From, State) -> {stop, normal, ok, State}.
  62. -spec terminate (reason(), state(_)) -> any(). % IO. Result ignored
  63. % Execute finalizer upon termination
  64. terminate (_Reason, {A, Finalize}) -> Finalize (A).
  65. -type tag() :: any(). % Unique tag
  66. -type reason() :: any().
  67. handle_cast (_Request, State) -> {noreply, State}.
  68. handle_info (_Info, State) -> {noreply, State}.
  69. code_change (_OldVsn, State, _Extra) -> {ok, State}.