<commit_before>class Marty::Promise < Marty::Base

  # default timeout (seconds) to wait for promise values
  DEFAULT_PROMISE_TIMEOUT = Rails.configuration.marty.promise_timeout || 30

  # default timeout (seconds) to wait for jobs to start
  DEFAULT_JOB_TIMEOUT = Rails.configuration.marty.job_timeout || 10

  SELECT_COLS = columns.map(&:name)-["result"]
  default_scope {
    select(*SELECT_COLS)
  }

  # implements laziness for the result column -- FIXME: are we just
  # doing this for the jobs dashboard?  If so, why don't we create a
  # database view which excludes result? That seems a lot safer.
  def result
    unless has_attribute?(:result)
      changes_before_reload = self.changes.clone
      self.reload
      changes_before_reload.each{
        |attribute_name, values|
        self.send("#{attribute_name}=", values[1])
      }
    end
    read_attribute(:result) || {}
  end

  validates_presence_of :title

  has_many :children,
           foreign_key: 'parent_id',
           class_name: "Marty::Promise",
           dependent: :destroy

  belongs_to :parent, class_name: "Marty::Promise"
  belongs_to :user, class_name: "Marty::User"

  def self.cleanup(all=false)
    begin
      where('start_dt < ? AND parent_id IS NULL',
            DateTime.now - (all ? 0.hours : 4.hours)).destroy_all
    rescue => exc
      Marty::Util.logger.error("promise GC error: #{exc}")
    end
  end

  class VirtualRoot
    def self.primary_key
      'id'
    end

    def id
      'root'
    end

    def user_id
      0
    end
    alias_method :job_id, :user_id

    def result
      nil
    end
    [:start_dt, :end_dt].each { |m| alias_method m, :result }

    def status
      true
    end
  end

  def self.root
    VirtualRoot.new
  end

  def self.children_for_id(id, search_order)
    q = id == 'root' ? where(parent_id: nil) : find(id).children
    q.live_search(search_order).order(id: :desc).includes(:children, :user)
  end

  def leaf
    children.empty?
  end

  def raw_conn
    self.class.connection.raw_connection
  end

  def pg_notify
    raw_conn.async_exec("NOTIFY promise_#{id}")
  end

  def set_start
    if self.start_dt || self.result != {}
      Marty::Util.logger.error("promise already started: #{self}")
      return
    end

    # mark promise as started
    self.start_dt = DateTime.now
    self.save!
  end

  def set_result(res)
    log "SETRES #{Process.pid} #{self}"

    # promise must have been started and not yet ended
    if !self.start_dt || self.end_dt || self.result != {}
      # log "SETERR #{Process.pid} #{self}"
      Marty::Util.logger.error("unexpected promise state: #{self}")
      return
    end

    raise "bad result" unless res.is_a?(Hash)

    self.status = res["error"].nil?
    self.result = res

    # update title/format from result hash (somewhat hacky)
    self.title   = res["title"].to_s  if res["title"]
    self.cformat = res["format"].to_s if res["format"]

    # mark promise as ended
    self.end_dt = DateTime.now
    self.save!

    log "NOTIFY #{Process.pid}"
    pg_notify
  end

  def to_s
    inspect
  end

  def log(msg)
    open('/tmp/dj.out', 'a') { |f| f.puts msg }
  end

  def wait_for_my_notify(timeout)
    while true do
      # FIXME: we keep using the same timeout.  The timeout should be
      # reduced by total time spent here.
      n = raw_conn.wait_for_notify(timeout)
      return n if !n || n=="promise_#{id}"
    end
  end

  def latest
    # FIXME: Not sure if this is idiomatic.  What's the best way to
    # force AR to reload the promise object?  reset+reload doesn't
    # seems to work.

    # get latest uncached version
    Marty::Promise.uncached {Marty::Promise.find(id)}
  end

  def self.job_by_id(job_id)
    Delayed::Job.uncached {Delayed::Job.find_by_id(job_id)}
  end
