Packages

A timer module with microsecond resolution

Current section

Files

Jump to
micro_timer lib micro_timer.ex
Raw

lib/micro_timer.ex

defmodule MicroTimer do
@moduledoc """
A timer module with microsecond resolution.
"""
@sleep_done :___usleep_done
@type executable :: {module(), atom()} | function()
@doc """
Suspend the current process for the given `timeout` and then returns `:ok`.
`timeout` is the number of microsends to sleep as an integer.
## Examples
iex> MicroTimer.usleep(250)
:ok
"""
@spec µsleep(non_neg_integer()) :: :ok
defdelegate µsleep(timeout), to: __MODULE__, as: :usleep
@spec usleep(non_neg_integer()) :: :ok
def usleep(timeout) when is_integer(timeout) and timeout > 0 do
do_usleep(timeout)
receive do
@sleep_done -> :ok
end
end
def usleep(timeout) when is_integer(timeout) do
:ok
end
@doc """
Invokes the given `executable` after `timeout` microseconds with the list of
arguments `args`.
`executable` can either be the tuple `{Module, :function}`, an anonymous function
or a function capture.
Returns the `pid` of the timer.
See also `cancel_timer/1`.
## Examples
MicroTimer.apply_after(250, {Module. :function}, [])
MicroTimer.apply_after(250, fn a -> a + 1 end, [1])
iex> pid = MicroTimer.apply_after(250, fn arg -> arg end, [1])
iex> is_pid(pid)
true
"""
@spec apply_after(non_neg_integer(), executable, [any]) :: pid()
def apply_after(timeout, executable, args \\ [])
def apply_after(timeout, {module, function}, args)
when is_atom(module) and is_atom(function) do
spawn(fn ->
do_apply_after(timeout, {module, function}, args)
end)
end
def apply_after(time, function, args) when is_function(function) do
spawn(fn ->
do_apply_after(time, function, args)
end)
end
@doc """
Invokes the given `executable` repeatedly every `timeout` microseconds with the list of
arguments `args`.
`executable` can either be the tuple `{Module, :function}`, an anonymous function
or a function capture.
Returns the `pid` of the timer.
See also `cancel_timer/1`.
## Examples
MicroTimer.apply_every(250, {Module. :function}, [])
MicroTimer.apply_every(250, fn a -> a + 1 end, [1])
iex> pid = MicroTimer.apply_every(250, fn arg -> arg end, [1])
iex> is_pid(pid)
true
"""
@spec apply_every(non_neg_integer(), executable, [any]) :: pid()
def apply_every(timeout, executable, args \\ [])
def apply_every(timeout, {module, function}, args)
when is_atom(module) and is_atom(function) do
spawn(fn ->
do_apply_every(timeout, {module, function}, args)
end)
end
def apply_every(timeout, function, args) when is_function(function) do
spawn(fn ->
do_apply_every(timeout, function, args)
end)
end
@doc """
Send `message` to `pid` after `timeout` microseconds.
If `pid` is left empty, the `message` is sent to `self()`.
Returns the `pid` of the timer.
See also `cancel_timer/1`.
## Examples
MicroTimer.send_after(250, :msg)
MicroTimer.send_after(250, "msg", self())
iex> pid = MicroTimer.send_after(250, :msg)
iex> is_pid(pid)
true
"""
@spec send_after(non_neg_integer(), any, pid()) :: pid()
def send_after(timeout, message, pid \\ self()) do
spawn(fn ->
do_apply_after(timeout, fn -> send(pid, message) end, [])
end)
end
@doc """
Send `message` to `pid` every `timeout` microseconds.
If `pid` is left empty, the `message` is sent to `self()`.
Returns the `pid` of the timer.
See also `cancel_timer/1`.
## Examples
MicroTimer.send_every(250, :msg)
MicroTimer.send_every(250, "msg", self())
iex> pid = MicroTimer.send_every(250, :msg)
iex> is_pid(pid)
true
"""
@spec send_every(non_neg_integer(), any, pid()) :: pid()
def send_every(timeout, message, pid \\ self()) do
spawn(fn ->
do_apply_every(timeout, fn -> send(pid, message) end, [])
end)
end
@doc """
Cancel a timer `pid` created by `apply_after/2`, `apply_after/3`, `apply_every/2`
or `apply_every/3`
Always returns `true`
## Examples
timer = MicroTimer.apply_every(250, {Module. :function}, [])
MicroTimer.cancel_timer(timer)
iex> pid = MicroTimer.apply_every(250, fn arg -> arg end, [1])
iex> MicroTimer.cancel_timer(pid)
iex> Process.alive?(pid)
false
"""
@spec cancel_timer(pid()) :: true
def cancel_timer(pid) when is_pid(pid) do
Process.exit(pid, :kill)
end
defp do_usleep(timeout) when timeout > 2_000 do
ms_timeout = div(timeout, 1_000) - 1
{real_sleep_time, _} =
:timer.tc(fn ->
Process.sleep(ms_timeout)
end)
do_usleep(System.monotonic_time(:microsecond), timeout - real_sleep_time)
end
defp do_usleep(timeout) do
do_usleep(System.monotonic_time(:microsecond), timeout)
end
defp do_usleep(start, timeout) do
if System.monotonic_time(:microsecond) - start >= timeout do
send(self(), @sleep_done)
else
do_usleep(start, timeout)
end
end
defp do_apply_after(timeout, {module, function}, args) do
usleep(timeout)
apply(module, function, args)
end
defp do_apply_after(timeout, function, args) do
usleep(timeout)
apply(function, args)
end
defp do_apply_every(timeout, executable, args) do
do_apply_after(timeout, executable, args)
do_apply_every(timeout, executable, args)
end
end