I put together a tiny library and I would love any feedback on it. It’s my first public elixir repo, though I’ve been using Elixir for very small things for about a year. It is a work in progress, there are some todos listed in the README.
I find I often have a need to schedule things at absolute times. We have great primitives for relative timers, like
Process.send_after, but nothing robust that I’ve seen for absolute times.
send_after does accept an
abs option, but this requires you to provide a monotonic time value and is not time warp safe. I thought it would be handy to have a primitive that would handle time warps correctly, guaranteeing that regardless of what changes may happen to the OS clock, forward or backward, your message will be sent at the requested time.
Aside from general feedback on the idea and my code, I would love to hear any thoughts on the following questions:
In multi time warp mode, a process is needed to monitor for time warps and adjust the timers. My first implementation used a process per timer. I refactored to what you see now - one server process that keeps a list of timers in memory. What are the pros and cons of each approach? My guess would be that one process will bottleneck in the case of lots of requests for new timers or lots of timers that expire at the same time, while multiple processes will bottleneck during a time warp, as erlang must send a time warp notification to many subscribed processes.
As far as I know, the time warp mode can’t change while the VM is running. I check this at startup and only start the server if it is needed. However, a supervisor is started regardless. Is there any way to check this at runtime and avoid starting a supervision tree at all if it’s not needed? Or perhaps even based on an option set by the library user?
Is this worth posting on Hex? Would others find it useful?
Is this something that might make sense to live in the
Process module next to
I would appreciate any thoughts or feedback.
Heh, the concept is quite cool and useful.
@jlevy: Here is what I can talk after first look:
dest (destination) it’s too general name - here a better name could be
pid (process identificator)
msg (message) for me it should be bitstring or struct, so I suggest to change name to
data, because here we need generic name -you don’t know what developer would like to send
time - here we are not passing any time, but
value for specified
time_unit could be better?)
If you are creating tool then think if it’s really needed. I can’t say that your work is only waste of time, but at least for now you could use
crontab and create mix tasks that
crontab will run. I believe it will be enough for 99% cases. If you found edge-case when
crontab limits you then please let me know about it.
Of course it’s only my own opinion.
Thanks @Eiji. Regarding the argument names, I tried to be as consistent as possible with Process.send_after/4, but I agree they can be improved. As far as the use case,
crontab or something like
quantum is good when you have recurring tasks that are known at deployment time. The use cases I have are a little different. Off the top of my head:
I have a
GenServer that maintains a connection to an external REST API. I authenticate and get back a token with an expiration time which is stored in state. The server needs to send itself a message to renew the token a few minutes before it expires. That needs to happen at an absolute UTC time. Using
send_after here doesn’t work. What happens if the OS clock is changed, revealing that it was wrong when the timer was set? Handing time warps in every such situation is repetitive and error prone.
Sometimes the task or the time it should be performed is decided by the user. In other words, the jobs and schedules are part of the application data and can’t be defined in the OS (crontab) or config (quantum). For example, a user may trigger something like “send this message tomorrow at 5pm.”
I was just wondering wouldn’t it be better to use SNTP.?
@jlevy: you can still do it with
Let’s explain it with your example:
user may trigger something like "send this message tomorrow at 5pm.".
Possible solution: You have a
GenServer that asynchronously saves messages with specified
date in database. After that you are generating
crontab entry and update its configuration.
crontab calls task which fetches required data from database and send it.
The 1% edge-cases are for example plug-in system when code is loaded dynamically and therefore there are no mix tasks to run. Of course someone could find a good hack for it, but we should keep things as simple as possible, so in most cases
crontab should be enough.
@Eiji I agree we should keep things as simple as possible, but I think we have different ideas of what that means.
To me it seems simpler to keep all of the logic contained within the BEAM vm and not call out to the OS, even if it means re-implementing some logic myself. This way the application remains cross platform with no assumptions about what facilities are available in the host OS. I also use releases in production, so Mix is generally not available. Of course there are ways of running custom tasks in a release, but that adds another layer of complexity.
Crontab is a valid approach, but not one that I prefer.
@Schultzer: Hopefully I’m interpreting your question correctly. Something along the lines of: “The server should be configured with NTP to keep it reasonably close to atomic time. The only OS time warps should be for leap seconds or to correct drift, which should never be more than a second or two, barring a prolonged network failure (which would cause bigger problems anyway). A margin of error of a few seconds don’t seem like it would impact the use cases you listed.”
This is a really good point, and one that I probably didn’t spend enough time thinking about. I think I was so concerned with how that I didn’t spend enough time focusing on why. There are probably still cases where to-the-second accuracy is important, but few.
Since the original post, I’ve also come to realize that I was also overusing
send_after as my only means of triggering a later action in a
GenServer. This afternoon I discovered that you can return a
timeout value in the
GenServer callbacks, which makes some of my uses cases much simpler to implement. When the logic is implemented that way, it also makes subscribing to and handing time warp messages pretty trivial.
@jlevy I was thinking if using NTP in your app would solve the problem. I made a library to solve an absolute time issue with client-server communication SNTP.