tomthestorm

tomthestorm

Hi, I try understand GenServer .
Why GenServer.call is synchronous ? I want to get, in asynchronous way, information about state of the pid, but I can’t.
For example if I send 1000 of GenServer.call(pid,:get) via :

[ Task.await(Task.async(fn → Genserver.call(pid,:get) end)) , [ Task.await(Task.async(fn → Genserver.call(pid,:get) end)) , … , … ]

when I get the last one I have to wait 999 times.

Maybe it is stupid question , but for me GenServer.call should be asynchronous because we want read state and GenServer.cast should be synchronous cause we modify the state (and we should lock state during modification) .
But the situation is opposite and I don’t understand why .
Thanks in advance for answer .

Showing Posts 1 to 10

sztosz

sztosz

Cast and call are not about whether we read or modify the state, but do we wan’t to wait for the response, and do nothing or are we not going to wait for the response (we don’t care about the response at all).

Cast and call both can modify state.

Call is synchronous by nature, because it hast to send a response for which calling process await.

And beside that messages in GenServer are read sequentially IIRC, so any computation done based on message are done sequentially too.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

There are two minor misconceptions here: call vs cast is not about read vs write. You can (and usually should) modify via call, and you often use call to read too. Call vs cast is literally about whether you as the client want to communicate async vs sync.

This becomes clearer if you think about the mechanism of communication: message passing. All message passing is async, so if you send a message to the genserver to read OR write and you want to know the result, the only way you can do so is to wait around for a reply message from the genserver. This is exactly what call does. If you want to write and you don’t care about the result, or if you want to tell the genserver to send you a reply and you’ll look at it later, you can consider cast.

The second misconception is that the genserver is some state that can be read directly by the client. As noted, all communication between processes is via message passing, and so if clients want to read a genserver, the genserver needs to send them a message. Genservers, like all processes, are single threaded, and so the message sending happens one after the next.

11
Post #3
peerreynders

peerreynders

For some background this topic may be of some interest:

This may already be an indication that you are solving your problem in a less than optimal way in a process-oriented environment (there simply isn’t enough detail to know whether this is in fact true).

More often than not process state exists to enable the process to enact some sort of protocol in concert with other processes rather than the process simply acting as a container-of-state to be queried.

To a certain degree this has been a problem in some object-oriented designs and can be even more a problem with process-oriented solutions.

Some of my posts around “Tell, don’t Ask”:

You wouldn’t write code like that anyway. At the very least you would organize it more like:

funs = [fn -> Genserver.call(pid,:get) end, fn -> Genserver.call(pid,:get) end, … , … ]
tasks = Enum.map(funs, &Task.async/1)
results = Enum.map(tasks, &Task.await/2)

or

funs = [fn -> Genserver.call(pid,:get) end, fn -> Genserver.call(pid,:get) end, … , … ]
results =
  funs
  |> Enum.map(&Task.async/1)
  |> Enum.map(&Task.await/2)

The idea being that you first launch all the tasks and only then you start waiting on them (in this particular case it wouldn’t make much of a difference as you are waiting on the same process anyway).

And ideally you would be using Task.Supervisor.async_stream/6 anyway, as that gives you better control of the level of concurrency that may be reasonable under the circumstances.

tomthestorm

tomthestorm OP

Thanks for all answers.
So is there any possibility to get the state (if the process hold the state) from the process in asynchronous way ?
Anyway thanks for idea ““Tell, don’t Ask” , I’ll try change my thinking.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

@tomthestorm if you mean async with respect to the client, the client can simply do current_process = self(); GenServer.cast(pid, {:give_me_state, current_process}) and then the genserver can message the current process with its state.

If what you’re looking for is some kind of key value store that could be accessed concurrently by many processes, then that’s what an :ets table provides.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

It may also help if you were to sketch out what kind of client side code you’re expecting here. It sort of sounds like you’re wondering if there is a way to get the Processes’s state without actually sending it a message, and no that is not possible. A GenServer is just a loop with a receive block, and if you want it to do something the only want to ask it is via a message.

sribe

sribe

What do you mean get it in an asynchronous way? How do you want the caller to receive the value?

peerreynders

peerreynders

Example: stack server with an asynchronous “pop” and a proxy server which turns it into a synchronous “pop”.

# file cast/lib/cast/application.ex
#
defmodule Cast.Application do
  use Application

  def start(_type, _args) do
    stack_server = :cast_stack
    proxy_server = :proxy

    children = [
      {Stack, [args: [:hello], name: stack_server]},
      {Proxy, [for_server: stack_server, name: proxy_server]}
    ]

    opts = [strategy: :rest_for_one, name: Cast.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
# file: cast/lib/stack.ex
# stack server with asynchronous "pop"
#
defmodule Stack do
  use GenServer

  # client

  def start_link(opts) do
    {args, opts} = Keyword.pop(opts, :args, [])
    {name, opts} = Keyword.pop(opts, :name, __MODULE__)
    opts = Keyword.put(opts, :name, name)
    GenServer.start_link(__MODULE__, args, opts)
  end

  def push(server, item), 
    do: GenServer.cast(server, {:push, item})

  def pop(server) do
    ref = make_ref()    # create a reference as a correlation identifier
    GenServer.cast(server, {:pop, ref, self()})
    ref
  end

  # implemention

  def pop_to([item | rest], ref, to) do
    Process.send_after(self(), {:pop_to, to, ref, item}, 500) # send head item in half a second
    rest
  end

  # callbacks

  @impl GenServer
  def init(stack),
    do: {:ok, stack}

  @impl GenServer
  def handle_cast({:pop, ref, to}, state),
    do: {:noreply, pop_to(state, ref, to)} # Part 1 of asynchronous "pop": pop the top and prepare to send it later.
  def handle_cast({:push, item}, state),
    do: {:noreply, [item | state]}         # push new item on the stack

  @impl GenServer
  def handle_info({:pop_to, to, ref, item}, state) do # process send_after message
    GenServer.cast(to, {:popped, ref, item})          # Part 2 of asynchronous "pop": send popped item back to client.
    {:noreply, state}
  end

end
# file: cast/lib/proxy.ex
# proxy server to the stack server that turns the
# stack server's asynchronous "pop" into a synchronous "pop".
#
defmodule Proxy do
  use GenServer

  # client

  def start_link(opts) do
    {for_server, opts} = Keyword.pop(opts, :for_server, [])
    {name, opts} = Keyword.pop(opts, :name, __MODULE__)
    opts = Keyword.put(opts, :name, name)
    GenServer.start_link(__MODULE__, {for_server, %{}}, opts)
  end

  def push(server, item),
    do: GenServer.cast(server, {:push, item})

  def pop(server),
    do: GenServer.call(server, :pop)

  # implementation
  def start_pop(from, {for_server, pending}) do
    ref = Stack.pop(for_server)               # cast asynchronous "pop" request
    new_pending = Map.put(pending, ref, from) # store the correlation identifier
    {for_server, new_pending}
  end

  def complete_pop(pending, ref, item) do
    case Map.fetch(pending, ref) do # find client based on correlation identifier
      {:ok, from} ->
        GenServer.reply(from, item) # now complete that pending "pop" call
        Map.delete(pending, ref)
      _ ->
        pending
    end
  end

  # callbacks

  @impl GenServer
  def init(args),
    do: {:ok, args}

  @impl GenServer
  def handle_call(:pop, from, state),
    do: {:noreply, start_pop(from, state)} # i.e. don't complete "pop" call; wait until _item_ is cast back

  @impl GenServer
  def handle_cast({:push, item}, {for_server, _} = state) do
    Stack.push(for_server, item) # forward item to stack "push"
    {:noreply, state}
  end
  def handle_cast({:popped, ref, item},{for_server, pending}) do # i.e. popped item has been cast back
    state = {for_server, complete_pop(pending, ref, item)}
    {:noreply, state}
  end

end
$ iex -S mix
Erlang/OTP 21 [erts-10.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Application.started_applications()
[
  {:cast, 'cast', '0.1.0'},
  {:logger, 'logger', '1.6.6'},
  {:mix, 'mix', '1.6.6'},
  {:iex, 'iex', '1.6.6'},
  {:elixir, 'elixir', '1.6.6'},
  {:compiler, 'ERTS  CXC 138 10', '7.2'},
  {:stdlib, 'ERTS  CXC 138 10', '3.5'},
  {:kernel, 'ERTS  CXC 138 10', '6.0'}
]
iex(2)> Application.stop(:cast)
:ok

16:46:40.552 [info]  Application cast exited: :stopped
iex(3)> r(Stack)
warning: redefining module Stack (current version loaded from _build/dev/lib/cast/ebin/Elixir.Stack.beam)
  lib/stack.ex:4

{:reloaded, Stack, [Stack]}
iex(4)> r(Proxy)
warning: redefining module Proxy (current version loaded from _build/dev/lib/cast/ebin/Elixir.Proxy.beam)
  lib/proxy.ex:5

{:reloaded, Proxy, [Proxy]}
iex(5)> r(Cast.Application)
warning: redefining module Cast.Application (current version loaded from _build/dev/lib/cast/ebin/Elixir.Cast.Application.beam)
  lib/cast/application.ex:3

{:reloaded, Cast.Application, [Cast.Application]}
iex(6)> Application.start(:cast)
:ok
iex(7)> Proxy.pop(:proxy)              # pop via proxy (synchronous)
:hello
iex(8)> Proxy.push(:proxy, :greetings) # push via proxy
:ok
iex(9)> Stack.push(:cast_stack, :bye)  # push directly
:ok
iex(10)> Stack.pop(:cast_stack)        # pop directly
#Reference<0.983023136.1924136961.112221>
iex(11)> flush()
:ok
iex(12)> Process.sleep(500)
:ok
iex(13)> flush()
{:"$gen_cast", {:popped, #Reference<0.983023136.1924136961.112221>, :bye}}
:ok
iex(14)> Stack.pop(:cast_stack)
#Reference<0.983023136.1924136961.112255>
iex(15)> Process.sleep(600)
:ok
iex(16)> flush()
{:"$gen_cast", {:popped, #Reference<0.983023136.1924136961.112255>, :greetings}}
:ok
iex(17)> 
OvermindDL1

OvermindDL1

I wish instead of just GenServer.call and GenServer.cast we also had a GenServer.async or something where it returns an opaque type that you can then later GenServer.await on to get the result. It would be really easy to make and would make interleaving work easier, but otherwise it would just be a wrapper around the GenServer.call protocol (async would send a message with a ref and pid, return that ref, then await would take that ref and receive on it to get the output). You can build that yourself with cast, but it would be nice to wrap the call protocol.

peerreynders

peerreynders

Given the blocking nature, I don’t think the await concept is really appropriate inside a typical GenServer. What you want is a way to continue processing once the result becomes available - and there really isn’t that much ceremony involved in rigging something up.

# file: cast/lib/proxy.ex
# proxy server to the stack server that turns the
# stack server's asynchronous "pop" into a synchronous "pop".
#
defmodule Proxy do
  use GenServer

  # client

  def start_link(opts) do
    {for_server, opts} = Keyword.pop(opts, :for_server, [])
    {name, opts} = Keyword.pop(opts, :name, __MODULE__)
    opts = Keyword.put(opts, :name, name)
    GenServer.start_link(__MODULE__, {for_server, %{}}, opts)
  end

  def push(server, item),
    do: GenServer.cast(server, {:push, item})

  def pop(server),
    do: GenServer.call(server, :pop)

  # implementation

  # general continuation mechanism:
  # start_continue/3 and do_continue/3
  #
  # continued via handle_cast({:cont, ref, value}, state)
  #
  defp start_continue(cont, ref, fun),
    do: Map.put(cont, ref, fun)

  defp do_continue(cont, ref, value) do
    case Map.fetch(cont, ref) do # find continuation function
      {:ok, fun} ->
        fun.(value)                  # variant: return {:cont, ref, fun} to continue further 
        Map.delete(cont, ref)
      _ ->
        cont
    end
  end

  # start "pop" that needs to be continued later
  #
  defp start_pop(from, {for_server, cont}) do
    ref = Stack.pop(for_server, :cont)        # cast asynchronous "pop" request
    fun = make_reply_to(from)
    new_cont = start_continue(cont, ref, fun) # store a continuation function
    {for_server, new_cont}
  end

  defp make_reply_to(to) do
    fn item ->
      GenServer.reply(to, item)
    end
  end

  # callbacks

  @impl GenServer
  def init(args),
    do: {:ok, args}

  @impl GenServer
  def handle_call(:pop, from, state),
    do: {:noreply, start_pop(from, state)} # i.e. don't finish "pop" call; wait until _item_ is cast back

  @impl GenServer
  def handle_cast({:push, item}, {for_server, _} = state) do
    Stack.push(for_server, item) # forward item to stack "push"
    {:noreply, state}
  end
  def handle_cast({:cont, ref, value},{for_server, cont}) do # i.e. process via continuation function
    state = {for_server, do_continue(cont, ref, value)}
    {:noreply, state}
  end

end
# file: cast/lib/stack.ex
# stack server with asynchronous "pop"
#
defmodule Stack do
  use GenServer

  # client

  def start_link(opts) do
    {args, opts} = Keyword.pop(opts, :args, [])
    {name, opts} = Keyword.pop(opts, :name, __MODULE__)
    opts = Keyword.put(opts, :name, name)
    GenServer.start_link(__MODULE__, args, opts)
  end

  def push(server, item),
    do: GenServer.cast(server, {:push, item})

  def pop(server, as \\ :popped_item) do
    ref = make_ref()    # create a reference as a correlation identifier
    GenServer.cast(server, {:pop, self(), as, ref})
    ref
  end

  # implementation

  def pop_to([item | rest], to, as, ref) do
    Process.send_after(self(), {:cast_to, to, {as, ref, item}}, 500) # send head item in half a second
    rest
  end

  # callbacks

  @impl GenServer
  def init(stack),
    do: {:ok, stack}

  @impl GenServer
  def handle_cast({:pop, to, as, ref}, state),
    do: {:noreply, pop_to(state, to, as, ref)}  # Part 1 of asynchronous "pop": pop the top and prepare to send it later.
  def handle_cast({:push, item}, state),
    do: {:noreply, [item | state]}              # push new item on the stack

  @impl GenServer
  def handle_info({:cast_to, to, msg}, state) do # process send_after message
    GenServer.cast(to, msg)                      # Part 2 of asynchronous "pop": send popped value back to client.
    {:noreply, state}
  end

end

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews