18865: Rearrange code & update comments for clarity.
[arvados.git] / services / api / lib / update_priority.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 module UpdatePriority
6   extend CurrentApiClient
7
8   # Clean up after races.
9   #
10   # If container priority>0 but there are no committed container
11   # requests for it, reset priority to 0.
12   #
13   # If container priority=0 but there are committed container requests
14   # for it with priority>0, update priority.
15   #
16   # Normally, update_priority is a no-op if another thread/process is
17   # already updating. Test cases that need to check priorities after
18   # updating can force a (possibly overlapping) update in the current
19   # thread/transaction by setting the "nolock" flag. See #14878.
20   def self.update_priority(nolock: false)
21     if !File.owned?(Rails.root.join('tmp'))
22       Rails.logger.warn("UpdatePriority: not owner of #{Rails.root}/tmp, skipping")
23       return
24     end
25     lockfile = Rails.root.join('tmp', 'update_priority.lock')
26     File.open(lockfile, File::RDWR|File::CREAT, 0600) do |f|
27       return unless nolock || f.flock(File::LOCK_NB|File::LOCK_EX)
28
29       # priority>0 but should be 0:
30       ActiveRecord::Base.connection.
31         exec_query("UPDATE containers AS c SET priority=0 WHERE state IN ('Queued', 'Locked', 'Running') AND priority>0 AND uuid NOT IN (SELECT container_uuid FROM container_requests WHERE priority>0 AND state='Committed');", 'UpdatePriority')
32
33       # priority==0 but should be >0:
34       act_as_system_user do
35         Container.
36           joins("JOIN container_requests ON container_requests.container_uuid=containers.uuid AND container_requests.state=#{ActiveRecord::Base.connection.quote(ContainerRequest::Committed)} AND container_requests.priority>0").
37           where('containers.state IN (?) AND containers.priority=0 AND container_requests.uuid IS NOT NULL',
38                 [Container::Queued, Container::Locked, Container::Running]).
39           map(&:update_priority!)
40       end
41     end
42   end
43
44   def self.run_update_thread
45     need = false
46     Rails.cache.fetch('UpdatePriority', expires_in: 5.seconds) do
47       need = true
48     end
49     return if !need
50
51     Thread.new do
52       Thread.current.abort_on_exception = false
53       begin
54         update_priority
55       rescue => e
56         Rails.logger.error "#{e.class}: #{e}\n#{e.backtrace.join("\n\t")}"
57       ensure
58         # Rails 5.1+ makes test threads share a database connection, so we can't
59         # close a connection shared with other threads.
60         # https://github.com/rails/rails/commit/deba47799ff905f778e0c98a015789a1327d5087
61         if Rails.env != "test"
62           ActiveRecord::Base.connection.close
63         end
64       end
65     end
66   end
67 end