5 Stimmen

Ist es möglich, einen bereits laufenden verzögerten Auftrag mit Ruby Threading zu beenden?

Nehmen wir an, ich habe delayed_job im Hintergrund laufen. Aufgaben können geplant oder sofort ausgeführt werden (einige sind lange Aufgaben, andere nicht)

Wenn eine Aufgabe zu lang ist, sollte ein Benutzer sie abbrechen können. Ist dies bei verzögerten Aufträgen möglich? Ich habe in den Dokumenten nachgesehen und kann keine terminate-Methode oder ähnliches finden. Es gibt nur eine Möglichkeit, einen verzögerten Auftrag selbst abzubrechen (und damit alle Aufgaben abzubrechen... ich muss nur eine bestimmte laufende Aufgabe abbrechen).

UPDATE Mein Chef (der übrigens ein großartiger Programmierer ist) schlug vor, Ruby Threading für diese Funktion von uns zu verwenden. Ist das möglich? Wie das Erstellen neuer Threads pro Aufgabe und das Beenden dieses Threads, während er läuft?

etwas wie:

t1 = Thread.new(task.run)
self.delay.t1.join (?) -- still reading on threads so correct me if im wrong

dann, um es zu stoppen, werde ich einfach t1.stop verwenden (?) wieder weiß noch nicht

Ist dies möglich? Danke!

3voto

corroded Punkte 21008

Es scheint, als hätte mein Chef den Nagel auf den Kopf getroffen, also haben wir Folgendes getan (bitte sagen Sie uns, wenn es irgendeine Möglichkeit gibt, dass dies eine schlechte Praxis ist, damit ich es erwähnen kann):

  1. Zunächst haben wir ein Job-Modell, das def execute! (das ausführt, was es tun soll).
  2. Als nächstes haben wir verzögerter_Job Arbeiter im Hintergrund, der nach neuen Aufträgen Ausschau hält. Wenn Sie nun einen Auftrag erstellen, können Sie ihn so planen, dass er sofort oder an einem bestimmten Tag ausgeführt wird (wir verwenden hierfür rufus)
  3. Wenn ein Auftrag erstellt wird, wird geprüft, ob er sofort ausgeführt werden soll. Wenn ja, fügt er sich in die Warteschlange für verzögerte Aufträge ein. Die Funktion execute erzeugt einen Thread, so dass jeder Auftrag seinen eigenen Thread hat.
  4. Der Benutzer kann in der Benutzeroberfläche sehen, ob ein Auftrag läuft (wenn es ein started_at und kein finished_at gibt). Wenn er läuft, gibt es eine Schaltfläche, um ihn abzubrechen. Beim Abbrechen wird das canceled_at des Jobs einfach auf Time.now gesetzt.
  5. Während der Auftrag läuft, prüft er auch selbst, ob er ein canceled_at hat oder ob Time.now ist > finished_at . Wenn ja, löschen Sie das Thema.

Voilà! Wir haben es für einen Auftrag getestet und es scheint zu funktionieren. Jetzt ist das einzige Problem die Skalierung...

Wenn ihr irgendwelche Probleme damit seht, dann schreibt es bitte in die Kommentare oder macht weitere Vorschläge, wenn überhaupt :) Ich hoffe, das hilft auch jemandem!

2voto

Tim Snowhite Punkte 3658

Delayed::Job ist ein < ActiveRecord::Base Modell, so dass Sie es wie gewohnt abfragen können Delayed::Job.all(:conditions => {:last_error => nil}) .

Delayed::Job-Objekte haben ein Payload-Feld, das eine serialisierte Version der Methode oder des Jobs enthält, die Sie ausführen wollen. Der Zugriff auf dieses Objekt erfolgt über die Methode "#payload_object", die das betreffende Objekt lädt.

Sie können diese beiden Fähigkeiten kombinieren, um abfragbare Job-Worker zu erstellen, zum Beispiel, wenn Sie eine User Modell, und der Benutzer hat eine Büroklammer :avatar können Sie eine Methode zum Löschen nicht verarbeiteter Aufträge entwickeln:

class User < ActiveRecord::Base
   has_attached_file :avatar, PaperclipOptions.new(:avatar)
   before_create :'process_avatar_later'

   def process_avatar_later
      filename = Rails.root.join('tmp/avatars_for_processing/',self.id)
      open(filename, 'w') do |file| file <<self.avatar.to_file end
      Delayed::Job.enqueue(WorkAvatar.new(self.id, filename))
      self.avatar = nil
   end

   def cancel_future_avatar_processing
      WorkAvatar.future_jobs_for_user(self.id).each(&:destroy)
      #ummm... tell them to reupload their avatar, I guess?
   end

   class WorkAvatar < Struct.new(:user_id, :path)
     def user
        @user ||= User.find(self.user_id)
     end
     def self.all_jobs
       Delayed::Job.scoped(:conditions => 'payload like "%WorkAvatar%"')
     end
     def self.future_jobs_for_user(user_id)
       all_jobs.scoped(:conditions => {:locked_at => nil}).select do |job|
          job.payload_object.user_id == user_id
       end
     end  
     def perform
        @user.avatar = File.open(path, 'rb')
        @user.save()
     end
   end          
end

Es ist möglich, dass jemand ein Plugin erstellt hat, das solche Objekte abfragbar macht. Vielleicht Suche auf GitHub wäre fruchtbar.

Beachten Sie auch, dass Sie mit Prozessüberwachungswerkzeugen arbeiten müssen, um laufende Job-Worker-Prozesse abzubrechen, wenn Sie einen Job abbrechen wollen, der bereits locked_at y locked_by gesetzt.

1voto

Simone Carletti Punkte 168374

Sie können die Aufgabe in eine Timeout Erklärung.

require 'timeout'

class TaskWithTimeout < Struct.new(:parameter)
  def perform
    Timeout.timeout(10) do
      # ...
    end
  rescue Timeout::Error => e
    # the task took longer than 10 seconds
  end
end

0voto

betamatt Punkte 500

Nein, es gibt keine Möglichkeit, dies zu tun. Wenn Sie sich Sorgen machen, dass ein Job aus dem Ruder läuft, sollten Sie ihn auf jeden Fall in eine Auszeit verpacken, wie Simone vorschlägt. Es klingt jedoch so, als ob Sie auf der Suche nach etwas mehr sind, aber ich bin mir über Ihr Endziel nicht im Klaren.

Es wird nie eine Möglichkeit geben, dass ein Benutzer eine "Abbrechen"-Schaltfläche hat, da dafür eine Methode gefunden werden müsste, um direkt mit dem Worker-Prozess zu kommunizieren, der den Auftrag ausführt. Es wäre möglich, dem Worker einen Signalhandler hinzuzufügen, so dass man etwas wie kill -USR1 pid machen könnte, damit er den Job, an dem er gerade arbeitet, abbricht und weitermacht. Würde dies Ihr Ziel erreichen?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X