Skip to content

Commit d16c750

Browse files
author
juhlig
committed
Worker agents
1 parent d099c59 commit d16c750

21 files changed

+1120
-663
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ ebin/test
1010
ebin/*.beam
1111
logs
1212
test/*.beam
13+
cover
14+
test/ct.cover.spec

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
PROJECT = hnc
44
PROJECT_DESCRIPTION = hnc - Erlang Worker Pool
5-
PROJECT_VERSION = 0.3.0
5+
PROJECT_VERSION = 0.4.0
66

77
CT_OPTS += -pa ebin -pa test -ct_hooks hnc_ct_hook []
88

README.md

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# hnc (Agency) - Erlang worker pool
22

3+
`hnc` is an Erlang worker pool application,
4+
focussing on robustness and performance.
5+
6+
## Usage Example
7+
38
```erlang
49
%% Start hnc application.
510
application:ensure_all_started(hnc).
@@ -8,17 +13,48 @@ application:ensure_all_started(hnc).
813
{ok, _}=hnc:start_pool(my_pool, #{}, my_worker, []).
914

1015
%% Check out a worker.
11-
WorkerRef=hnc:checkout(my_pool).
16+
Ref=hnc:checkout(my_pool).
1217

1318
%% Get the worker pid from the identifier.
14-
Worker=hnc:get_worker(WorkerRef).
19+
Worker=hnc:get_worker(Ref).
1520

1621
%% Do some stuff with the worker.
17-
my_worker:do_stuff(Worker).
22+
Stuff=my_worker:do_stuff(Worker).
1823

1924
%% Check worker back in.
20-
ok=hnc:checkin(my_pool, WorkerRef).
25+
ok=hnc:checkin(my_pool, Ref).
26+
27+
%% Or use a transaction to combine checking out,
28+
%% doing stuff, and checking back in into one
29+
%% convenient function.
30+
Stuff=hnc:transaction(
31+
my_pool,
32+
fun (Worker) -> my_worker:do_stuff(Worker) end
33+
).
2134

2235
%% Stop the pool
2336
ok=hnc:stop_pool(my_pool).
2437
```
38+
39+
## Usage as a dependency
40+
41+
### `Erlang.mk`
42+
43+
```
44+
DEPS = hnc
45+
dep_hnc = git https://github.com/hnc-agency/hnc 0.4.0
46+
```
47+
48+
### `rebar3`
49+
50+
```
51+
{deps, [
52+
{hnc, ".*", {git, "https://github.com/hnc-agency/hnc",
53+
{tag, "0.4.0"}}}
54+
]}.
55+
```
56+
57+
## Authors
58+
59+
* Jan Uhlig (`juhlig`)
60+
* Maria Scott (`Maria-12648430`)

doc/src/guide/advanced.asciidoc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,3 @@ second element specifies the interval (in milliseconds) between such checks.
6161
given time.
6262
* It will always keep (ie, not stop) the minimum amount of workers specified in the `size`
6363
configuration option.
64-
* When stopping workers, the cleanup will take the setting of the `strategy` configuration
65-
option into consideration, ie it will preferrably stop those workers that are the least
66-
likely to be checked out again. Specifically, with the `fifo` strategy it will stop those
67-
workers that returned last, while with the `lifo` strategy it will stop those workers
68-
that returned first.

doc/src/guide/changelog.asciidoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,26 @@ Mostly refactoring and cleanup, and some new features.
3838

3939
* The pool does not wait for the initial workers to complete their
4040
startup before becoming operationali any more.
41+
42+
=== 0.4.0
43+
44+
Large refactoring and bugfixes.
45+
46+
* Workers are paired with agents that watch and take care of them,
47+
removing complexity from and reducing resource consumption of the
48+
pool process.
49+
50+
* The new `restart` option can be used to control if agents should
51+
transparently restart failed workers.
52+
53+
* The new `random` strategy can be used to check out workers in
54+
a random fashion.
55+
56+
* The strategy is no longer considered to select workers for pool
57+
shrinking.
58+
59+
* The functions `checkin`, `give_away`, and `get_worker` raise an
60+
error exception if the calling process is not the current owner
61+
of the worker.
62+
63+
* Augmented tests.

doc/src/guide/embedded.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ in the `Usage` chapter.
3535
----
3636
{ok, _} = my_pool_sup:start_link().
3737
38-
WorkerRef = hnc:checkout(my_pool).
39-
ok = hnc:checkin(WorkerRef).
38+
Ref = hnc:checkout(my_pool).
39+
ok = hnc:checkin(Ref).
4040
4141
Result = hnc:transaction(my_other_pool, fun do_stuff/1).
4242
----

doc/src/guide/misc.asciidoc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,3 @@ their start phase before it can be started.
1212
Still, you may want to keep the startup phases of your workers
1313
as short as possible, since a user will still have to wait for
1414
that amount of time before it receives a newly started worker.
15-
16-
To achieve short startup phases, you may consider returning
17-
the worker pid from the respective `start_link/1` function
18-
immediately, and do any initialization in a separate step,
19-
maybe even when the worker is being used for the first time.

doc/src/guide/usage.asciidoc

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ The pool identifier, as given in `start_pool/4`.
7474

7575
[source,erlang]
7676
----
77-
WorkerRef = hnc:checkout(PoolName, Timeout).
77+
Ref = hnc:checkout(PoolName, Timeout).
7878
----
7979

8080
`PoolName`::
@@ -84,31 +84,31 @@ The pool identifier, as given in `start_pool/4`.
8484
The maximum time allowed for the checkout. This
8585
argument is optional and defaults to `infinity`.
8686

87-
`WorkerRef`::
87+
`Ref`::
8888
The identifier of a worker from the pool specified by `PoolName`.
8989

9090
The return value is a worker _identifier_ (to be used when checking
9191
it back in, giving it away, or querying it's status). To get the actual
92-
worker _pid_ to perform a task with, `hnc:get_worker(WorkerRef)` must be
92+
worker _pid_ to perform a task with, `hnc:get_worker(Ref)` must be
9393
used.
9494

9595
==== Checking in a worker
9696

9797
[source,erlang]
9898
----
99-
Result = hnc:checkin(WorkerRef, Timeout).
99+
ok = hnc:checkin(Ref, Timeout).
100100
----
101101

102-
`WorkerRef`::
102+
`Ref`::
103103
The identifier of the worker to be returned, as returned by `checkout/1,2`.
104104

105105
`Timeout`::
106106
The maximum time allowed for the checkin. This argument
107107
is optional and defaults to `infinity`.
108108

109-
`Result`::
110-
The result of the checkin operation, either `ok` on success, or `{error, not_owner}`
111-
if the process doing the checkin is not the current owner of the worker.
109+
This function returns `ok` on success, or raises an error exception with
110+
reason `not_owner` if the process doing the checkin is not the current
111+
owner of the worker.
112112

113113
==== Transactions
114114

@@ -150,15 +150,15 @@ and the worker will not be checked in when the original process dies.
150150

151151
The process calling this function must be the current owner of the worker.
152152

153-
The process receiving the worker is sent a message `{'HNC-WORKER-TRANSFER', WorkerRef, FromPid, GiftData}`.
153+
The process receiving the worker is sent a message `{'HNC-WORKER-TRANSFER', Ref, FromPid, GiftData}`.
154154

155155
[source,erlang]
156156
----
157-
Result = hnc:give_away(WorkerRef, OtherProcess, GiftData).
158-
Result = hnc:give_away(WorkerRef, OtherProcess, GiftData, Timeout).
157+
ok = hnc:give_away(Ref, OtherProcess, GiftData).
158+
ok = hnc:give_away(Ref, OtherProcess, GiftData, Timeout).
159159
----
160160

161-
`WorkerRef`::
161+
`Ref`::
162162
The identifier of the worker to be given away, as returned by `checkout/1,2`.
163163

164164
`OtherProcess`::
@@ -170,9 +170,9 @@ Arbitrary term to send along with the transfer message.
170170
`Timeout`::
171171
The maximum time allowed for the worker transfer operation.
172172

173-
`Result`::
174-
The result of the operation, either `ok` on success, or `{error, not_owner}`
175-
if the process calling this function is not the current owner of the worker.
173+
This function returns `ok` on success, or raises and error exception
174+
with reason `not_owner` if the process calling this function is not
175+
the current owner of the worker.
176176

177177
=== Runtime configuration
178178

@@ -291,20 +291,19 @@ remains in the pool.
291291

292292
[source,erlang]
293293
----
294-
WorkerStatus = hnc:worker_status(WorkerRef, Timeout).
294+
WorkerStatus = hnc:worker_status(Ref, Timeout).
295295
----
296296

297-
`WorkerRef`::
297+
`Ref`::
298298
The identifier of the worker whose status to retrieve, as returned by `checkout/1,2`.
299299

300300
`Timeout`::
301301
The maximum time allowed to fetch the status. This
302302
argument is optional and defaults to `5000`.
303303

304304
`WorkerStatus`::
305-
The current status of the worker, either `idle`,
306-
`out`, or `returning`. If the given worker is not
307-
known to the pool, `undefined` is returned.
305+
The current status of the worker, either `starting`,
306+
`idle`, `out`, `returning`, or `restarting`.
308307

309308
==== Pool status
310309

doc/src/manual/hnc.asciidoc

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ interacting with pools.
4242

4343
[source,erlang]
4444
----
45-
worker_ref() = term()
45+
ref() = term()
4646
4747
pool() = atom()
4848
@@ -56,23 +56,25 @@ strategy() = fifo | lifo
5656
5757
linger() = infinity | {MaxLinger :: non_neg_integer(), Interval :: non_neg_integer()}
5858
59+
restart() = never | out | always.
60+
5961
on_return() = undefined | {fun((worker()) -> any()), timeout()}
6062
6163
shutdown() = timeout() | brutal_kill
6264
63-
worker_status() = idle | out | returning
65+
worker_status() = starting | idle | out | returning | restarting
6466
6567
pool_status() = #{
66-
idle := non_neg_integer(),
67-
out := non_neg_integer(),
68-
starting := non_neg_integer(),
69-
returning := non_neg_integer()
68+
available := non_neg_integer(),
69+
unavailable := non_neg_integer(),
70+
starting := non_neg_integer()
7071
}
7172
7273
opts() = #{
7374
size => size(),
7475
strategy => strategy(),
7576
linger => linger(),
77+
restart => restart(),
7678
on_return => on_return(),
7779
shutdown => shutdown()
7880
}
@@ -145,6 +147,20 @@ specified by the `size` option.
145147
If set to `infinity` instead, workers never expire, and the pool may
146148
eventually grow to the maximum number defined by the `size` option.
147149

150+
restart (never)::
151+
152+
Worker restart strategy of the pool.
153+
154+
If set to `never`, failing workers will not be restarted by their
155+
agents, and the agents themselves will fail.
156+
157+
If set to `out`, failing workers will be restarted by their agents
158+
if they are checked out, but not if they are idle, in which case
159+
the agents themselves will fail.
160+
161+
If set to `always`, failing workers will be transparently restarted
162+
by their agents, no matter if they are checked out or idle.
163+
148164
on_return (undefined)::
149165

150166
Function to be called when a worker returns to the pool.
@@ -230,7 +246,7 @@ WorkerRef = hnc:checkout(PoolName, Timeout).
230246

231247
PoolName = pool():: Pool identifier as given in `start_pool/4`.
232248
Timeout = timeout():: Maximum time to wait for the checkout to succeed.
233-
WorkerRef = worker_ref():: The identifier of the worker that was checked out from the pool.
249+
WorkerRef = ref():: The identifier of the worker that was checked out from the pool.
234250

235251
Checks out a worker from the pool. If the pool has `idle` workers available,
236252
it will return one of them. Which of the available workers is picked depends
@@ -250,14 +266,12 @@ worker.
250266

251267
[source,erlang]
252268
----
253-
Result = hnc:checkin(WorkerRef).
254-
Result = hnc:checkin(WorkerRef, Timeout).
269+
ok = hnc:checkin(WorkerRef).
270+
ok = hnc:checkin(WorkerRef, Timeout).
255271
----
256272

257273
WorkerRef = worker_ref():: The identifier of the worker to be checkedi back in, as returned by `checkout/1,2`.
258274
Timeout = timeout():: Maximum time to wait for the checkin to succeed.
259-
Result = ok | {error, Reason}:: The result of the `checkin` operation.
260-
Reason = not_owner | not_found:: If checking in failed, the reason for the failure.
261275

262276
Returns the worker identified by the given `WorkerRef` to the pool.
263277

@@ -269,7 +283,7 @@ it in the `on_return` option, the worker is killed and removed from the pool, as
269283
it is then assumed to be in an undefined state.
270284

271285
Returns `ok` on success. If the process doing the checkin is not the current owner
272-
of the worker, `{error, not_owner}` is returned.
286+
of the worker identifier, an error exception with reason `not_owner` is raised.
273287

274288
=== Performing a transaction
275289

@@ -298,22 +312,20 @@ If you want the worker to remain checked out, you may give it away to another pr
298312

299313
[source,erlang]
300314
----
301-
Result = hnc:give_away(WorkerRef, NewUser, GiftData).
302-
Result = hnc:give_away(WorkerRef, NewUser, GiftData, Timeout).
315+
ok = hnc:give_away(WorkerRef, NewUser, GiftData).
316+
ok = hnc:give_away(WorkerRef, NewUser, GiftData, Timeout).
303317
----
304318

305-
WorkerRef = worker_ref():: The identifier of the worker to be transferred to `NewUser`, as returned by `checkout/1,2`.
319+
WorkerRef = ref():: The identifier of the worker to be transferred to `NewUser`, as returned by `checkout/1,2`.
306320
NewUser = pid():: The pid of the process to give the worker to.
307321
GiftData = term():: Additional data to send to the new user.
308322
Timeout = timeout():: Maximum time to wait before a worker becomes available.
309-
Result = ok | {error, Reason}:: The result of the `give_away` operation.
310-
Reason = not_owner | not_found:: If giving away failed, the reason for the failure.
311323

312324
On success, `ok` is returned. Additionally, the new owner process is sent a message
313325
`{'HNC-WORKER-TRANSFER', WorkerRef, FromPid, GiftData}`.
314326

315-
If the process calling this function is not the owner of the worker, `{error, not_owner}`
316-
is returned.
327+
If the process calling this function is not the owner of the worker identifier, an error exception
328+
with reason `not_owner` is raised.
317329

318330
=== Getting and setting the checkout strategy
319331

@@ -384,9 +396,9 @@ Status = hnc:worker_status(WorkerRef).
384396
Status = hnc:worker_status(WorkerRef, Timeout).
385397
----
386398

387-
WorkerRef = worker_ref():: The identifier of the worker whose status to query.
399+
WorkerRef = ref():: The identifier of the worker whose status to query.
388400
Timeout = timeout():: Maximum time to wait.
389-
Status = worker_status() | undefined:: The status of the given worker of the given pool.
401+
Status = worker_status():: The status of the given worker of the given pool.
390402

391403
Retrieve the status of the given worker.
392404

@@ -408,7 +420,6 @@ Status = pool_status():: The status of the given pool.
408420

409421
Retrieve the pool status in a map.
410422

411-
* `idle`: number of checked in (available) workers.
412-
* `out`: number of checked out (not available) workers.
423+
* `available`: number of available workers.
424+
* `unavailable`: number of unavailable workers.
413425
* `starting`: number of workers that are in the process of being started (not yet available).
414-
* `returning`: number of workers that are in the process of returning (not yet available).

ebin/hnc.app

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{application, 'hnc', [
22
{description, "hnc - Erlang Worker Pool"},
3-
{vsn, "0.3.0"},
4-
{modules, ['hnc','hnc_app','hnc_pool','hnc_pool_sup','hnc_sup','hnc_worker','hnc_worker_sup','hnc_worker_sup_sup']},
3+
{vsn, "0.4.0"},
4+
{modules, ['hnc','hnc_agent','hnc_app','hnc_pool','hnc_pool_sup','hnc_sup','hnc_worker','hnc_worker_sup','hnc_worker_sup_sup']},
55
{registered, [hnc_sup]},
66
{applications, [kernel,stdlib]},
77
{mod, {hnc_app, []}},

0 commit comments

Comments
 (0)