[ARVADOS] created: ae7d341b2a46d4740aa6ad76e2756ae6721ad874

Git user git at public.curoverse.com
Wed May 4 15:01:53 EDT 2016


        at  ae7d341b2a46d4740aa6ad76e2756ae6721ad874 (commit)


commit ae7d341b2a46d4740aa6ad76e2756ae6721ad874
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed May 4 14:55:03 2016 -0400

    8886: Experimental asynchronous permissions update.
    
    Add configuration parameter 'async_permissions_update' (default false).  If
    true, do not delete permission cache in #invalidate_permissions_cache, but
    instead trigger "NOTIFY invalidate_permissions_cache" on the database.
    
    Add script/permission-updater.rb which runs as an independent process.  It
    blocks on "LISTEN invalidate_permissions_cache" and updates the permission
    cache whenever notified.
    
    This is not ready for use; in particular it creates a race condition
    recomputing permissions with effects such as not being able to read back API
    records that were just created.

diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb
index 2200d05..041557c 100644
--- a/services/api/app/models/user.rb
+++ b/services/api/app/models/user.rb
@@ -124,18 +124,14 @@ class User < ArvadosModel
   end
 
   def self.invalidate_permissions_cache
-    Rails.cache.delete_matched(/^groups_for_user_/)
+    if Rails.configuration.async_permissions_update
+      connection.execute "NOTIFY invalidate_permissions_cache"
+    else
+      Rails.cache.delete_matched(/^groups_for_user_/)
+    end
   end
 
-  # Return a hash of {group_uuid: perm_hash} where perm_hash[:read]
-  # and perm_hash[:write] are true if this user can read and write
-  # objects owned by group_uuid.
-  #
-  # The permission graph is built by repeatedly enumerating all
-  # permission links reachable from self.uuid, and then calling
-  # search_permissions
-  def group_permissions
-    Rails.cache.fetch "groups_for_user_#{self.uuid}" do
+  def calculate_group_permissions
       permissions_from = {}
       todo = {self.uuid => true}
       done = {}
@@ -182,7 +178,21 @@ class User < ArvadosModel
           end
         end
       end
-      search_permissions(self.uuid, permissions_from)
+      perms = search_permissions(self.uuid, permissions_from)
+      Rails.cache.write "groups_for_user_#{self.uuid}", perms
+      perms
+  end
+
+  # Return a hash of {group_uuid: perm_hash} where perm_hash[:read]
+  # and perm_hash[:write] are true if this user can read and write
+  # objects owned by group_uuid.
+  #
+  # The permission graph is built by repeatedly enumerating all
+  # permission links reachable from self.uuid, and then calling
+  # search_permissions
+  def group_permissions
+    Rails.cache.fetch "groups_for_user_#{self.uuid}" do
+      calculate_group_permissions
     end
   end
 
diff --git a/services/api/config/application.default.yml b/services/api/config/application.default.yml
index c709633..f1c4dd0 100644
--- a/services/api/config/application.default.yml
+++ b/services/api/config/application.default.yml
@@ -345,6 +345,12 @@ common:
 
   crunch_log_partial_line_throttle_period: 5
 
+  # Enable asynchronous permission graph rebuild.  Must run
+  # script/permission-updater.rb as a separate process.  When the permission
+  # cache is invalidated, the background process will update the permission
+  # graph cache.  This feature is experimental!
+  async_permissions_update: false
+
 development:
   force_ssl: false
   cache_classes: false
diff --git a/services/api/script/permission-updater.rb b/services/api/script/permission-updater.rb
new file mode 100755
index 0000000..5ecab0f
--- /dev/null
+++ b/services/api/script/permission-updater.rb
@@ -0,0 +1,41 @@
+#!/usr/bin/env ruby
+
+dispatch_argv = []
+ARGV.reject! do |arg|
+  dispatch_argv.push(arg) if /^--/ =~ arg
+end
+
+ENV["RAILS_ENV"] = ARGV[0] || ENV["RAILS_ENV"] || "development"
+require File.dirname(__FILE__) + '/../config/boot'
+require File.dirname(__FILE__) + '/../config/environment'
+
+User.all.each do |u|
+  u.calculate_group_permissions
+end
+
+ActiveRecord::Base.connection_pool.with_connection do |connection|
+  conn = connection.instance_variable_get(:@connection)
+  begin
+    conn.async_exec "LISTEN invalidate_permissions_cache"
+    while true
+      # wait_for_notify will block until there is a change
+      # notification from Postgres about the logs table, then push
+      # the notification into the EventMachine channel.  Each
+      # websocket connection subscribes to the other end of the
+      # channel and calls #push_events to actually dispatch the
+      # events to the client.
+      conn.wait_for_notify do |channel, pid, payload|
+        Rails.logger.info "Begin updating permission cache"
+        User.all.each do |u|
+          u.calculate_group_permissions
+        end
+        Rails.logger.info "Permission cache updated"
+      end
+    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

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list