32

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?

3
  • 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. Commented 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 Commented 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 Commented May 9, 2013 at 16:55

2 Answers 2

60
+300

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}"
    end

    # 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}"
    end
  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 *"
  end
end

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Just for clarification: if it doesn't receive a response in 1/2 a second, does it stop listening?
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.
So we would need to poll the wait_for_notify in order to actually get the message?
Thanks for digging into it and putting it in layman's terms. Makes a lot more sense now.
@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?
1

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

The source in hey_you is easy to read and looks similar to the other examples

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.