Replace Pleroma.Jobs with `pleroma_job_queue` See merge request pleroma/pleroma!992stable
commit
a5edc4da62
@ -1,152 +0,0 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Jobs do
|
||||
@moduledoc """
|
||||
A basic job queue
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
def start_link do
|
||||
queues =
|
||||
Pleroma.Config.get(Pleroma.Jobs)
|
||||
|> Enum.map(fn {name, _} -> create_queue(name) end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
state = %{
|
||||
queues: queues,
|
||||
refs: %{}
|
||||
}
|
||||
|
||||
GenServer.start_link(__MODULE__, state, name: __MODULE__)
|
||||
end
|
||||
|
||||
def create_queue(name) do
|
||||
{name, {:sets.new(), []}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enqueues a job.
|
||||
|
||||
Returns `:ok`.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `queue_name` - a queue name(must be specified in the config).
|
||||
- `mod` - a worker module (must have `perform` function).
|
||||
- `args` - a list of arguments for the `perform` function of the worker module.
|
||||
- `priority` - a job priority (`0` by default).
|
||||
|
||||
## Examples
|
||||
|
||||
Enqueue `Module.perform/0` with `priority=1`:
|
||||
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [])
|
||||
:ok
|
||||
|
||||
Enqueue `Module.perform(:job_name)` with `priority=5`:
|
||||
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:job_name], 5)
|
||||
:ok
|
||||
|
||||
Enqueue `Module.perform(:another_job, data)` with `priority=1`:
|
||||
|
||||
iex> data = "foobar"
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:another_job, data])
|
||||
:ok
|
||||
|
||||
Enqueue `Module.perform(:foobar_job, :foo, :bar, 42)` with `priority=1`:
|
||||
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:foobar_job, :foo, :bar, 42])
|
||||
:ok
|
||||
|
||||
"""
|
||||
|
||||
def enqueue(queue_name, mod, args, priority \\ 1)
|
||||
|
||||
if Mix.env() == :test do
|
||||
def enqueue(_queue_name, mod, args, _priority) do
|
||||
apply(mod, :perform, args)
|
||||
end
|
||||
else
|
||||
@spec enqueue(atom(), atom(), [any()], integer()) :: :ok
|
||||
def enqueue(queue_name, mod, args, priority) do
|
||||
GenServer.cast(__MODULE__, {:enqueue, queue_name, mod, args, priority})
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast({:enqueue, queue_name, mod, args, priority}, state) do
|
||||
{running_jobs, queue} = state[:queues][queue_name]
|
||||
|
||||
queue = enqueue_sorted(queue, {mod, args}, priority)
|
||||
|
||||
state =
|
||||
state
|
||||
|> update_queue(queue_name, {running_jobs, queue})
|
||||
|> maybe_start_job(queue_name, running_jobs, queue)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
|
||||
queue_name = state.refs[ref]
|
||||
|
||||
{running_jobs, queue} = state[:queues][queue_name]
|
||||
|
||||
running_jobs = :sets.del_element(ref, running_jobs)
|
||||
|
||||
state =
|
||||
state
|
||||
|> remove_ref(ref)
|
||||
|> update_queue(queue_name, {running_jobs, queue})
|
||||
|> maybe_start_job(queue_name, running_jobs, queue)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def maybe_start_job(state, queue_name, running_jobs, queue) do
|
||||
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, queue_name, :max_jobs]) &&
|
||||
queue != [] do
|
||||
{{mod, args}, queue} = queue_pop(queue)
|
||||
{:ok, pid} = Task.start(fn -> apply(mod, :perform, args) end)
|
||||
mref = Process.monitor(pid)
|
||||
|
||||
state
|
||||
|> add_ref(queue_name, mref)
|
||||
|> update_queue(queue_name, {:sets.add_element(mref, running_jobs), queue})
|
||||
else
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_sorted(queue, element, priority) do
|
||||
[%{item: element, priority: priority} | queue]
|
||||
|> Enum.sort_by(fn %{priority: priority} -> priority end)
|
||||
end
|
||||
|
||||
def queue_pop([%{item: element} | queue]) do
|
||||
{element, queue}
|
||||
end
|
||||
|
||||
defp add_ref(state, queue_name, ref) do
|
||||
refs = Map.put(state[:refs], ref, queue_name)
|
||||
Map.put(state, :refs, refs)
|
||||
end
|
||||
|
||||
defp remove_ref(state, ref) do
|
||||
refs = Map.delete(state[:refs], ref)
|
||||
Map.put(state, :refs, refs)
|
||||
end
|
||||
|
||||
defp update_queue(state, queue_name, data) do
|
||||
queues = Map.put(state[:queues], queue_name, data)
|
||||
Map.put(state, :queues, queues)
|
||||
end
|
||||
end
|
@ -1,83 +0,0 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.JobsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Jobs.WorkerMock
|
||||
alias Pleroma.Jobs
|
||||
|
||||
setup do
|
||||
state = %{
|
||||
queues: Enum.into([Jobs.create_queue(:testing)], %{}),
|
||||
refs: %{}
|
||||
}
|
||||
|
||||
[state: state]
|
||||
end
|
||||
|
||||
test "creates queue" do
|
||||
queue = Jobs.create_queue(:foobar)
|
||||
|
||||
assert {:foobar, set} = queue
|
||||
assert :set == elem(set, 0) |> elem(0)
|
||||
end
|
||||
|
||||
test "enqueues an element according to priority" do
|
||||
queue = [%{item: 1, priority: 2}]
|
||||
|
||||
new_queue = Jobs.enqueue_sorted(queue, 2, 1)
|
||||
assert new_queue == [%{item: 2, priority: 1}, %{item: 1, priority: 2}]
|
||||
|
||||
new_queue = Jobs.enqueue_sorted(queue, 2, 3)
|
||||
assert new_queue == [%{item: 1, priority: 2}, %{item: 2, priority: 3}]
|
||||
end
|
||||
|
||||
test "pop first item" do
|
||||
queue = [%{item: 2, priority: 1}, %{item: 1, priority: 2}]
|
||||
|
||||
assert {2, [%{item: 1, priority: 2}]} = Jobs.queue_pop(queue)
|
||||
end
|
||||
|
||||
test "enqueue a job", %{state: state} do
|
||||
assert {:noreply, new_state} =
|
||||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state)
|
||||
|
||||
assert %{queues: %{testing: {running_jobs, []}}, refs: _} = new_state
|
||||
assert :sets.size(running_jobs) == 1
|
||||
assert [ref] = :sets.to_list(running_jobs)
|
||||
assert %{refs: %{^ref => :testing}} = new_state
|
||||
end
|
||||
|
||||
test "max jobs setting", %{state: state} do
|
||||
max_jobs = Pleroma.Config.get([Jobs, :testing, :max_jobs])
|
||||
|
||||
{:noreply, state} =
|
||||
Enum.reduce(1..(max_jobs + 1), {:noreply, state}, fn _, {:noreply, state} ->
|
||||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state)
|
||||
end)
|
||||
|
||||
assert %{
|
||||
queues: %{
|
||||
testing:
|
||||
{running_jobs, [%{item: {WorkerMock, [:test_job, :foo, :bar]}, priority: 3}]}
|
||||
}
|
||||
} = state
|
||||
|
||||
assert :sets.size(running_jobs) == max_jobs
|
||||
end
|
||||
|
||||
test "remove job after it finished", %{state: state} do
|
||||
{:noreply, new_state} =
|
||||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state)
|
||||
|
||||
%{queues: %{testing: {running_jobs, []}}} = new_state
|
||||
[ref] = :sets.to_list(running_jobs)
|
||||
|
||||
assert {:noreply, %{queues: %{testing: {running_jobs, []}}, refs: %{}}} =
|
||||
Jobs.handle_info({:DOWN, ref, :process, nil, nil}, new_state)
|
||||
|
||||
assert :sets.size(running_jobs) == 0
|
||||
end
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Jobs.WorkerMock do
|
||||
require Logger
|
||||
|
||||
def perform(:test_job, arg, arg2) do
|
||||
Logger.debug({:perform, :test_job, arg, arg2})
|
||||
end
|
||||
|
||||
def perform(:test_job, payload) do
|
||||
Logger.debug({:perform, :test_job, payload})
|
||||
end
|
||||
|
||||
def test_job(payload) do
|
||||
Pleroma.Jobs.enqueue(:testing, __MODULE__, [:test_job, payload])
|
||||
end
|
||||
end
|
Loading…
Reference in new issue