Delaying a Task in elixir

I am trying to show post to the first friend of a person first and other’s after making a delay of 1 mins,For that i am using genserver.

Problem is occurring that,the first friend as well as the other friends are getting the post after 1 min.

Here is my code of genserver:

defmodule Phoenix.SchedulePost do
	use GenServer
  
  def start_link(state) do
    GenServer.start_link __MODULE__,state
  end

  def init(state) do
  	schedule_post(state)
    {:ok, state}
  end
  #handling looby 
  def handle_info(:postSchedule,state) do
    #sending posts to others
    {:noreply,state}
  end
  #scheduling a task
  defp schedule_post(state) do
  	IO.puts "scheduling the task"
    Process.send_after(self(),:postSchedule,1*60*1000)
  end
end 

I am starting genserver process for each post request and sending it to the first friend,
Here is the code:

def handle_in("post:toFrstFrnd", %{"friendId"=>friendId,"body" => body}, socket) do
	newSocket = PhoenixWeb.SocketBucket.get_socket(friendId)
	if  newSocket != nil do
	  push newSocket, "post:toFrstFrnd", %{"friendId": friendId,"body": body}
	end
	Phoenix.SchedulePost.start_link(postId);
	 {:noreply, socket}
end

Help me out,Thank you in advance.

You might like The Erlangelist - To spawn, or not to spawn?. I don’t think it’s a good use case for Genserver.

Also,

  • handle_in is not a Genserver callback, but specific to Phoenix channels
  • websocket are powered by Genserver, there is not need to spawn one
  • :timer.sleep allows delaying an action
  • You can use

socket.endpoint.broadcast!(“user:#{id}”, message, payload)

to send on a specific channel… It could replace following code.

	newSocket = PhoenixWeb.SocketBucket.get_socket(friendId)
	if  newSocket != nil do
	  push newSocket, "post:toFrstFrnd", %{"friendId": friendId,"body": body}
	end

You could probably replace all your code by a single handle_in channel function.

Thank you @kokolegorille for your help,:timer.sleep will make handle_in to wait for the timeout of the timer or it will return after timeout,the delayed action will be performed??.

It will block… but this will not

spawn(fn -> :timer.sleep(1000); notify_others() end)

oh! that’s a great help friend.Thank you.

For note, a while back I’ve made an efficient (at least far more efficient than lots of sleeping tasks) library to handle this:

:slight_smile:

4 Likes

Please review it:
I was trying to push to the first friend
push newSocket, “post:tofrstFrnd”, %{postId: postId,body: body}
since i am making a push call then,it should send the post to the first friend,but the post is coming for first friend after the delay job completed.
#sending it to others by making a delay
spawn(fn -> :timer.sleep(60*1000); PhoenixWeb.Endpoint.broadcast! “timer:lobby”,
“scheduledPost:toFrnd”,%{postId: postId}; end)
But the above timer is sending the posts to all friend including the first after 1 minutes,so it is delaying the task.

That’s a good work my friend,whether the taskAfter job will be async or it will make the handler to wait for the timeout.

1 Like

There is one more thing you have to consider. If you do sleep tasks like that, and then you have a traditional deployment that does stop and start the Erlang VM during the process, your sleeping tasks would be silently killed and cancelled. It might be totally okay to do what you do, but in a scenario where you do multiple deployments per day it can be a show stopper as lots of these tasks would never be executed and your user experience will suffer.

So, if it’s the second case, I would advise storing the jobs somewhere. It does look like exq library already allows you to schedule delayed execution of tasks. I haven’t used it but it does appear to have enqueue_at function that wil ldo what you need: https://hexdocs.pm/exq/Exq.Enqueuer.html#enqueue_at/5

1 Like

do you think exq library will work for my requirement,The post shared by a person first sent to the friend who has introduced into the application and after 1 min shared to all other friends.

Right now i am facing a problem that after making :timer.sleep(timeout),the delay is happening for both the first friend and other friends.So both(first friend and other friends) can able to get the post after 1 min of delay.

I think that is totally separated problem resulting from that you are starting one job to do these two different things. I think you should start two jobs/processes, or even process per friend and give it a delay as a paremeter that they’d use to sleep on to. For the one you want to get notification immediately, you just pass 0.

If you are going to keep your own solution, change the :postSchedule message to be an actual tuple, consisting of {:postSchedule, user_id} and schedule a number of these messages in the queue, with different third send_after parameter to send those out at different dates. This way you are also de-coupling the behavior so if something fails for one user, others would not be affected.

3 Likes

Thank you,it worked.

1 Like

Yeah Tasks are in-system only. If it needs to persist I’d recommend a database or mnesia or dets or something.

Oooo, that’s an idea… I could add a (d)ets backend to TaskAfter as an option, can someone open up an issue for that feature? :slight_smile:

Maybe have pluggable backends via a simple behaviour, hmm…

1 Like