ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

Erlang Fault-Tolerant Worker Pool with Supervision

Erlang

Goal -- WPM

Ready
Exercise Algorithm Area
1-module(task_supervisor).
2
3-export([start_link/2, stop/1, add_task/2, get_result/1]).
4
5-behavior(supervisor).
6
7%% Supervisor API
8start_link(PoolSize, MaxQueueSize) ->
9WorkerSpec = #{id => worker,
10start => {worker_process, start_link, [MaxQueueSize]},
11restart => permanent,
12shutdown => brutal_kill,
13type => worker},
14
15SupSpec = #{id => task_supervisor,
16start => {supervisor, start_link, [Self, [WorkerSpec]]},
17restart => permanent,
18shutdown => infinity},
19
20%% Start the supervisor with the worker pool
21supervisor:start_link(SupSpec, []).
22
23stop(SupPid) ->
24supervisor:terminate_child(SupPid, worker).
25
26%% Task Management API (interacts with the worker pool)
27add_task(SupPid, TaskData) ->
28%% Find a worker to add the task to
29case supervisor:which_children(SupPid) of
30[] -> {error, no_workers_available};
31Children ->
32%% Simple round-robin or random selection could be implemented here.
33%% For simplicity, we'll try to send to the first available worker.
34{_, WorkerPid} = hd(Children), % Assuming only one worker type for simplicity
35gen_server:cast(WorkerPid, {add_task, TaskData})
36end.
37
38get_result(SupPid) ->
39%% This is a simplified example. In a real system, results would likely be
40%% associated with tasks and retrieved via a separate mechanism or by
41%% the task submitter. Here, we'll ask a worker for a sample result.
42case supervisor:which_children(SupPid) of
43[] -> {error, no_workers_available};
44Children ->
45{_, WorkerPid} = hd(Children),
46gen_server:call(WorkerPid, get_result)
47end.
48
49
50%% Worker Process (GenServer)
51-module(worker_process).
52
53-export([start_link/1]).
54
55-behavior(gen_server).
56
57start_link(MaxQueueSize) ->
58gen_server:start_link({local, ?MODULE}, ?MODULE, [MaxQueueSize], []).
59
60init(MaxQueueSize) ->
61%% State: {Queue, MaxQueueSize, Results}
62{ok, {[], MaxQueueSize, #{}}}.
63
64handle_call({add_task, TaskData}, _From, {Queue, MaxQ, Results}) ->
65%% Check if queue is full
66if length(Queue) >= MaxQ ->
67{reply, {error, queue_full}, {Queue, MaxQ, Results}};
68else ->
69NewQueue = Queue ++ [TaskData],
70{reply, ok, {NewQueue, MaxQ, Results}}
71end;
72
73handle_call(get_result, _From, {Queue, MaxQ, Results}) ->
74case Queue of
75[] -> {reply, {error, no_tasks}, {Queue, MaxQ, Results}};
76[TaskData | RestQueue] ->
77%% Simulate task processing
78Result = io_lib:format("Processed: ~p", [TaskData]),
79NewResults = maps:put(TaskData, Result, Results), % Simple mapping for demonstration
80{reply, {ok, Result}, {RestQueue, MaxQ, NewResults}}
81end;
82
83handle_call(Other, _From, State) ->
84io:format("Worker received unexpected call: ~p~n", [Other]),
85{reply, {error, unknown_command}, State}.
86
87handle_cast({add_task, TaskData}, {Queue, MaxQ, Results}) ->
88%% This is a duplicate of handle_call for add_task, but for cast messages.
89%% In a real scenario, you might want to distinguish between sync and async adds.
90if length(Queue) >= MaxQ ->
91io:format("Worker queue full, dropping task: ~p~n", [TaskData]),
92{noreply, {Queue, MaxQ, Results}};
93else ->
94NewQueue = Queue ++ [TaskData],
95{noreply, {NewQueue, MaxQ, Results}}
96end;
97
98handle_cast(Other, State) ->
99io:format("Worker received unexpected cast: ~p~n", [Other]),
100{noreply, State}.
101
102handle_info(_Info, State) ->
103{noreply, State}.
104
105terminate(_Reason, _State) ->
106ok.
107
108code_change(_OldVsn, State, _Extra) ->
109{ok, State}.
Algorithm description viewbox

Erlang Fault-Tolerant Worker Pool with Supervision

Algorithm description:

This Erlang code defines a fault-tolerant worker pool managed by a supervisor. The supervisor (`task_supervisor`) starts and monitors multiple `worker_process` GenServers. Each worker maintains a queue of tasks and processes them. If a worker crashes, the supervisor restarts it, ensuring the system remains available. Tasks are added to a worker's queue, and results can be retrieved. This pattern is crucial for building robust, scalable, and self-healing distributed systems in Erlang.

Algorithm explanation:

The `task_supervisor` module acts as a supervisor for a pool of `worker_process` GenServers. The `start_link/2` function defines the supervisor's strategy, specifying that workers should be restarted permanently if they crash (`restart => permanent`). The `worker_process` is a GenServer that manages a task queue and a map for results. The `init/1` function sets up the initial state: an empty list for the queue, the maximum queue size, and an empty map for results. `handle_call/3` and `handle_cast/2` are used to add tasks to the queue, with checks for queue fullness. `handle_call/3` also handles `get_result` requests, simulating task processing and returning a result. The `restart => permanent` strategy ensures that if a worker crashes, the supervisor will automatically restart it, maintaining the desired number of workers. The time complexity for adding a task is O(N) in the worst case due to list concatenation, where N is the queue size. Retrieving a result is O(1) on average if the queue is not empty. Space complexity is O(M*K) where M is the number of workers and K is the maximum queue size per worker.

Pseudocode:

Supervisor:
  Define worker specification (GenServer, permanent restart).
  Start supervisor with worker specifications.

Worker (GenServer):
  Initialize state: empty queue, max queue size, empty results map.
  Handle 'add_task' call/cast:
    If queue is full, reply/noreply with error.
    Else, append task to queue, reply/noreply with success.
  Handle 'get_result' call:
    If queue is empty, reply with error.
    Else, dequeue task, simulate processing, store result, reply with result.
  Handle other messages: log and ignore.
  Terminate gracefully.