2016-02-12 11 views
10

Ho un flusso di lavoro che comporta il risveglio ogni 30 secondi circa e il polling di un database per gli aggiornamenti, intervenire su quello, quindi tornare a dormire. Accantonando il fatto che il polling del database non scala e altre preoccupazioni simili, qual è il modo migliore per strutturare questo flusso di lavoro utilizzando Supervisori, lavoratori, Attività e così via?Elixir corretto modo OTP per strutturare un'attività a ciclo infinito

Esporrò alcune idee che ho avuto e i miei pensieri per/contro. Per favore aiutami a capire l'approccio più elisir-y. (Sono ancora molto nuovo a Elixir, btw.)

1. Infinite Loop Through Function Call

Basta mettere un semplice ciclo ricorsivo in là, in questo modo:

def do_work() do 
    # Check database 
    # Do something with result 
    # Sleep for a while 
    do_work() 
end 

I ho visto qualcosa di simile quando ho seguito insieme a tutorial on building a web crawler.

Una preoccupazione che ho qui è la profondità della catasta infinita dovuta alla ricorsione. Questo alla fine non causerà un overflow dello stack dal momento che ricorreremo alla fine di ogni ciclo? Questa struttura è utilizzata in the standard Elixir guide for Tasks, quindi probabilmente mi sbaglio sul problema dello stack overflow.

Aggiornamento - Come menzionato nelle risposte, tail call recursion in Elisir significa che gli overflow dello stack non sono un problema qui. I loop che si chiamano alla fine sono un modo accettato di fare loop infinito.

2. Utilizzare un task, Riavvia Ogni Tempo

L'idea di base è quella di utilizzare un compito che viene eseguito una volta e poi esce, ma accoppiarlo con un supervisore con una strategia one-to-one riavvio, quindi diventa riavviato ogni volta che viene completato. L'attività controlla il database, dorme, quindi esce. Il Supervisore vede l'uscita e ne inizia una nuova.

Questo ha il vantaggio di vivere all'interno di un Supervisore, ma sembra un abuso del Supervisore. Viene utilizzato per il loop in aggiunta al trapping degli errori e al riavvio.

(Nota:. Probabilmente c'è qualcosa che può essere fatto con Task.Supervisor, in contrasto con il normale Supervisore e non sto solo capirla)

3. Task + Infinite Loop ricorsione

Fondamentalmente, combinare 1 e 2 in modo che sia un compito che utilizza un ciclo di ricorsione infinito. Ora è gestito da un Supervisore e si riavvierà se si è arrestato in modo anomalo, ma non si riavvia più e più volte come una normale parte del flusso di lavoro. Questo è attualmente il mio approccio preferito.

4. Altro?

La mia preoccupazione è che ci siano alcune strutture OTP fondamentali che mi mancano. Ad esempio, ho familiarità con Agent e GenServer, ma recentemente mi sono imbattuto in Task. Forse c'è un qualche tipo di Looper esattamente per questo caso, o qualche caso d'uso di Task.Supervisor che lo copre.

risposta

6

ho da poco iniziato a utilizzare OTP, ma credo di essere in grado di darvi alcune indicazioni:

  1. Questo è il modo elisir di fare questo, ho preso una citazione da programmazione Elixir di Dave Thomas come spiega meglio di me:

    La funzione di saluto ricorsivo potrebbe averti preoccupato un po '. Ogni volta che riceve un messaggio, finisce per chiamarsi. In molte lingue , questo aggiunge una nuova cornice allo stack. Dopo un numero elevato di messaggi , potresti esaurire la memoria. Ciò non accade in Elixir, , poiché implementa l'ottimizzazione della coda di chiamata. Se l'ultima cosa che fa una funzione è la chiamata stessa, non è necessario effettuare la chiamata. Invece, il runtime può semplicemente tornare all'inizio della funzione . Se la chiamata ricorsiva ha argomenti, questi sostituiscono i parametri originali mentre si verifica il ciclo.

  2. Le attività (come nel modulo Attività) sono pensate per una singola attività, processi di breve durata, quindi possono essere ciò che si desidera. In alternativa, perché non avere un processo che viene generato (magari all'avvio) per avere quell'attività e averlo in loop e accedere al DB ogni volta x?
  3. e 4, magari esaminare l'utilizzo di un GenServer con la seguente architettura Supervisor -> GenServer -> Workers spawning quando necessario per l'attività (qui si può semplicemente usare spawn fn -> ... end, non serve davvero preoccuparsi di scegliere Task o un altro modulo) e poi uscire quando finito.
3

Penso che il modo generalmente accettato di fare ciò che stai cercando sia l'approccio n. 1. Poiché Erlang ed Elixir ottimizzano automaticamente tail calls, non è necessario preoccuparsi dell'overflow dello stack.

2

C'è un altro modo con Stream.cycle. Ecco un esempio di macro mentre

defmodule Loop do 

    defmacro while(expression, do: block) do 
    quote do 
     try do 
     for _ <- Stream.cycle([:ok]) do 
      if unquote(expression) do 
      unquote(block) 
      else 
      throw :break 
      end 
     end 
     catch 
     :break -> :ok 
     end 
    end 
    end 
end 
2

userei GenServer e in init di ritorno della funzione

{:ok, <state>, <timeout_in_ milliseconds>} 

Impostazione timeout provoca che la funzione handle_info viene chiamata quando viene raggiunto il timeout.

E posso verificare che questo processo sia in esecuzione aggiungendolo al supervisore del progetto principale.

Questo è un esempio di come può essere utilizzato:

defmodule MyApp.PeriodicalTask do 
    use GenServer 

    @timeout 50_000 

    def start_link do 
    GenServer.start_link(__MODULE__, [], name: __MODULE__) 
    end 

    def init(_) do 
    {:ok, %{}, @timeout} 
    end 

    def handle_info(:timeout, _) do 
    #do whatever I need to do 
    {:noreply, %{}, @timeout} 
    end 
end 
6

Sono un po 'in ritardo qui, ma per quelli di voi ancora cercando il modo giusto per farlo, penso che vale la pena menzionare il GenServer documentation stessa:

handle_info/2 può essere utilizzato in molte situazioni, come la gestione monitorare GIÙ messaggi inviati da Process.monitor/1. Un altro caso d'uso per handle_info/2 è quello di svolgere un lavoro periodico, con l'aiuto di Process.send_after/4:

defmodule MyApp.Periodically do 
    use GenServer 

    def start_link do 
     GenServer.start_link(__MODULE__, %{}) 
    end 

    def init(state) do 
     schedule_work() # Schedule work to be performed on start 
     {:ok, state} 
    end 

    def handle_info(:work, state) do 
     # Do the desired work here 
     schedule_work() # Reschedule once more 
     {:noreply, state} 
    end 

    defp schedule_work() do 
     Process.send_after(self(), :work, 2 * 60 * 60 * 1000) # In 2 hours 
    end 
end 
+0

Sì, ho fatto questo modello esatto in pochi luoghi. Lo raccomando sicuramente se vuoi che un'attività periodica si verifichi in qualcosa che è già un GenServer – Micah

Problemi correlati