<commit_msg>turn off the lazy result mechanism<commit_after>class Marty::Promise < Marty::Base

  # default timeout (seconds) to wait for promise values
  DEFAULT_PROMISE_TIMEOUT = Rails.configuration.marty.promise_timeout || 30

  # default timeout (seconds) to wait for jobs to start
  DEFAULT_JOB_TIMEOUT = Rails.configuration.marty.job_timeout || 10

=begin
  SELECT_COLS = columns.map(&:name)-["result"]
  default_scope {
    select(*SELECT_COLS)
  }

  # implements laziness for the result column -- FIXME: are we just
  # doing this for the jobs dashboard?  If so, why don't we create a
  # database view which excludes result? That seems a lot safer.
  def result
    unless has_attribute?(:result)
      changes_before_reload = self.changes.clone
      self.reload
      changes_before_reload.each{
        |attribute_name, values|
        self.send("#{attribute_name}=", values[1])
      }
    end
    read_attribute :result
  end
=end

  validates_presence_of :title

  has_many :children,
           foreign_key: 'parent_id',
           class_name: "Marty::Promise",
           dependent: :destroy

  belongs_to :parent, class_name: "Marty::Promise"
  belongs_to :user, class_name: "Marty::User"

  def self.cleanup(all=false)
    begin
      where('start_dt < ? AND parent_id IS NULL',
            DateTime.now - (all ? 0.hours : 4.hours)).destroy_all
    rescue => exc
      Marty::Util.logger.error("promise GC error: #{exc}")
    end
  end

  class VirtualRoot
    def self.primary_key
      'id'
    end

    def id
      'root'
    end

    def user_id
      0
    end
    alias_method :job_id, :user_id

    def result
      nil
    end
    [:start_dt, :end_dt].each { |m| alias_method m, :result }

    def status
      true
    end
  end

  def self.root
    VirtualRoot.new
  end

  def self.children_for_id(id, search_order)
    q = id == 'root' ? where(parent_id: nil) : find(id).children
    q.live_search(search_order).order(id: :desc).includes(:children, :user)
  end

  def leaf
    children.empty?
  end

  def raw_conn
    self.class.connection.raw_connection
  end

  def pg_notify
    raw_conn.async_exec("NOTIFY promise_#{id}")
  end

  def set_start
    if self.start_dt || self.result != {}
      Marty::Util.logger.error("promise already started: #{self}")
      return
    end

    # mark promise as started
    self.start_dt = DateTime.now
    self.save!
  end

  def set_result(res)
    # log "SETRES #{Process.pid} #{self}"

    # promise must have been started and not yet ended
    if !self.start_dt || self.end_dt || self.result != {}
      # log "SETERR #{Process.pid} #{self}"
      Marty::Util.logger.error("unexpected promise state: #{self}")
      return
    end

    raise "bad result" unless res.is_a?(Hash)

    self.status = res["error"].nil?
    self.result = res

    # update title/format from result hash (somewhat hacky)
    self.title   = res["title"].to_s  if res["title"]
    self.cformat = res["format"].to_s if res["format"]

    # mark promise as ended
    self.end_dt = DateTime.now
    self.save!

    # log "NOTIFY #{Process.pid}"
    pg_notify
  end

  def to_s
    inspect
  end

  # def log(msg)
  #   open('/tmp/dj.out', 'a') { |f| f.puts msg }
  # end

  def wait_for_my_notify(timeout)
    while true do
      # FIXME: we keep using the same timeout.  The timeout should be
      # reduced by total time spent here.
      n = raw_conn.wait_for_notify(timeout)
      return n if !n || n=="promise_#{id}"
    end
  end

  def latest
    # FIXME: Not sure if this is idiomatic.  What's the best way to
    # force AR to reload the promise object?  reset+reload doesn't
    # seems to work.

    # get latest uncached version
    Marty::Promise.uncached {Marty::Promise.find(id)}
  end

  def self.job_by_id(job_id)
    Delayed::Job.uncached {Delayed::Job.find_by_id(job_id)}
  end

{'fraction_non_alphanumeric': 0.10824607329842932, 'fraction_numerical': 0.0020942408376963353, 'mean_word_length': 2.7640394088669953, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5972551', 'n_tokens_mistral': 2581, 'n_tokens_neox': 2501, 'n_words': 694}