在阅读Rufus Scheduler Docs大约100次后,我可以找到每个StackOverflow问题,查看源代码,最后阅读how to report bugs effectively两次。我已经到了@jmettraux我需要你帮助的地步:
我正在编写一个内部应用程序,允许我的团队创建指标,定期运行将值保存到数据库的代码。我有以下设置。
schema.rb
create_table "metrics", force: :cascade do |t|
t.string "frequency"
t.string "name"
t.text "data"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.enter code hereboolean "active"
end
初始化/ scheduler.rb
require 'rufus-scheduler'
class Rufus::Scheduler::Job
def report
logger = Logger.new(STDOUT)
logger.info "Job: #{@id} Tags: #{@tags} Frequency: #{@frequency}"
end
end
SCHEDULER = Rufus::Scheduler.new(:lockfile => ".rufus-scheduler.lock")
unless defined?(Rails::Console) || File.split($0).last == 'rake'
#Launch All Matrics Jobs that are active
@metrics = Metric.active
unless SCHEDULER.down?
@metrics.each_with_index do | metric, index |
SCHEDULER.every metric.frequency, :tags => metric.id, :overlap => false, :timeout => metric.try(:timeout) || '3m' do | job |
metric.add_value
job.report
end
end
end
end
上面的代码完美无瑕,Rufus正确初始化,所有工作都按照应有的方式开始。
我遇到的问题是,一旦应用程序运行,我需要能够取消计划作业并在保存指标时重新安排它们。我使用Rufus标签在创建时为其分配带有度量标准ID的标记,并且我使用它来加载它们以进行非排序。 这是我当前的度量模型及相关代码。
模型/ metric.rb
class Metric < ApplicationRecord
...
after_save :update_job
private
def update_job
if self.changed.present?
job = SCHEDULER.jobs(tag: self.id).first
if job.present?
logger.info "Job #{job.id}, for Metric #{self.id} unscheduled."
SCHEDULER.unschedule(job.id)
end
if self.active
metric = self
new_job = SCHEDULER.every self.frequency, :tags => self.id, :overlap => false, :timeout => self.try(:timeout) || '3m' do | job |
metric.add_value
end
logger.info "Job #{new_job} for Metric #{self.id} scheduled."
end
end
end
end
情景1:
- 应用程序启动
- scheduler initilizer加载所有活动指标
- scheduler initilizer将所有活动指标作为“每个”作业启动
- 用户编辑有效指标,将其设置为无效
- 在保存指标后运行update_job方法
- 在update_job方法中,成功找到当前指标的作业
- 在作业上调用unscheudle方法
- 问题在这里:即使在不定期之后,工作也会再次运行
情景2:
- 应用程序启动
- scheduler initilizer加载所有活动指标
- scheduler initilizer将所有活动指标作为“每个”作业启动
- 用户编辑非活动指标,将其设置为活动
- 在保存指标后运行update_job方法
- 在update_job方法中,为度量标准
安排了新的“每个”作业- 问题在这里:工作永远不会开始
我尝试将以下代码添加到初始化程序的底部,以确保取消计划方法在我的应用程序中正常运行。
sleep 50
job = SCHEDULER.jobs(tag: 2).first
SCHEDULER.unschedule(job)
ID为2的指标的频率为20秒。它运行两次然后正确调度,所以我知道它在模型代码之外正常工作。
情景1:
xxx SCHEDULER started: 70168058449780
xxx trigger (scheduled in initializer) Job: Rufus::Scheduler::EveryJob every_1482273018.01852_1684784125738549751 (70168045492220) Tags: ["2"] Frequency: 20.0
xxx Metric 2 #update_job active: false
xxx Metric 2 #update_job 0 SCHEDULER Rufus::Scheduler 3.2.2 70168058449780 down? false at_jobs: 0 in_jobs: 0 every_jobs: 2 interval_jobs: 0 cron_jobs: 0
xxx Metric 2 found Job Job: Rufus::Scheduler::EveryJob every_1482273018.01852_1684784125738549751 (70168045492220) Tags: ["2"] Frequency: 20.0
xxx Metric 2 Job every_1482273018.01852_1684784125738549751 unscheduled
xxx Metric 2 #update_job 1 SCHEDULER Rufus::Scheduler 3.2.2 70168058449780 down? false at_jobs: 0 in_jobs: 0 every_jobs: 1 interval_jobs: 0 cron_jobs: 0
xxx Metric 2 #update_job 2 SCHEDULER Rufus::Scheduler 3.2.2 70168058449780 down? false at_jobs: 0 in_jobs: 0 every_jobs: 1 interval_jobs: 0 cron_jobs: 0
xxx trigger (scheduled in initializer) Job: Rufus::Scheduler::EveryJob every_1482273018.01852_1684784125738549751 (70168045492220) Tags: ["2"] Frequency: 20.0
xxx trigger (scheduled in initializer) Job: Rufus::Scheduler::EveryJob every_1482273018.01852_1684784125738549751 (70168045492220) Tags: ["2"] Frequency: 20.0
情景2:
xxx SCHEDULER started: 70272158805840
xxx Metric 2 #update_job active: true
xxx Metric 2 #update_job 0 SCHEDULER Rufus::Scheduler 3.2.2 70272158805840 down? false at_jobs: 0 in_jobs: 0 every_jobs: 1 interval_jobs: 0 cron_jobs: 0
xxx Metric 2 #update_job 1 SCHEDULER Rufus::Scheduler 3.2.2 70272158805840 down? false at_jobs: 0 in_jobs: 0 every_jobs: 1 interval_jobs: 0 cron_jobs: 0
xxx Metric 2 Job every_1482273159.799382_1489585858424637616 scheduled
xxx Metric 2 #update_job 2 SCHEDULER Rufus::Scheduler 3.2.2 70272158805840 down? false at_jobs: 0 in_jobs: 0 every_jobs: 2 interval_jobs: 0 cron_jobs: 0
情景1:
xxx SCHEDULER started: 70321240613760
xxx sc Rufus::Scheduler 3.2.2 70321240613760
xxx sc down? false
xxx sc Process.pid 39975
xxx sc jobs:
xxx sc 0: j: Rufus::Scheduler::EveryJob i: "every_1482335616.453129_4198855636205920237" oi: 70321198587560 ts: ["2"] frq: 20.0 ua: nil c: 0 nt: 2016-12-21 09:53:56 -0600
xxx initializer over.
xxx Metric 2 Job every_1482335616.453129_4198855636205920237 scheduled in initializer for Metric 2
xxx Metric 2 Job every_1482335616.453129_4198855636205920237 scheduled in initializer for Metric 2
xxx Metric 2 Job every_1482335616.453129_4198855636205920237 scheduled in initializer for Metric 2
xxx Metric 2 #update_job changed: ["active", "updated_at"] active: false
xxx Metric 2 sc #update_job 0 Rufus::Scheduler 3.2.2 70321240613760
xxx Metric 2 sc #update_job 0 down? false
xxx Metric 2 sc #update_job 0 Process.pid 40037
xxx Metric 2 sc #update_job 0 jobs:
xxx Metric 2 sc #update_job 0 0: j: Rufus::Scheduler::EveryJob i: "every_1482335616.453129_4198855636205920237" oi: 70321198587560 ts: ["2"] frq: 20.0 ua: nil c: 0 nt: 2016-12-21 09:53:56 -0600
xxx Metric 2 found Job j: Rufus::Scheduler::EveryJob i: "every_1482335616.453129_4198855636205920237" oi: 70321198587560 ts: ["2"] frq: 20.0 ua: nil c: 0 nt: 2016-12-21 09:53:56 -0600 first
xxx Metric 2 Job every_1482335616.453129_4198855636205920237 unscheduled
xxx Metric 2 Job every_1482335616.453129_4198855636205920237 scheduled? false
xxx Metric 2 sc #update_job 1 Rufus::Scheduler 3.2.2 70321240613760
xxx Metric 2 sc #update_job 1 down? false
xxx Metric 2 sc #update_job 1 Process.pid 40037
xxx Metric 2 sc #update_job 1 jobs:
xxx Metric 2 Job every_1482335616.453129_4198855636205920237 scheduled in initializer for Metric 2
情景2:
xxx SCHEDULER started: 70152969118240
xxx sc Rufus::Scheduler 3.2.2 70152969118240
xxx sc down? false
xxx sc Process.pid 40811
xxx sc jobs:
xxx initializer over.
xxx Metric 2 #update_job changed: ["active", "updated_at"] active: true
xxx Metric 2 sc #update_job 0 Rufus::Scheduler 3.2.2 70152969118240
xxx Metric 2 sc #update_job 0 down? false
xxx Metric 2 sc #update_job 0 Process.pid 40872
xxx Metric 2 sc #update_job 0 jobs:
xxx Metric 2 sc #update_job 1 Rufus::Scheduler 3.2.2 70152969118240
xxx Metric 2 sc #update_job 1 down? false
xxx Metric 2 sc #update_job 1 Process.pid 40872
xxx Metric 2 sc #update_job 1 jobs:
xxx Metric 2 Job every_1482336051.9823449_3069051954259909338 scheduled in #update_job
xxx Metric 2 sc #update_job 2 Rufus::Scheduler 3.2.2 70152969118240
xxx Metric 2 sc #update_job 2 down? false
xxx Metric 2 sc #update_job 2 Process.pid 40872
xxx Metric 2 sc #update_job 2 jobs:
xxx Metric 2 sc #update_job 2 0: j: Rufus::Scheduler::EveryJob i: "every_1482336051.9823449_3069051954259909338" oi: 70152971745840 ts: ["2"] frq: 20.0 ua: nil c: 0 nt: 2016-12-21 10:01:11 -0600
答案 0 :(得分:1)
当上述代码运行以使活动指标变为非活动状态时,我成功获取了Logger消息并加载了正确的作业。同样,当我使用更好的错误来检查该区域的代码时,作业本身会正确设置@unscheduled_at属性,但是Job会不断重新安排再次运行。
另一方面,当update_job方法运行并尝试重新安排作业时,它永远不会启动。
那么你是否想说约伯被重新安排,但新的,重新安排的约伯永远不会触发?
如果我天真地看着你的代码(为了更好的理解而重新编写代码):
def update_job
if self.changed.present?
job = SCHEDULER.jobs(tag: self.id).first
if job.present?
logger.info "Job #{job.id}, for Metric #{self.id} unscheduled."
job.unschedule
end
if self.active
metric = self
new_job_id =
SCHEDULER.every(
self.frequency,
:tags => self.id,
:overlap => false,
:timeout => self.try(:timeout) || '3m'
) { |job| metric.add_value }
logger.info "Job #{new_job_id} for Metric #{self.id} scheduled."
end
end
end
您描述的症状是否可以恢复为“应用程序永远不会进入update_job方法的重新安排块”?
ID为2的指标的频率为20秒。它运行两次然后正确计划,所以我知道它在模型代码之外正常工作。
所以这是你的代码模型中的东西。与rufus-scheduler相关的可能性很小。
请仔细改写您的“实际问题”。在目前的版本中,我很困惑。
第2阶段 - 2016-12-21
您能否尝试使用以下代码?
警告:它未经过测试,可能包含您必须发现并修复以使其可用的错误。
然后尝试你的两个scenarii并报告,这个代码正在发出“xxx”的日志grep。它可能会告诉我们出了什么问题。
提前致谢。
# initializers/scheduler.rb
require 'rufus-scheduler'
class Rufus::Scheduler
def to_report_s
a = []
a << self.class
a << Rufus::Scheduler::VERSION
a << self.object_id
a << "down? #{self.down?}"
%w[ at in every internal cron ].each do |flav|
m = "#{flav}_jobs".to_sym
a << "#{m}: #{self.send(m).size}"
end
a.collect(&:to_s).join(' ')
end
end
class Rufus::Scheduler::Job
def to_report_s
"Job: #{self.class} #{@id} (#{self.object_id}) " +
"Tags: #{@tags.inspect} Frequency: #{@frequency}"
end
end
#SCHEDULER = Rufus::Scheduler.new(:lockfile => ".rufus-scheduler.lock")
SCHEDULER = Rufus::Scheduler.new
# Add "global" error handler to the rufus-scheduler instance
#
def SCHEDULER.on_error(job, error)
Rails.logger.error(
"xxx err#{error.object_id} rufus-scheduler intercepted #{error.inspect}" +
" in job #{job.to_report_s}")
error.backtrace.each_with_index do |line, i|
Rails.logger.error(
"xxx err#{error.object_id} #{i}: #{line}")
end
end
logger.info("xxx SCHEDULER started: #{SCHEDULER.object_id}")
unless (
defined?(Rails::Console) || File.split($0).last == 'rake' ||
SCHEDULER.down?
)
# launch All Matrics Jobs that are active
Metric.active.each do |metric|
SCHEDULER.every(
metric.frequency,
:tags => metric.id,
:overlap => false,
:timeout => metric.try(:timeout) || '3m'
) do |job|
logger.info "xxx trigger (scheduled in initializer) #{job.to_report_s}"
metric.add_value
end
end
end
和
# models/metric.rb
class Metric < ApplicationRecord
after_save :update_job
private
def update_job
lip = "Metric #{id}" # logger info prefix
logger.info("xxx #{lip} #update_job active: #{self.active.inspect}")
if self.changed.present?
logger.info("xxx #{lip} #update_job 0 SCHEDULER #{SCHEDULER.to_report_s}")
#job = SCHEDULER.jobs(tag: self.id).first
#if job.present?
if job = SCHEDULER.jobs(tag: self.id).first
logger.info "xxx #{lip} found Job #{job.to_report_s}"
#SCHEDULER.unschedule(job.id)
job.unschedule
logger.info "xxx #{lip} Job #{job.id} unscheduled"
end
logger.info("xxx #{lip} #update_job 1 SCHEDULER #{SCHEDULER.to_report_s}")
if self.active
metric = self
job_id = SCHEDULER.every(
self.frequency,
:tags => self.id,
:overlap => false,
:timeout => self.try(:timeout) || '3m'
) do |job|
logger.info "xxx trigger (scheduled in #updateJob) #{job.to_report_s}"
metric.add_value
end
logger.info "xxx #{lip} Job #{job_id} scheduled"
end
logger.info("xxx #{lip} #update_job 2 SCHEDULER #{SCHEDULER.to_report_s}")
end
end
end
第3阶段 - 2016-12-21
请您重试以下代码:
# initializers/scheduler.rb
require 'rufus-scheduler'
class Rufus::Scheduler
def to_report_a
a = []
a << "#{self.class} #{Rufus::Scheduler::VERSION} #{object_id}"
a << "down? #{self.down?}"
a << "Process.pid #{Process.pid}"
a << "jobs:"
jobs.each_with_index { |job, i| a << " #{i}: #{job.to_report_s}" }
a.collect(&:to_s)
end
end
class Rufus::Scheduler::Job
def to_report_s
{
j: self.class, i: @id, oi: object_id, ts: @tags,
frq: @frequency, ua: @unscheduled_at, c: count, nt: next_time
}
.collect { |k, v| "#{k}: #{v.inspect}" }
.join(' ')
end
end
#SCHEDULER = Rufus::Scheduler.new(:lockfile => ".rufus-scheduler.lock")
SCHEDULER = Rufus::Scheduler.new
# Add "global" error handler to the rufus-scheduler instance
#
def SCHEDULER.on_error(job, error)
lep = "xxx err#{error.object_id}"
Rails.logger.error(
"xxx #{lep} rufus-scheduler intercepted #{error.inspect}" +
" in job #{job.to_report_s}")
error.backtrace.each_with_index do |line, i|
Rails.logger.error(
"xxx #{lep} #{i}: #{line}")
end
Rails.logger.error("xxx #{lep} scheduler:")
SCHEDULER.to_report_a.each { |l| Rails.logger.error("xxx #{lep} #{l}") }
end
logger.info("xxx SCHEDULER started: #{SCHEDULER.object_id}")
unless (
defined?(Rails::Console) || File.split($0).last == 'rake' ||
SCHEDULER.down?
)
# launch All Matrics Jobs that are active
Metric.active.each do |metric|
lip = "Metric #{metric.id}" # logger info prefix
job_id =
SCHEDULER.every(
metric.frequency,
:tags => metric.id,
:overlap => false,
:timeout => metric.try(:timeout) || '3m'
) do |job|
logger.info(
"xxx #{lip} trigger (scheduled in initializer) " +
"#{job.to_report_s}")
metric.add_value
end
logger.info(
"xxx #{lip} Job #{job_id} scheduled in initializer " +
"for Metric #{metric.id}")
end
SCHEDULER.to_report_a.each { |l| logger.info("xxx sc #{l}") }
logger.info("xxx initializer over.")
end
和
# models/metric.rb
class Metric < ApplicationRecord
after_save :update_job
private
def update_job
lip = "Metric #{id}" # logger info prefix
logger.info(
"xxx #{lip} #update_job " +
"changed: #{self.changed.inspect} active: #{self.active.inspect}")
return unless self.changed.present?
SCHEDULER.to_report_a
.each { |l| logger.info("xxx #{lip} sc #update_job 0 #{l}") }
jobs = SCHEDULER.jobs(tag: self.id)
jobs.each_with_index do |job, i|
logger.info(
"xxx #{lip} found Job #{job.to_report_s} #{i == 0 ? 'first' : ''}")
end
if job = jobs.first
#SCHEDULER.unschedule(job.id)
job.unschedule
logger.info "xxx #{lip} Job #{job.id} unscheduled"
logger.info "xxx #{lip} Job #{job.id} scheduled? #{SCHEDULER.scheduled?(job.id)}"
end
SCHEDULER.to_report_a
.each { |l| logger.info("xxx #{lip} sc #update_job 1 #{l}") }
return unless self.active
metric = self
job_id = SCHEDULER.every(
self.frequency,
:tags => self.id,
:overlap => false,
:timeout => self.try(:timeout) || '3m'
) do |job|
logger.info "xxx trigger (scheduled in #update_job) #{job.to_report_s}"
metric.add_value
end
logger.info "xxx #{lip} Job #{job_id} scheduled in #update_job"
SCHEDULER.to_report_a
.each { |l| logger.info("xxx #{lip} sc #update_job 2 #{l}") }
end
end
第4阶段 - 2016-12-22
添加第3阶段日志以提问。看起来有些新的调度程序进程随后会在模型代码中创建然后销毁。再次感谢您对此的勤奋!
这是否真的发生在型号代码中?您的日志告诉我们它发生在另一个进程中。您的初始Ruby进程实例化rufus-scheduler,然后您的HTTP请求在工作进程中提供,这些进程是您的初始进程的分支(没有线程,换句话说是非活动的调度程序)。
您在群集模式下使用Puma。我应该立即问你关于你的配置。
在https://github.com/puma/puma#configuration
仔细阅读其文档一个简单的解决方法是不使用集群模式,因此只涉及一个Ruby进程,为所有HTTP请求提供服务。
另一方面,如果您需要群集模式,则必须改变您的思维方式。您可能不希望每个工作线程有1个rufus-scheduler实例。您可以专注于在主进程中使用核心(实时)rufus调度程序。它可能有一个“管理”工作,可以检查最近更新的指标和计划/计划工作。
SCHEDULER.every '10s', overlap: false do
Metric.recently_updated.each do |metric|
SCHEDULER.jobs(tags: metric.id).each(&:unschedule)
SCHEDULER.every(metric.frequency, tags: self.id) { metric.add_value }
end
end
# or something like that...
玩得开心!