{"action":"created","comment":{"url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/23528894","id":23528894,"diff_hunk":"@@ -0,0 +1,164 @@\n+defmodule Phoenix.PubSub.RedisServer do\n+  use GenServer\n+  require Logger\n+  alias Phoenix.PubSub.RedisServer\n+\n+  @moduledoc \"\"\"\n+  The server for the RedisAdapter\n+\n+  See `Phoenix.PubSub.RedisAdapter` for details and configuration options.\n+  \"\"\"\n+\n+  @derive [Access]\n+  defstruct eredis_sub_pid: nil,\n+            status: :disconnected,\n+            reconnect_attemps: 0,\n+            node_ref: nil,\n+            local_pid: nil,\n+            opts: []\n+\n+  @defaults [host: \"127.0.0.1\", port: 6379, password: \"\"]\n+\n+  @max_connect_attemps 3   # 15s to establish connection\n+  @reconnect_after_ms 5000\n+\n+  @doc \"\"\"\n+  Starts the server\n+\n+  TODO document options\n+  \"\"\"\n+  def start_link(opts) do\n+    GenServer.start_link(__MODULE__, opts, name: Dict.fetch!(opts, :name))\n+  end\n+\n+  @doc \"\"\"\n+  Initializes the server.\n+\n+  An initial connection establishment loop is entered. Once `:eredis_sub`\n+  is started successfully, it handles reconnections automatically, so we\n+  pass off reconnection handling once we find an initial connection.\n+  \"\"\"\n+  def init(opts) do\n+    {:ok, local_pid} = Phoenix.PubSub.Local.start_link()\n+    opts = Dict.merge(@defaults, opts)\n+    opts = Dict.merge(opts, host: String.to_char_list(to_string(opts[:host])),\n+                            password: String.to_char_list(to_string(opts[:password])))\n+\n+    Process.flag(:trap_exit, true)\n+    send(self, :establish_conn)\n+\n+    {:ok, struct(RedisServer, opts: opts,\n+                              local_pid: local_pid,\n+                              node_ref: :erlang.make_ref)}\n+  end\n+\n+\n+\n+  def handle_call({:subscribe, pid, topic}, _from, state) do\n+    {:reply, GenServer.call(state.local_pid, {:subscribe, pid, topic}), state}\n+  end\n+\n+  def handle_call({:unsubscribe, pid, topic}, _from, state) do\n+    {:reply, GenServer.call(state.local_pid, {:unsubscribe, pid, topic}), state}\n+  end\n+\n+  def handle_call({:subscribers, topic}, _from, state) do\n+    {:reply, GenServer.call(state.local_pid, {:subscribers, topic}), state}\n+  end\n+\n+  def handle_call(:list, _from, state) do\n+    {:reply, GenServer.call(state.local_pid, :list), state}\n+  end\n+\n+  def handle_call({:broadcast, from_pid, topic, msg}, _from, state) do\n+    with_conn state, fn state ->\n+      result = :poolboy.transaction :phx_redis_pool, fn worker_pid ->\n+        GenServer.call(worker_pid, {:publish_to_redis, \"phx:#{topic}\",\n+                                   {1, state.node_ref, from_pid, msg}})\n+      end\n+      {:reply, result, state}\n+    end\n+  end\n+\n+  def handle_info({:pmessage, \"phx:*\", \"phx:\" <> topic, binary_msg, _client_pid}, state) do\n+    :poolboy.transaction :phx_redis_pool, fn worker_pid ->","path":"lib/phoenix/pubsub/redis_server.ex","position":84,"original_position":84,"commit_id":"4f0ab9f82a140d41e40240ed10341bcb8dc23121","original_commit_id":"4f0ab9f82a140d41e40240ed10341bcb8dc23121","user":{"login":"josevalim","id":9582,"avatar_url":"https://avatars.githubusercontent.com/u/9582?v=3","gravatar_id":"","url":"https://api.github.com/users/josevalim","html_url":"https://github.com/josevalim","followers_url":"https://api.github.com/users/josevalim/followers","following_url":"https://api.github.com/users/josevalim/following{/other_user}","gists_url":"https://api.github.com/users/josevalim/gists{/gist_id}","starred_url":"https://api.github.com/users/josevalim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josevalim/subscriptions","organizations_url":"https://api.github.com/users/josevalim/orgs","repos_url":"https://api.github.com/users/josevalim/repos","events_url":"https://api.github.com/users/josevalim/events{/privacy}","received_events_url":"https://api.github.com/users/josevalim/received_events","type":"User","site_admin":false},"body":"So the pool here is not helping with anything because we are only using it from this server. We need to improve the design of the Redis adapter generally. Please ping me later so we can talk about it.","created_at":"2015-01-26T13:12:51Z","updated_at":"2015-01-26T13:12:51Z","html_url":"https://github.com/phoenixframework/phoenix/pull/594#discussion_r23528894","pull_request_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/594","_links":{"self":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/23528894"},"html":{"href":"https://github.com/phoenixframework/phoenix/pull/594#discussion_r23528894"},"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, PG2Adapter, RedisAdapter","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  * [ ] 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  * [ ] New local subscriber processes can be responsible for GC of their topic\r\n  * [ ] Remove pg2 from RedisAdapter entirely with new local subscriber tracking process\r\n  * [ ] Make pubsub API process based. `Phoenix.PubSub.broadcast(:my_process_name, \"foo\", \"bar\")`\r\n  * [ ] Remove configuration based pubsub module\r\n  * [x] Pool redis clients for broadcasting to redis (single connection remains for phx subscriber)\r\n  * [ ] Decide on Channel.broadcast API from outside channels","created_at":"2015-01-19T15:15:59Z","updated_at":"2015-01-26T13:12:51Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c1317431ecb66f42f4477924ade09aefcaa1d62e","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/4f0ab9f82a140d41e40240ed10341bcb8dc23121","head":{"label":"phoenixframework:cm-pubsub-adapters","ref":"cm-pubsub-adapters","sha":"4f0ab9f82a140d41e40240ed10341bcb8dc23121","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-01-26T08:20:49Z","pushed_at":"2015-01-25T18:46:06Z","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":6083,"stargazers_count":1885,"watchers_count":1885,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":167,"mirror_url":null,"open_issues_count":19,"forks":167,"open_issues":19,"watchers":1885,"default_branch":"master"}},"base":{"label":"phoenixframework:master","ref":"master","sha":"caa24baed26fc2abc2ca415846bf2fcb2463a06b","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-01-26T08:20:49Z","pushed_at":"2015-01-25T18:46:06Z","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":6083,"stargazers_count":1885,"watchers_count":1885,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":167,"mirror_url":null,"open_issues_count":19,"forks":167,"open_issues":19,"watchers":1885,"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/4f0ab9f82a140d41e40240ed10341bcb8dc23121"}}}}