相关文章推荐
玩足球的斑马  ·  System.Runtime.Interop ...·  1 年前    · 
慈祥的佛珠  ·  PowerShell is open ...·  1 年前    · 
开朗的键盘  ·  java.io.FileNotFoundEx ...·  2 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Ryan Bates mentions the LISTEN/NOTIFY functionality of Postgres when discussing push notifications in this episode , but I haven't been able to find any hint on how to implement a LISTEN/NOTIFY in my rails app.

Here is documentation for a wait_for_notify function inside of the pg adaptor, but I can't figure out what exactly that does/is designed for.

Do we need to tap directly into the connection variable of the pg adaptor?

If I understand correctly, you want db to push information to rails. If rails has some kind of listener, it could be done with plperl which then connects to such listener and delivers information. mpapec May 9, 2013 at 7:29 I'm trying to find some sample code of how to set up rails to act as that listener. I don't think we need plperl, because of the LISTEN/NOTIFY abilities of Postgres, but I'm game to try anything you can think of Tyler DeWitt May 9, 2013 at 15:29 I've had a hack in mind; better look at sequel.rubyforge.org/rdoc/files/doc/postgresql_rdoc.html or github.com/taotetek/listen_notify_poller/blob/master/example.rb mpapec May 9, 2013 at 16:55

You're looking in the right place with the wait_for_notify method, but since ActiveRecord apparently doesn't provide an API for using it, you'll need to get at the underlying PG::Connection object (or one of them, if you're running a multithreaded setup) that ActiveRecord is using to talk to Postgres.

Once you've got the connection, simply execute whatever LISTEN statements you need, then pass a block (and an optional timeout period) to wait_for_notify . Note that this will block the current thread, and monopolize the Postgres connection, until the timeout is reached or a NOTIFY occurs (so you wouldn't want to do this inside a web request, for example). When another process issues a NOTIFY on one of the channels you're listening to, the block will be called with three arguments - the channel that the notified, the pid of the Postgres backend that triggered the NOTIFY , and the payload that accompanied the NOTIFY (if any).

I haven't used ActiveRecord in quite a while, so there may be a cleaner way to do this, but this seems to work alright in 4.0.0.beta1:

# Be sure to check out a connection, so we stay thread-safe.
ActiveRecord::Base.connection_pool.with_connection do |connection|
  # connection is the ActiveRecord::ConnectionAdapters::PostgreSQLAdapter object
  conn = connection.instance_variable_get(:@connection)
  # conn is the underlying PG::Connection object, and exposes #wait_for_notify
  begin
    conn.async_exec "LISTEN channel1"
    conn.async_exec "LISTEN channel2"
    # This will block until a NOTIFY is issued on one of these two channels.
    conn.wait_for_notify do |channel, pid, payload|
      puts "Received a NOTIFY on channel #{channel}"
      puts "from PG backend #{pid}"
      puts "saying #{payload}"
    # Note that you'll need to call wait_for_notify again if you want to pick
    # up further notifications. This time, bail out if we don't get a
    # notification within half a second.
    conn.wait_for_notify(0.5) do |channel, pid, payload|
      puts "Received a second NOTIFY on channel #{channel}"
      puts "from PG backend #{pid}"
      puts "saying #{payload}"
  ensure
    # Don't want the connection to still be listening once we return
    # it to the pool - could result in weird behavior for the next
    # thread to check it out.
    conn.async_exec "UNLISTEN *"

For an example of a more general usage, see Sequel's implementation.

Edit to add: Here's another description of what's going on. This may not be the exact implementation behind the scenes, but it seems to describe the behavior well enough.

Postgres keeps a list of notifications for each connection. When you use a connection to execute LISTEN channel_name, you're telling Postgres that any notifications on that channel should be pushed to this connection's list (multiple connections can listen to the same channel, so a single notification can wind up being pushed to many lists). A connection can LISTEN to many channels at the same time, and notifications to any of them will all be pushed to the same list.

What wait_for_notify does is pop the oldest notification off of the connection's list and passes its information to the block - or, if the list is empty, sleeps until a notification becomes available and does the same for that (or until the timeout is reached, in which case it just returns nil). Since wait_for_notify only handles a single notification, you're going to have to call it repeatedly if you want to handle multiple notifications.

When you UNLISTEN channel_name or UNLISTEN *, Postgres will stop pushing those notifications to your connection's list, but the ones that have already been pushed to that list will stay there, and wait_for_notify will still return them when it is next called. This might cause an issue where notifications that are accumulated after wait_for_notify but before UNLISTEN stick around and are still present when another thread checks out that connection. In that case, after UNLISTEN you might want to call wait_for_notify with short timeouts until it returns nil. But unless you're making heavy use of LISTEN and NOTIFY for many different purposes, though, it's probably not worth worrying about.

I added a better link to Sequel's implementation above, I'd recommend looking at it. It's pretty straightforward.

Just for clarification: if it doesn't receive a response in 1/2 a second, does it stop listening? – Tyler DeWitt May 13, 2013 at 18:04 If it doesn't receive a response by the timeout you specify, wait_for_notify will return nil and the block won't be called, but the LISTEN for channel1 and channel2 will remain in effect until you UNLISTEN. So your code can go do whatever else, and when you wait_for_notify again you'll still be listening to channel1 and channel2. I believe that if a NOTIFY occurred while you weren't blocking on wait_for_notify, you'll pick it up immediately when you wait_for_notify again, but I'm not certain about that. – PreciousBodilyFluids May 14, 2013 at 21:24 Thanks for digging into it and putting it in layman's terms. Makes a lot more sense now. – Tyler DeWitt May 15, 2013 at 13:26 @PreciousBodilyFluids Thanks for your answer. One question: if I want to continuously listen should I put wait_for_notify inside an infinite loop. In that case I can continously listen to those channels? – samol Jan 2, 2014 at 19:36

The accepted answer looks good to me. Some promising resources I found while exploring postgres LISTEN/NOTIFY:

  • https://gist.github.com/chsh/9c9f5702919c83023f83
  • https://github.com/schneems/hey_you
  • The source in hey_you is easy to read and looks similar to the other examples

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.