{"action":"created","comment":{"url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/24556416","id":24556416,"diff_hunk":"@@ -79,75 +72,119 @@ defmodule Phoenix.Transports.LongPoller do\n   \"\"\"\n   def start_session(conn) do\n     router = router_module(conn)\n-    child  = [router, timeout_window_ms(conn)]\n-    {:ok, server_pid} = Supervisor.start_child(LongPoller.Supervisor, child)\n-    conn = put_session_with_salt(conn, server_pid)\n+    priv_topic =\n+      \"phx:lp:\"\n+      |> Kernel.<>(Base.encode64(:crypto.strong_rand_bytes(16)))\n+      |> Kernel.<>(:os.timestamp() |> Tuple.to_list |> Enum.join(\"\"))\n \n-    {conn, server_pid}\n-  end\n+    child = [router, timeout_window_ms(conn), priv_topic, pubsub_server(conn)]\n+    {:ok, server_pid} = Supervisor.start_child(LongPoller.Supervisor, child)\n \n-  # Serialized longpoll server pid into session and harden with random salt\n-  defp put_session_with_salt(conn, server_pid) do\n-    key = session_key(conn)\n-    conn\n-    |> put_session(key, :erlang.term_to_binary(server_pid))\n-    |> put_session(\"#{key}_salt\", :crypto.strong_rand_bytes(16) |> Base.encode64)\n+    {conn, priv_topic, sign(conn, priv_topic), server_pid}\n   end\n \n   @doc \"\"\"\n   Finds the `Phoenix.LongPoller.Server` server from the session\n   \"\"\"\n   def resume_session(conn) do\n-    case longpoll_pid(conn) do\n-      {:ok, pid}            -> {:ok, conn, pid}\n-      :nopid                -> {:error, conn, :terminated}\n+    case verify_longpoll_topic(conn) do\n+      {:ok, priv_topic}     -> {:ok, conn, priv_topic}\n+      :notopic              -> {:error, conn, :terminated}\n       {:error, :terminated} -> {:error, conn, :terminated}\n     end\n   end\n \n   @doc \"\"\"\n-  Retrieves the serialized `Phoenix.LongPoller.Server` pid from the session\n+  Retrieves the serialized `Phoenix.LongPoller.Server` pid from the encrypted token\n   \"\"\"\n-  def longpoll_pid(conn) do\n-    case get_session(conn, session_key(conn)) do\n-      nil -> :nopid\n-      bin ->\n-        pid = :erlang.binary_to_term(bin)\n-        if Process.alive?(pid) do\n-          {:ok, pid}\n-        else\n-          {:error, :terminated}\n+  def verify_longpoll_topic(%Plug.Conn{params: %{\"token\" => token, \"sig\" => sig}} = conn) do\n+    case verify(conn, token, sig) do\n+      {:ok, priv_topic} ->\n+        ref = :erlang.make_ref()\n+        :ok = subscribe(conn, priv_topic)\n+        :ok = broadcast_from(conn, priv_topic, {:subscribe, ref})\n+        receive do\n+          {:ok, :subscribe, ^ref} -> {:ok, priv_topic}\n+        after\n+          pubsub_timeout_ms(conn)  -> {:error, :terminated}\n         end\n+\n+      _ -> :notopic\n     end\n   end\n+  def verify_longpoll_topic(_conn), do: :notopic\n \n   @doc \"\"\"\n   Ack's a list of `%Phoenix.Socket.Messages{}`'s back to the `Phoenix.LongPoller.Server`\n \n   To be called after buffered messages have been relayed to client\n   \"\"\"\n-  def ack(server_pid, messages) do\n-    :ok = GenServer.call(server_pid, {:ack, messages})\n-  end\n-\n-  defp session_key(conn), do: \"#{router_module(conn)}_longpoll_pid\"\n-\n-  @doc \"\"\"\n-  Sets the active listener process. Called by polling `conn`\n-  \"\"\"\n-  def set_active_listener(server_pid, listener_pid) do\n-    GenServer.call(server_pid, {:set_active_listener, listener_pid})\n+  def ack(conn, priv_topic, messages) do\n+    ref = :erlang.make_ref()\n+    :ok = broadcast_from(conn, priv_topic, {:ack, messages, ref})","path":"lib/phoenix/transports/long_poller.ex","position":null,"original_position":177,"commit_id":"6c0e05400c5f652415542578fccdbb9be2c7ebf1","original_commit_id":"072552410468b9ebd1fa415ee58c25b2eca09923","user":{"login":"chrismccord","id":576796,"avatar_url":"https://avatars.githubusercontent.com/u/576796?v=3","gravatar_id":"","url":"https://api.github.com/users/chrismccord","html_url":"https://github.com/chrismccord","followers_url":"https://api.github.com/users/chrismccord/followers","following_url":"https://api.github.com/users/chrismccord/following{/other_user}","gists_url":"https://api.github.com/users/chrismccord/gists{/gist_id}","starred_url":"https://api.github.com/users/chrismccord/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrismccord/subscriptions","organizations_url":"https://api.github.com/users/chrismccord/orgs","repos_url":"https://api.github.com/users/chrismccord/repos","events_url":"https://api.github.com/users/chrismccord/events{/privacy}","received_events_url":"https://api.github.com/users/chrismccord/received_events","type":"User","site_admin":false},"body":"I made the message buffer a list of {ref, %Message{}}'s and now I only ack the refs. I don't think we can guarantee ordering so just acking the length won't work. acking the refs avoids most of the copying.","created_at":"2015-02-12T03:38:40Z","updated_at":"2015-02-12T03:38:40Z","html_url":"https://github.com/phoenixframework/phoenix/pull/594#discussion_r24556416","pull_request_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594","_links":{"self":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/24556416"},"html":{"href":"https://github.com/phoenixframework/phoenix/pull/594#discussion_r24556416"},"pull_request":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594"}}},"pull_request":{"url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594","id":27613419,"html_url":"https://github.com/phoenixframework/phoenix/pull/594","diff_url":"https://github.com/phoenixframework/phoenix/pull/594.diff","patch_url":"https://github.com/phoenixframework/phoenix/pull/594.patch","issue_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/594","number":594,"state":"open","locked":false,"title":"PubSub Adapters, PG2, Redis","user":{"login":"chrismccord","id":576796,"avatar_url":"https://avatars.githubusercontent.com/u/576796?v=3","gravatar_id":"","url":"https://api.github.com/users/chrismccord","html_url":"https://github.com/chrismccord","followers_url":"https://api.github.com/users/chrismccord/followers","following_url":"https://api.github.com/users/chrismccord/following{/other_user}","gists_url":"https://api.github.com/users/chrismccord/gists{/gist_id}","starred_url":"https://api.github.com/users/chrismccord/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chrismccord/subscriptions","organizations_url":"https://api.github.com/users/chrismccord/orgs","repos_url":"https://api.github.com/users/chrismccord/repos","events_url":"https://api.github.com/users/chrismccord/events{/privacy}","received_events_url":"https://api.github.com/users/chrismccord/received_events","type":"User","site_admin":false},"body":"This is a working, starting point for PubSub adapters with redis support. I extracted our pg2 setup into a `PubSub.Adapter` behavior and added a `PubSub.RedisAdapter`. I initially tracked redis subscriber state with a HashDict of HashSet approach, but after I started writing a bunch of pid monitoring code, I realized *local* pg2 works perfectly in conjunction with the redis adapter. So I'm using redis to broadcast multi-node an local pg2 to track subscribers.\r\n\r\nNotes:\r\n\r\n  * I could use some input on the supervision strategy for `Phoenix.PubSub.RedisSupervisor`. I set a giant `max_restarts` for now, otherwise, `redis-server` going down nukes the entire app. // @fishcakez\r\n  * Is the `PubSub.Adapter` behavior well specified? We started with just global pg2, but the API worked just as well for redis. Is it flexible enough for other generalized pubsub adapters?\r\n  * Should we rip out the redis adapter into a `phoenix-pubsub-redis` project now, or wait?\r\n\r\n`redis-server` must be running for specs to pass, but all green locally.\r\n\r\nIt still needs some work, but all feedback appreciated. Thanks!\r\n\r\n\r\nTODO:\r\n  * [x] Add custom pg2'eque subscriber processes for local subscriptions. Move to single global pg2 group for broadcasting b/w nodes. Basically, we want to make our PG2Adapter look more like RedisAdapter.\r\n  * [x] New local subscriber processes can be responsible for GC of their topic\r\n  * [x] Remove pg2 from RedisAdapter entirely with new local subscriber tracking process\r\n  * [x] Make pubsub API process based. `Phoenix.PubSub.broadcast(:my_process_name, \"foo\", \"bar\")`\r\n  * [x] Remove configuration based pubsub module\r\n  * [x] Pool redis clients for broadcasting to redis (single connection remains for phx subscriber)\r\n  * [x] Decide on Channel.broadcast API from outside channels\r\n  * [x] Add MFA style returns from PubSub adapters to offload work back onto the `PubSub` client API","created_at":"2015-01-19T15:15:59Z","updated_at":"2015-02-12T03:38:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"64c65324d40bca6344d3accb8eb97a445093077a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594/commits","review_comments_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594/comments","review_comment_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/{number}","comments_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/594/comments","statuses_url":"https://api.github.com/repos/phoenixframework/phoenix/statuses/6c0e05400c5f652415542578fccdbb9be2c7ebf1","head":{"label":"phoenixframework:cm-pubsub-adapters","ref":"cm-pubsub-adapters","sha":"6c0e05400c5f652415542578fccdbb9be2c7ebf1","user":{"login":"phoenixframework","id":6510388,"avatar_url":"https://avatars.githubusercontent.com/u/6510388?v=3","gravatar_id":"","url":"https://api.github.com/users/phoenixframework","html_url":"https://github.com/phoenixframework","followers_url":"https://api.github.com/users/phoenixframework/followers","following_url":"https://api.github.com/users/phoenixframework/following{/other_user}","gists_url":"https://api.github.com/users/phoenixframework/gists{/gist_id}","starred_url":"https://api.github.com/users/phoenixframework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phoenixframework/subscriptions","organizations_url":"https://api.github.com/users/phoenixframework/orgs","repos_url":"https://api.github.com/users/phoenixframework/repos","events_url":"https://api.github.com/users/phoenixframework/events{/privacy}","received_events_url":"https://api.github.com/users/phoenixframework/received_events","type":"Organization","site_admin":false},"repo":{"id":16072585,"name":"phoenix","full_name":"phoenixframework/phoenix","owner":{"login":"phoenixframework","id":6510388,"avatar_url":"https://avatars.githubusercontent.com/u/6510388?v=3","gravatar_id":"","url":"https://api.github.com/users/phoenixframework","html_url":"https://github.com/phoenixframework","followers_url":"https://api.github.com/users/phoenixframework/followers","following_url":"https://api.github.com/users/phoenixframework/following{/other_user}","gists_url":"https://api.github.com/users/phoenixframework/gists{/gist_id}","starred_url":"https://api.github.com/users/phoenixframework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phoenixframework/subscriptions","organizations_url":"https://api.github.com/users/phoenixframework/orgs","repos_url":"https://api.github.com/users/phoenixframework/repos","events_url":"https://api.github.com/users/phoenixframework/events{/privacy}","received_events_url":"https://api.github.com/users/phoenixframework/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/phoenixframework/phoenix","description":"A highly connected web framework for the Elixir programming language","fork":false,"url":"https://api.github.com/repos/phoenixframework/phoenix","forks_url":"https://api.github.com/repos/phoenixframework/phoenix/forks","keys_url":"https://api.github.com/repos/phoenixframework/phoenix/keys{/key_id}","collaborators_url":"https://api.github.com/repos/phoenixframework/phoenix/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/phoenixframework/phoenix/teams","hooks_url":"https://api.github.com/repos/phoenixframework/phoenix/hooks","issue_events_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/events{/number}","events_url":"https://api.github.com/repos/phoenixframework/phoenix/events","assignees_url":"https://api.github.com/repos/phoenixframework/phoenix/assignees{/user}","branches_url":"https://api.github.com/repos/phoenixframework/phoenix/branches{/branch}","tags_url":"https://api.github.com/repos/phoenixframework/phoenix/tags","blobs_url":"https://api.github.com/repos/phoenixframework/phoenix/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/phoenixframework/phoenix/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/phoenixframework/phoenix/git/refs{/sha}","trees_url":"https://api.github.com/repos/phoenixframework/phoenix/git/trees{/sha}","statuses_url":"https://api.github.com/repos/phoenixframework/phoenix/statuses/{sha}","languages_url":"https://api.github.com/repos/phoenixframework/phoenix/languages","stargazers_url":"https://api.github.com/repos/phoenixframework/phoenix/stargazers","contributors_url":"https://api.github.com/repos/phoenixframework/phoenix/contributors","subscribers_url":"https://api.github.com/repos/phoenixframework/phoenix/subscribers","subscription_url":"https://api.github.com/repos/phoenixframework/phoenix/subscription","commits_url":"https://api.github.com/repos/phoenixframework/phoenix/commits{/sha}","git_commits_url":"https://api.github.com/repos/phoenixframework/phoenix/git/commits{/sha}","comments_url":"https://api.github.com/repos/phoenixframework/phoenix/comments{/number}","issue_comment_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/comments/{number}","contents_url":"https://api.github.com/repos/phoenixframework/phoenix/contents/{+path}","compare_url":"https://api.github.com/repos/phoenixframework/phoenix/compare/{base}...{head}","merges_url":"https://api.github.com/repos/phoenixframework/phoenix/merges","archive_url":"https://api.github.com/repos/phoenixframework/phoenix/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/phoenixframework/phoenix/downloads","issues_url":"https://api.github.com/repos/phoenixframework/phoenix/issues{/number}","pulls_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls{/number}","milestones_url":"https://api.github.com/repos/phoenixframework/phoenix/milestones{/number}","notifications_url":"https://api.github.com/repos/phoenixframework/phoenix/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/phoenixframework/phoenix/labels{/name}","releases_url":"https://api.github.com/repos/phoenixframework/phoenix/releases{/id}","created_at":"2014-01-20T14:14:11Z","updated_at":"2015-02-11T20:08:04Z","pushed_at":"2015-02-12T03:35:44Z","git_url":"git://github.com/phoenixframework/phoenix.git","ssh_url":"git@github.com:phoenixframework/phoenix.git","clone_url":"https://github.com/phoenixframework/phoenix.git","svn_url":"https://github.com/phoenixframework/phoenix","homepage":"http://www.phoenixframework.org","size":6537,"stargazers_count":1949,"watchers_count":1949,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":182,"mirror_url":null,"open_issues_count":22,"forks":182,"open_issues":22,"watchers":1949,"default_branch":"master"}},"base":{"label":"phoenixframework:master","ref":"master","sha":"92e64521fcc660a0edaf32097a4855d37438b2db","user":{"login":"phoenixframework","id":6510388,"avatar_url":"https://avatars.githubusercontent.com/u/6510388?v=3","gravatar_id":"","url":"https://api.github.com/users/phoenixframework","html_url":"https://github.com/phoenixframework","followers_url":"https://api.github.com/users/phoenixframework/followers","following_url":"https://api.github.com/users/phoenixframework/following{/other_user}","gists_url":"https://api.github.com/users/phoenixframework/gists{/gist_id}","starred_url":"https://api.github.com/users/phoenixframework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phoenixframework/subscriptions","organizations_url":"https://api.github.com/users/phoenixframework/orgs","repos_url":"https://api.github.com/users/phoenixframework/repos","events_url":"https://api.github.com/users/phoenixframework/events{/privacy}","received_events_url":"https://api.github.com/users/phoenixframework/received_events","type":"Organization","site_admin":false},"repo":{"id":16072585,"name":"phoenix","full_name":"phoenixframework/phoenix","owner":{"login":"phoenixframework","id":6510388,"avatar_url":"https://avatars.githubusercontent.com/u/6510388?v=3","gravatar_id":"","url":"https://api.github.com/users/phoenixframework","html_url":"https://github.com/phoenixframework","followers_url":"https://api.github.com/users/phoenixframework/followers","following_url":"https://api.github.com/users/phoenixframework/following{/other_user}","gists_url":"https://api.github.com/users/phoenixframework/gists{/gist_id}","starred_url":"https://api.github.com/users/phoenixframework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phoenixframework/subscriptions","organizations_url":"https://api.github.com/users/phoenixframework/orgs","repos_url":"https://api.github.com/users/phoenixframework/repos","events_url":"https://api.github.com/users/phoenixframework/events{/privacy}","received_events_url":"https://api.github.com/users/phoenixframework/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/phoenixframework/phoenix","description":"A highly connected web framework for the Elixir programming language","fork":false,"url":"https://api.github.com/repos/phoenixframework/phoenix","forks_url":"https://api.github.com/repos/phoenixframework/phoenix/forks","keys_url":"https://api.github.com/repos/phoenixframework/phoenix/keys{/key_id}","collaborators_url":"https://api.github.com/repos/phoenixframework/phoenix/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/phoenixframework/phoenix/teams","hooks_url":"https://api.github.com/repos/phoenixframework/phoenix/hooks","issue_events_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/events{/number}","events_url":"https://api.github.com/repos/phoenixframework/phoenix/events","assignees_url":"https://api.github.com/repos/phoenixframework/phoenix/assignees{/user}","branches_url":"https://api.github.com/repos/phoenixframework/phoenix/branches{/branch}","tags_url":"https://api.github.com/repos/phoenixframework/phoenix/tags","blobs_url":"https://api.github.com/repos/phoenixframework/phoenix/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/phoenixframework/phoenix/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/phoenixframework/phoenix/git/refs{/sha}","trees_url":"https://api.github.com/repos/phoenixframework/phoenix/git/trees{/sha}","statuses_url":"https://api.github.com/repos/phoenixframework/phoenix/statuses/{sha}","languages_url":"https://api.github.com/repos/phoenixframework/phoenix/languages","stargazers_url":"https://api.github.com/repos/phoenixframework/phoenix/stargazers","contributors_url":"https://api.github.com/repos/phoenixframework/phoenix/contributors","subscribers_url":"https://api.github.com/repos/phoenixframework/phoenix/subscribers","subscription_url":"https://api.github.com/repos/phoenixframework/phoenix/subscription","commits_url":"https://api.github.com/repos/phoenixframework/phoenix/commits{/sha}","git_commits_url":"https://api.github.com/repos/phoenixframework/phoenix/git/commits{/sha}","comments_url":"https://api.github.com/repos/phoenixframework/phoenix/comments{/number}","issue_comment_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/comments/{number}","contents_url":"https://api.github.com/repos/phoenixframework/phoenix/contents/{+path}","compare_url":"https://api.github.com/repos/phoenixframework/phoenix/compare/{base}...{head}","merges_url":"https://api.github.com/repos/phoenixframework/phoenix/merges","archive_url":"https://api.github.com/repos/phoenixframework/phoenix/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/phoenixframework/phoenix/downloads","issues_url":"https://api.github.com/repos/phoenixframework/phoenix/issues{/number}","pulls_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls{/number}","milestones_url":"https://api.github.com/repos/phoenixframework/phoenix/milestones{/number}","notifications_url":"https://api.github.com/repos/phoenixframework/phoenix/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/phoenixframework/phoenix/labels{/name}","releases_url":"https://api.github.com/repos/phoenixframework/phoenix/releases{/id}","created_at":"2014-01-20T14:14:11Z","updated_at":"2015-02-11T20:08:04Z","pushed_at":"2015-02-12T03:35:44Z","git_url":"git://github.com/phoenixframework/phoenix.git","ssh_url":"git@github.com:phoenixframework/phoenix.git","clone_url":"https://github.com/phoenixframework/phoenix.git","svn_url":"https://github.com/phoenixframework/phoenix","homepage":"http://www.phoenixframework.org","size":6537,"stargazers_count":1949,"watchers_count":1949,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":182,"mirror_url":null,"open_issues_count":22,"forks":182,"open_issues":22,"watchers":1949,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594"},"html":{"href":"https://github.com/phoenixframework/phoenix/pull/594"},"issue":{"href":"https://api.github.com/repos/phoenixframework/phoenix/issues/594"},"comments":{"href":"https://api.github.com/repos/phoenixframework/phoenix/issues/594/comments"},"review_comments":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594/comments"},"review_comment":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594/commits"},"statuses":{"href":"https://api.github.com/repos/phoenixframework/phoenix/statuses/6c0e05400c5f652415542578fccdbb9be2c7ebf1"}}}}