Skip to content

Commit

Permalink
FIX: Gateway manager infinity loop
Browse files Browse the repository at this point in the history
This fixes a very crucial error which can causes the bot to become
unresponsive.

Start multiple instances of the bot with the same token.
Some will get the error message that the session id is invalid: OP 9

As a result the Gateway will be terminated and restarted.
In some cases this will work as expected but in others it ends in an
endless loop:

The gateway will call `Manager.request_url().()` which previously
updated the url_reset timer by 5 seconds even if the process who
requested the url has not been successful. The returned function
sleeps for 1sec before requesting again. But as it only slept 1sec
the url_reset is still higher than now(). But now we increase the
url_reset again by 5sec. This reminds me of Achilles and the tortoise :)
It will never be faster and pass the increasing 5sec of the url_reset.

First fix: We should only increase the url_reset by 5sec if the process
successfully acquired the url.

Second fix: `Manager.request_url().()` will either return a string
or a protocol again. So we have to check for this also as we may pass
a function to :websocket_client.start_link which will result in an
error and the process terminates and restarts. We can avoid that.
  • Loading branch information
Zarathustra2 committed Jan 31, 2021
1 parent e56bbe9 commit 176e2ea
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 14 deletions.
9 changes: 8 additions & 1 deletion lib/Discord/Gateway/gateway.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ defmodule Alchemy.Discord.Gateway do
def start_link(token, shard) do
:crypto.start()
:ssl.start()

# request_url will return a protocol to execute
url = Manager.request_url().()
# which either returns the url or another protocol
# to execute.
url = Manager.request_url().() |> get_url
Logger.info("Shard #{inspect(shard)} connecting to the gateway")

:websocket_client.start_link(url, __MODULE__, %State{token: token, shard: shard})
end

def get_url(s) when is_binary(s), do: s
def get_url(f), do: get_url(f.())

def init(state) do
{:once, state}
end
Expand Down
31 changes: 18 additions & 13 deletions lib/Discord/Gateway/manager.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,24 @@ defmodule Alchemy.Discord.Gateway.Manager do
now = now()
wait_time = state.url_reset - now

response =
cond do
wait_time <= 0 ->
fn -> state.url end

true ->
fn ->
Process.sleep(wait_time * 1000)
request_url()
end
end

{:reply, response, %{state | url_reset: now + 5}}
cond do
wait_time <= 0 ->
response = fn -> state.url end

# Increase the url_reset time as a process
# has successfull requested the url
{:reply, response, %{state | url_reset: now + 5}}

true ->
response = fn ->
Process.sleep(wait_time * 1000)
request_url()
end

# Don't increate the url_reset as the process
# has not been successfull in requesting the url.
{:reply, response, state}
end
end

def handle_cast({:start_shard, num}, %{shards: shards} = state)
Expand Down

0 comments on commit 176e2ea

Please sign in to comment.