Dealing with drift

Both of the techniques used so far suffer from drift in time where the execution time of the work to be done shifts the start of the next.

Say we wanted to do a piece of work every 1 second but the work we are doing takes 100 milliseconds to complete. After 10 seconds elapsed we would have only done the work 9 times as each execution was shifted.

Most of the time the simplest thing you can do is just spawn a separate process to perform the work and let the current process get back to scheduling the next piece of work.

Let’s take out GenServer example from earlier

def handle_info(:work, state) do

# do important stuff

IO.puts "Important stuff in progress..."

schedule_work()

{:noreply, state}

end

We can simply do this instead

def handle_info(:work, state) do

spawn_link(&do_work/0)

schedule_work()

{:noreply, state}

end defp do_work() do

# do important stuff

IO.puts "Important stuff in progress..."

end

By using spawn_link/1 we keep the same behavior as before in terms of exit signal handling and what happens if the code that does the work crashes.

Spawning processes is super fast in Elixir so this should all but mitigate your drift. For fun I decided to test out how long the spawn_link/1 takes in the above example and it was about 10 microseconds on average 😍 Quick back of envelope math would suggest a drift of 1 second after 100,000 executions.