I'm getting a Poison.SyntaxError that's crashing my app

Hello, I have a Telegram Bot which is running in a server and several days after deployment I’m getting an error that’s crashing my application but I don’t really know what can be the issue, It has happened like 3-4 times but it always happens after some days running, let’s say, 3-5 days. I’m using nadia as the wrapper for Telegram, this is the stacktrace:

[error] Task #PID<0.477.0> started from Euridime.Supervisor terminating
** (Poison.SyntaxError) Unexpected token at position 0: <
    (poison) lib/poison/parser.ex:57: Poison.Parser.parse!/2
    (poison) lib/poison.ex:83: Poison.decode!/2
    (nadia) lib/nadia/api.ex:40: Nadia.API.decode_response/1
    (nadia) lib/nadia/api.ex:30: Nadia.API.process_response/2
    (euridime) lib/euridime/telegram/task.ex:7: Euridime.Task.pull_updates/1
    (elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Function: &Euridime.Task.pull_updates/0
    Args: []

Same error for 3 different PIDs, the function that pulls the updates looks like this:

def pull_updates(offset \\ -1) do
  case Nadia.get_updates(offset: offset) do
    {:ok, updates} when length(updates) > 0 ->
      Dispatcher.dispatch(updates)
      :timer.sleep(1000)
      pull_updates(List.last(updates).update_id + 1)
    _ ->
      :timer.sleep(500)
      pull_updates(offset)
  end
end

And the relevant part of my supervision tree looks like this:

def start(_type, _args) do
    import Supervisor.Spec

    # Define workers and child supervisors to be supervised
    children = [
      # Start the Ecto repository
      supervisor(Euridime.Repo, []),
      # Start the endpoint when the application starts
      supervisor(Euridime.Web.Endpoint, []),

      worker(Task, [Euridime.Task, :pull_updates, []], id: :pull_updates),
      worker(Euridime.DETS, []),
      worker(Euridime.Emailer, []),
    ]
    ...

I’m using Phoenix alongside the bot.

And the function that pulls the updates from the wrapper looks like this if it matters.

def request(method, options \\ [], file_field \\ nil) do
    method
    |> build_url
    |> HTTPoison.post(build_request(options, file_field), [], build_options(options))
    |> process_response(method)
end
...
defp process_response(response, method) do
    case decode_response(response) do
      {:ok, true} -> :ok
      {:ok, result} -> {:ok, Nadia.Parser.parse_result(result, method)}
      %{ok: false, description: description} -> {:error, %Error{reason: description}}
      {:error, %HTTPoison.Error{reason: reason}} -> {:error, %Error{reason: reason}}
    end
end
...
defp decode_response(response) do
    with {:ok, %HTTPoison.Response{body: body}} <- response,
          %{result: result} <- Poison.decode!(body, keys: :atoms),
    do: {:ok, result}
end

Now my question is, why is it crashing the whole app, isn’t it meant to just be restarted? Or it goes deeper than that? Probably I should change the way I’m pulling updates from Telegram? I don’t really know what may be causing this problem.

Seems like service isn’t returning valid JSON. Since it’s saying that the first character is ‘<’, it’s a pretty safe bet that the body is HTML (or perhaps XML).

It’s a shame that nadia uses decode!/2 and doesn’t do content-type checking (assuming the content type isn’t wrong).

I don’t have a great suggestion except

1 - Creating an issue with Nadia asking them to be a little more defensive about how they code:

2 - Wrapping your call to nadia in a try / rescue.

As an option to 1, you could fork the project, point your mix file to your fork, and make it more defensive yourself (and logging what the body IS because I’ll bet you it’s an informative error message).

I’m wondering if there maybe some architectural problem in one of the related apps (@Aguxez bot, eurodime or nadia).

Especially [error] Task #PID<0.477.0> started from Euridime.Supervisor terminating… It should get restarted!

Exactly but my concern is why it’s crashing the whole application.

I already submit en issue regarding another topic and got no response so I’m guessing the project is no that active anymore. the thing is that it happens a couple of days after it’s been running so I could try option one, forking the project and logging the response and wait until it crash again…

Yes, Euridime is the bot and obviously it’s the response I’m getting from Telegram through nadia, because that’s the only time I “interact” with Telegram besides sending messages because after I receive the message I just pattern match on them and It’s another unrelated task, the thing is that I don’t know why it’s crashing completely, I might try what @kseg suggested.

Probably it’s not necessary but for a follow up on this, I just ended up changing the decode! to decode from the library and built a GenServer on the Task that pulls the updates to be sure it’s going to be restarted at least, we’ll have to wait to see. Ended up with this:

  defp decode_response(response) do
    with {:ok, %HTTPoison.Response{body: body}} <- response,
         {:ok, %{result: result}} <- Poison.decode(body, keys: :atoms) do # Changed this

      {:ok, result}
    end
  end

Instead of what’s above on my older replies, this is from the library, even tho I’m still concerned about the keys option, I tried to remove it but it result in lots of errors which would require to rewrite a lot of the code on the library it seems… and changed the pull_updates/1 function to:

defmodule Euridime.Task do
  @moduledoc false

  use GenServer

  alias Euridime.Dispatcher

  @mod __MODULE__

  def start_link,
    do: GenServer.start_link(@mod, [], name: @mod)

  # Server
  def init(state) do
    Process.send(self(), {:pull_updates, -1}, [:nosuspend])
    {:ok, state}
  end

  def handle_info({:pull_updates, offset}, state) do
    case Nadia.get_updates(offset: offset) do
      {:ok, updates} when length(updates) > 0 ->
        Dispatcher.dispatch(updates)
        Process.send_after(
          self(), {:pull_updates, List.last(updates).update_id + 1}, 1000
        )
      _ ->
        Process.send_after(self(), {:pull_updates, offset}, 1000)
    end

    {:noreply, state}
end

I still have a question about atoms and I hope someone can answer it, if an atom is already created and you write the atom again, is it created again? For example, in the response from the Poison.decode, there are about 10 atoms on each response, for each response will 10 new atoms be created? The BEAM doesn’t “re-use” already created atoms, right? That’s my concern with the keys: :atoms option…

1 Like

“re-using” them is the whole purpose of them not getting GC’d.

If you write :ok once, it will be the same forever and it will never get re-created.

1 Like

Just think of Atoms as Flyweight strings, because that is precisely what they are internally. ^.^

1 Like

And exactly this thinking would explain multiple allocations since otherwise equal strings/binaries can be multiple times in memory