I have a cron that uses IO.puts to ouput random debug strings. It has never thrown an error when I manually run it, but randomly throws errors when run un-attended (to be clear, not EVERY time)
Seems like there is no put_chars/3, only put_chars/1 and put_chars/2. Or maybe there is but it’s not public. Anyway, when called with a debugger, I only see put_chars/2.
You’re right, there is no put_chars/3 from what I can tell, and when I run w/ the debugger I get the same call to put_chars/2 as you do. But if I search for :io.put_chars(:standard_io, :unicode, I end up seeing results for other code snippets that end up calling it.
I’m wondering if it’s better to call :io.put_chars(:standard_io, ["#{prefix}Tracking #{Enum.count(orders)} orders...", 10]) instead of using IO.puts
Also, one other thing that I forgot to mention is that I’m running this through an RPC call, on a release built with distillery.
Hmm an rpc call forwards the stdin/stdout, however it returns when the call returns, so if it can return before the function actually finishes work (messages for example, and yes putting to IO does involve sending a message) then you get a race condition, so I can see why it would do that assuming I don’t have something grossly wrong here. ^.^;
A simple way of check which exports a module, both erlang and elixir, has is to use the :c module. :c.m/1 lists info about a specific module while :c.m/0 lists all the modules currently loaded and from which file they were loaded. For example
There a a lot of modules loaded when elixir is running. The :c module contains all the Erlang shell built-in commands. Some more I can recommend are :c.i/0, :c.regs/0 and :c.help/0.
Well that IO.puts call happens right before calling a third-party API 42 times (1 for each order). So I think it’s unlikely that the script is finishing before IO.puts is run. Unless I’m misunderstanding what you’re saying.
Doesn’t it seem stranger that it’s calling an erlang function that doesn’t exist?
Message ordering is only ensured between any 2 processes, once you start talking to more those others can interleave or halt or all kinds of other things.
I’m actually thinking it is apply-calling something based on a now-missing process, that would explain this missing function call.
put_chars/1/2 are in the end just wrappers around o_request/3, which in case of an error manipulates the stacktrace and injects a function call that never has happened.
Take a look at especiall line 66, where it drops the appearance of o_request/3 in the current stacktrace and then a line later injects something else.
I see, thanks for clearing that up! So in this case, I don’t want IO.puts/1 to raise if :standard_io is gone. I’m fine with it being ignored and moving on. Does this seem reasonable to put in a custom IO module?
def puts(device \\ :standard_io, str) do
:io.request(device, {:put_chars, :unicode, [str, ?\n]})
end
Well, io:request/2 is private, so that won’t work…
Anyway, it would just circumvent the mangling of the stack trace. But the registry would still complain about not beeing able to reach the process behind :standard_io.
Perhaps you can catch that one and surpress the error message that way.
I do think though, it were much cleaner not to try to read and write to stdin/stdout in an environment that has none…
Yes, exported, but still private. It is not documented in the io-manual, so I do consider it as not existing.
Trying to use :io.request/2 is even more hacky than the proposed Process.sleep/1, as it relies on an implementation detail and might break with any random update of Erlang.
Ah I see, thanks for the insight. I think I’ll just wrap IO.puts/2 in a try/rescue. I have a number of crons this is happening with, so I’d rather just ignore the error than prolonging their run time with Process.sleep/1
Thanks to everyone for the help here! I’ve learned some new tricks from all of you!