Просмотр исходного кода

Added attributes, update_counter() on names, gproc_pool

Ulf Wiger 12 лет назад
Родитель
Сommit
05cd9f8676
20 измененных файлов с 2969 добавлено и 681 удалено
  1. 20 15
      README.md
  2. 20 15
      doc/README.md
  3. 1 1
      doc/edoc-info
  4. 843 334
      doc/gproc.md
  5. 11 11
      doc/gproc_app.md
  6. 20 20
      doc/gproc_bcast.md
  7. 104 82
      doc/gproc_dist.md
  8. 11 7
      doc/gproc_info.md
  9. 15 7
      doc/gproc_init.md
  10. 62 47
      doc/gproc_lib.md
  11. 26 15
      doc/gproc_monitor.md
  12. 481 0
      doc/gproc_pool.md
  13. 142 71
      doc/gproc_ps.md
  14. 10 10
      doc/gproc_sup.md
  15. 1 1
      doc/overview.edoc
  16. 231 29
      src/gproc.erl
  17. 41 9
      src/gproc_dist.erl
  18. 30 6
      src/gproc_lib.erl
  19. 897 0
      src/gproc_pool.erl
  20. 3 1
      src/gproc_sup.erl

+ 20 - 15
README.md

@@ -1,17 +1,17 @@
 
 
-#The gproc application#
-
+# The gproc application #
 
 __Authors:__ Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erlang-solutions.com)), Joseph Wayne Norton ([`norton@geminimobile.com`](mailto:norton@geminimobile.com)).
 
 Extended process dictionary
 
-##Note##
 
+## Note ##
 
 Gproc has two dependencies: `gen_leader` and `edown`. Since most people don't
 actively use either, they are no longer fetched by default.
+
 * To enable fetching of `gen_leader`, export the OS environment variable`GPROC_DIST=true` (this can be done e.g. from a GNU Makefile)
 
 * `edown` is fetched on-demand whenver `rebar get-deps doc` is called (which
@@ -19,10 +19,10 @@ actively use either, they are no longer fetched by default.
 
 
 
-##Introduction##
-
+## Introduction ##
 
 Gproc is a process dictionary for Erlang, which provides a number of useful features beyond what the built-in dictionary has:
+
 * Use any term as a process alias
 
 * Register a process under several aliases
@@ -43,8 +43,7 @@ Gproc is a process dictionary for Erlang, which provides a number of useful feat
 
 
 
-###Use case: System inspection##
-
+### Use case: System inspection ###
 
 Gproc was designed to work as a central index for "process metadata", i.e.
 properties that describe the role and characteristics of each process. Having
@@ -52,11 +51,14 @@ a single registry that is flexible enough to hold important types of property
 makes it easier to (a) find processes of a certain type, and (b) query and
 browse key data in a running system.
 
-###Use case: Pub/Sub patterns##
 
+### Use case: Pub/Sub patterns ###
 
 An interesting application of gproc is building publish/subscribe patterns.
-Example:<pre>
+Example:
+
+```erlang
+
 subscribe(EventType) ->
     %% Gproc notation: {p, l, Name} means {(p)roperty, (l)ocal, Name}
     gproc:reg({p, l, {?MODULE, EventType}}).
@@ -64,10 +66,11 @@ subscribe(EventType) ->
 notify(EventType, Msg) ->
     Key = {?MODULE, EventType},
     gproc:send({p, l, Key}, {self(), Key, Msg}).
-</pre>
 
-###Use case: Environment handling##
+```
+
 
+### Use case: Environment handling ###
 
 Gproc provides a set of functions to read environment variables, possibly from
 alternative sources, and cache them for efficient lookup. Caching also provides
@@ -77,8 +80,8 @@ which values they actually ended up using.
 See [`gproc:get_env/4`](http://github.com/esl/gproc/blob/master/doc/gproc.md#get_env-4), [`gproc:get_set_env/4`](http://github.com/esl/gproc/blob/master/doc/gproc.md#get_set_env-4) and
 [`gproc:set_env/5`](http://github.com/esl/gproc/blob/master/doc/gproc.md#set_env-5) for details.
 
-##Testing##
 
+## Testing ##
 
 Gproc has a QuickCheck test suite, covering a fairly large part of the local
 gproc functionality, although none of the global registry. It requires a
@@ -88,16 +91,17 @@ available, and if it isn't, the code in gproc_eqc.erl will be "defined away".
 There is also an eunit suite, covering the basic operations for local and
 global gproc.
 
-##Building Edoc##
 
+## Building Edoc ##
 
 
 By default, `./rebar doc` generates Github-flavored Markdown files.
-If you want to change this, remove the `edoc_opts` line from `rebar.config`.Gproc was first introduced at the ACM SIGPLAN Erlang Workshop in
+If you want to change this, remove the `edoc_opts` line from `rebar.config`.
+Gproc was first introduced at the ACM SIGPLAN Erlang Workshop in
 Freiburg 2007 ([Paper available here](http://github.com/esl/gproc/blob/master/doc/erlang07-wiger.pdf)).
 
 
-##Modules##
+## Modules ##
 
 
 <table width="100%" border="0" summary="list of modules">
@@ -109,6 +113,7 @@ Freiburg 2007 ([Paper available here](http://github.com/esl/gproc/blob/master/do
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_init.md" class="module">gproc_init</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_lib.md" class="module">gproc_lib</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_monitor.md" class="module">gproc_monitor</a></td></tr>
+<tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_pool.md" class="module">gproc_pool</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_ps.md" class="module">gproc_ps</a></td></tr>
 <tr><td><a href="http://github.com/esl/gproc/blob/master/doc/gproc_sup.md" class="module">gproc_sup</a></td></tr></table>
 

+ 20 - 15
doc/README.md

@@ -1,17 +1,17 @@
 
 
-#The gproc application#
-
+# The gproc application #
 
 __Authors:__ Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erlang-solutions.com)), Joseph Wayne Norton ([`norton@geminimobile.com`](mailto:norton@geminimobile.com)).
 
 Extended process dictionary
 
-##Note##
 
+## Note ##
 
 Gproc has two dependencies: `gen_leader` and `edown`. Since most people don't
 actively use either, they are no longer fetched by default.
+
 * To enable fetching of `gen_leader`, export the OS environment variable`GPROC_DIST=true` (this can be done e.g. from a GNU Makefile)
 
 * `edown` is fetched on-demand whenver `rebar get-deps doc` is called (which
@@ -19,10 +19,10 @@ actively use either, they are no longer fetched by default.
 
 
 
-##Introduction##
-
+## Introduction ##
 
 Gproc is a process dictionary for Erlang, which provides a number of useful features beyond what the built-in dictionary has:
+
 * Use any term as a process alias
 
 * Register a process under several aliases
@@ -43,8 +43,7 @@ Gproc is a process dictionary for Erlang, which provides a number of useful feat
 
 
 
-###Use case: System inspection##
-
+### Use case: System inspection ###
 
 Gproc was designed to work as a central index for "process metadata", i.e.
 properties that describe the role and characteristics of each process. Having
@@ -52,11 +51,14 @@ a single registry that is flexible enough to hold important types of property
 makes it easier to (a) find processes of a certain type, and (b) query and
 browse key data in a running system.
 
-###Use case: Pub/Sub patterns##
 
+### Use case: Pub/Sub patterns ###
 
 An interesting application of gproc is building publish/subscribe patterns.
-Example:<pre>
+Example:
+
+```erlang
+
 subscribe(EventType) ->
     %% Gproc notation: {p, l, Name} means {(p)roperty, (l)ocal, Name}
     gproc:reg({p, l, {?MODULE, EventType}}).
@@ -64,10 +66,11 @@ subscribe(EventType) ->
 notify(EventType, Msg) ->
     Key = {?MODULE, EventType},
     gproc:send({p, l, Key}, {self(), Key, Msg}).
-</pre>
 
-###Use case: Environment handling##
+```
+
 
+### Use case: Environment handling ###
 
 Gproc provides a set of functions to read environment variables, possibly from
 alternative sources, and cache them for efficient lookup. Caching also provides
@@ -77,8 +80,8 @@ which values they actually ended up using.
 See [`gproc:get_env/4`](gproc.md#get_env-4), [`gproc:get_set_env/4`](gproc.md#get_set_env-4) and
 [`gproc:set_env/5`](gproc.md#set_env-5) for details.
 
-##Testing##
 
+## Testing ##
 
 Gproc has a QuickCheck test suite, covering a fairly large part of the local
 gproc functionality, although none of the global registry. It requires a
@@ -88,16 +91,17 @@ available, and if it isn't, the code in gproc_eqc.erl will be "defined away".
 There is also an eunit suite, covering the basic operations for local and
 global gproc.
 
-##Building Edoc##
 
+## Building Edoc ##
 
 
 By default, `./rebar doc` generates Github-flavored Markdown files.
-If you want to change this, remove the `edoc_opts` line from `rebar.config`.Gproc was first introduced at the ACM SIGPLAN Erlang Workshop in
+If you want to change this, remove the `edoc_opts` line from `rebar.config`.
+Gproc was first introduced at the ACM SIGPLAN Erlang Workshop in
 Freiburg 2007 ([Paper available here](erlang07-wiger.pdf)).
 
 
-##Modules##
+## Modules ##
 
 
 <table width="100%" border="0" summary="list of modules">
@@ -109,6 +113,7 @@ Freiburg 2007 ([Paper available here](erlang07-wiger.pdf)).
 <tr><td><a href="gproc_init.md" class="module">gproc_init</a></td></tr>
 <tr><td><a href="gproc_lib.md" class="module">gproc_lib</a></td></tr>
 <tr><td><a href="gproc_monitor.md" class="module">gproc_monitor</a></td></tr>
+<tr><td><a href="gproc_pool.md" class="module">gproc_pool</a></td></tr>
 <tr><td><a href="gproc_ps.md" class="module">gproc_ps</a></td></tr>
 <tr><td><a href="gproc_sup.md" class="module">gproc_sup</a></td></tr></table>
 

+ 1 - 1
doc/edoc-info

@@ -1,4 +1,4 @@
 {application,gproc}.
 {packages,[]}.
 {modules,[gproc,gproc_app,gproc_bcast,gproc_dist,gproc_info,gproc_init,
-          gproc_lib,gproc_monitor,gproc_ps,gproc_sup]}.
+          gproc_lib,gproc_monitor,gproc_pool,gproc_ps,gproc_sup]}.

+ 843 - 334
doc/gproc.md

@@ -1,6 +1,6 @@
 
 
-#Module gproc#
+# Module gproc #
 * [Description](#description)
 * [Data Types](#types)
 * [Function Index](#index)
@@ -9,13 +9,12 @@
 
 Extended process registry
 This module implements an extended process registry.
-
 __Behaviours:__ [`gen_server`](gen_server.md).
 
-__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).<a name="description"></a>
-
-##Description##
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
 
+## Description ##
 
 
 
@@ -24,8 +23,8 @@ For a detailed description, see
 
 
 
-##Tuning Gproc performance##
 
+## Tuning Gproc performance ##
 
 
 
@@ -35,6 +34,7 @@ sacrificing consistency. A few things can be tuned by setting the following
 application environment variables in the top application of `gproc`
 (usually `gproc`):
 
+
 * `{ets_options, list()}` - Currently, the options `{write_concurrency, F}`
 and `{read_concurrency, F}` are allowed. The default is
 `[{write_concurrency, true}, {read_concurrency, true}]`
@@ -45,274 +45,357 @@ will improve performance.
 
 <a name="types"></a>
 
-##Data Types##
+## Data Types ##
 
 
 
 
-###<a name="type-context">context()</a>##
+### <a name="type-context">context()</a> ###
 
 
 
-<pre>context() = {<a href="#type-scope">scope()</a>, <a href="#type-type">type()</a>} | <a href="#type-type">type()</a></pre>
+<pre><code>
+context() = {<a href="#type-scope">scope()</a>, <a href="#type-type">type()</a>} | <a href="#type-type">type()</a>
+</code></pre>
+
 
 
 {'all','all'} is the default
 
 
 
-###<a name="type-ctr_incr">ctr_incr()</a>##
+
+### <a name="type-ctr_incr">ctr_incr()</a> ###
+
+
+
+<pre><code>
+ctr_incr() = integer()
+</code></pre>
+
+
+
+
+
+### <a name="type-ctr_setval">ctr_setval()</a> ###
+
+
+
+<pre><code>
+ctr_setval() = integer()
+</code></pre>
+
 
 
 
-<pre>ctr_incr() = integer()</pre>
 
+### <a name="type-ctr_thr">ctr_thr()</a> ###
 
 
-###<a name="type-ctr_setval">ctr_setval()</a>##
 
+<pre><code>
+ctr_thr() = integer()
+</code></pre>
 
 
-<pre>ctr_setval() = integer()</pre>
 
 
 
-###<a name="type-ctr_thr">ctr_thr()</a>##
+### <a name="type-ctr_update">ctr_update()</a> ###
 
 
 
-<pre>ctr_thr() = integer()</pre>
+<pre><code>
+ctr_update() = <a href="#type-ctr_incr">ctr_incr()</a> | {<a href="#type-ctr_incr">ctr_incr()</a>, <a href="#type-ctr_thr">ctr_thr()</a>, <a href="#type-ctr_setval">ctr_setval()</a>}
+</code></pre>
 
 
 
-###<a name="type-ctr_update">ctr_update()</a>##
 
 
+### <a name="type-headpat">headpat()</a> ###
 
-<pre>ctr_update() = <a href="#type-ctr_incr">ctr_incr()</a> | {<a href="#type-ctr_incr">ctr_incr()</a>, <a href="#type-ctr_thr">ctr_thr()</a>, <a href="#type-ctr_setval">ctr_setval()</a>}</pre>
 
 
+<pre><code>
+headpat() = {<a href="#type-keypat">keypat()</a>, <a href="#type-pidpat">pidpat()</a>, ValPat}
+</code></pre>
 
-###<a name="type-headpat">headpat()</a>##
 
 
 
-<pre>headpat() = {<a href="#type-keypat">keypat()</a>, <a href="#type-pidpat">pidpat()</a>, ValPat}</pre>
 
+### <a name="type-increment">increment()</a> ###
 
 
-###<a name="type-increment">increment()</a>##
 
+<pre><code>
+increment() = <a href="#type-ctr_incr">ctr_incr()</a> | <a href="#type-ctr_update">ctr_update()</a> | [<a href="#type-ctr_update">ctr_update()</a>]
+</code></pre>
 
 
-<pre>increment() = <a href="#type-ctr_incr">ctr_incr()</a> | <a href="#type-ctr_update">ctr_update()</a> | [<a href="#type-ctr_update">ctr_update()</a>]</pre>
 
 
 
-###<a name="type-key">key()</a>##
+### <a name="type-key">key()</a> ###
 
 
 
-<pre>key() = {<a href="#type-type">type()</a>, <a href="#type-scope">scope()</a>, any()}</pre>
+<pre><code>
+key() = {<a href="#type-type">type()</a>, <a href="#type-scope">scope()</a>, any()}
+</code></pre>
+
 
 
 update_counter increment
 
 
-###<a name="type-keypat">keypat()</a>##
 
+### <a name="type-keypat">keypat()</a> ###
+
+
+
+<pre><code>
+keypat() = {<a href="#type-sel_type">sel_type()</a> | <a href="#type-sel_var">sel_var()</a>, l | g | <a href="#type-sel_var">sel_var()</a>, any()}
+</code></pre>
+
+
+
+
+
+### <a name="type-pidpat">pidpat()</a> ###
 
 
-<pre>keypat() = {<a href="#type-sel_type">sel_type()</a> | <a href="#type-sel_var">sel_var()</a>, l | g | <a href="#type-sel_var">sel_var()</a>, any()}</pre>
 
+<pre><code>
+pidpat() = pid() | <a href="#type-sel_var">sel_var()</a>
+</code></pre>
 
 
-###<a name="type-pidpat">pidpat()</a>##
 
 
 
-<pre>pidpat() = pid() | <a href="#type-sel_var">sel_var()</a></pre>
+### <a name="type-reg_id">reg_id()</a> ###
 
 
 
-###<a name="type-reg_id">reg_id()</a>##
+<pre><code>
+reg_id() = {<a href="#type-type">type()</a>, <a href="#type-scope">scope()</a>, any()}
+</code></pre>
 
 
 
-<pre>reg_id() = {<a href="#type-type">type()</a>, <a href="#type-scope">scope()</a>, any()}</pre>
 
 
+### <a name="type-scope">scope()</a> ###
 
-###<a name="type-scope">scope()</a>##
 
 
+<pre><code>
+scope() = l | g
+</code></pre>
 
-<pre>scope() = l | g</pre>
 
 
 l = local registration; g = global registration
 
 
 
-###<a name="type-sel_pattern">sel_pattern()</a>##
 
+### <a name="type-sel_pattern">sel_pattern()</a> ###
+
+
+
+<pre><code>
+sel_pattern() = [{<a href="#type-headpat">headpat()</a>, Guards, Prod}]
+</code></pre>
+
+
+
+
+
+### <a name="type-sel_scope">sel_scope()</a> ###
 
 
-<pre>sel_pattern() = [{<a href="#type-headpat">headpat()</a>, Guards, Prod}]</pre>
 
+<pre><code>
+sel_scope() = scope | all | global | local
+</code></pre>
 
 
-###<a name="type-sel_scope">sel_scope()</a>##
 
 
 
-<pre>sel_scope() = scope | all | global | local</pre>
+### <a name="type-sel_type">sel_type()</a> ###
 
 
 
-###<a name="type-sel_type">sel_type()</a>##
+<pre><code>
+sel_type() = <a href="#type-type">type()</a> | names | props | counters | aggr_counters
+</code></pre>
 
 
 
-<pre>sel_type() = <a href="#type-type">type()</a> | names | props | counters | aggr_counters</pre>
 
 
+### <a name="type-sel_var">sel_var()</a> ###
 
-###<a name="type-sel_var">sel_var()</a>##
 
 
+<pre><code>
+sel_var() = DollarVar | '_'
+</code></pre>
 
-<pre>sel_var() = DollarVar | '_'</pre>
 
 
 
-###<a name="type-type">type()</a>##
 
+### <a name="type-type">type()</a> ###
 
 
-<pre>type() = n | p | c | a</pre>
+
+<pre><code>
+type() = n | p | c | a
+</code></pre>
+
 
 
 n = name; p = property; c = counter;
 a = aggregate_counter
 
 
-###<a name="type-unique_id">unique_id()</a>##
 
+### <a name="type-unique_id">unique_id()</a> ###
+
+
+
+<pre><code>
+unique_id() = {n | a, <a href="#type-scope">scope()</a>, any()}
+</code></pre>
 
 
-<pre>unique_id() = {n | a, <a href="#type-scope">scope()</a>, any()}</pre>
 <a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#add_global_aggr_counter-1">add_global_aggr_counter/1</a></td><td>Registers a global (unique) aggregated counter.</td></tr><tr><td valign="top"><a href="#add_global_counter-2">add_global_counter/2</a></td><td>Registers a global (non-unique) counter.</td></tr><tr><td valign="top"><a href="#add_global_name-1">add_global_name/1</a></td><td>Registers a global (unique) name.</td></tr><tr><td valign="top"><a href="#add_global_property-2">add_global_property/2</a></td><td>Registers a global (non-unique) property.</td></tr><tr><td valign="top"><a href="#add_local_aggr_counter-1">add_local_aggr_counter/1</a></td><td>Registers a local (unique) aggregated counter.</td></tr><tr><td valign="top"><a href="#add_local_counter-2">add_local_counter/2</a></td><td>Registers a local (non-unique) counter.</td></tr><tr><td valign="top"><a href="#add_local_name-1">add_local_name/1</a></td><td>Registers a local (unique) name.</td></tr><tr><td valign="top"><a href="#add_local_property-2">add_local_property/2</a></td><td>Registers a local (non-unique) property.</td></tr><tr><td valign="top"><a href="#add_shared_local_counter-2">add_shared_local_counter/2</a></td><td>Registers a local shared (unique) counter.</td></tr><tr><td valign="top"><a href="#audit_process-1">audit_process/1</a></td><td></td></tr><tr><td valign="top"><a href="#await-1">await/1</a></td><td>Equivalent to <a href="#await-2"><tt>await(Key, infinity)</tt></a>.</td></tr><tr><td valign="top"><a href="#await-2">await/2</a></td><td>Wait for a local name to be registered.</td></tr><tr><td valign="top"><a href="#await-3">await/3</a></td><td>Wait for a local name to be registered on <code>Node</code>.</td></tr><tr><td valign="top"><a href="#bcast-2">bcast/2</a></td><td>Equivalent to <a href="#bcast-3"><tt>bcast(nodes(), Key, Msg)</tt></a>.</td></tr><tr><td valign="top"><a href="#bcast-3">bcast/3</a></td><td>Sends a message to processes corresponding to Key on Nodes.</td></tr><tr><td valign="top"><a href="#cancel_wait-2">cancel_wait/2</a></td><td>Cancels a previous call to nb_wait/1.</td></tr><tr><td valign="top"><a href="#cancel_wait-3">cancel_wait/3</a></td><td>Cancels a previous call to nb_wait/2.</td></tr><tr><td valign="top"><a href="#cancel_wait_or_monitor-1">cancel_wait_or_monitor/1</a></td><td></td></tr><tr><td valign="top"><a href="#default-1">default/1</a></td><td></td></tr><tr><td valign="top"><a href="#demonitor-2">demonitor/2</a></td><td>Remove a monitor on a registered name
-This function is the reverse of monitor/1.</td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td>Behaves as ets:first(Tab) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#get_env-3">get_env/3</a></td><td>Equivalent to <a href="#get_env-4"><tt>get_env(Scope, App, Key, [app_env])</tt></a>.</td></tr><tr><td valign="top"><a href="#get_env-4">get_env/4</a></td><td>Read an environment value, potentially cached as a <code>gproc_env</code> property.</td></tr><tr><td valign="top"><a href="#get_set_env-3">get_set_env/3</a></td><td>Equivalent to <a href="#get_set_env-4"><tt>get_set_env(Scope, App, Key, [app_env])</tt></a>.</td></tr><tr><td valign="top"><a href="#get_set_env-4">get_set_env/4</a></td><td>Fetch and cache an environment value, if not already cached.</td></tr><tr><td valign="top"><a href="#get_value-1">get_value/1</a></td><td>Reads the value stored with a key registered to the current process.</td></tr><tr><td valign="top"><a href="#get_value-2">get_value/2</a></td><td>Reads the value stored with a key registered to the process Pid.</td></tr><tr><td valign="top"><a href="#give_away-2">give_away/2</a></td><td>Atomically transfers the key <code>From</code> to the process identified by <code>To</code>.</td></tr><tr><td valign="top"><a href="#goodbye-0">goodbye/0</a></td><td>Unregister all items of the calling process and inform gproc
+This function is the reverse of monitor/1.</td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td>Behaves as ets:first(Tab) for a given type of registration.</td></tr><tr><td valign="top"><a href="#get_attribute-2">get_attribute/2</a></td><td>Get attribute value of <code>Attr</code> associated with <code>Key</code> for most likely Pid.</td></tr><tr><td valign="top"><a href="#get_attribute-3">get_attribute/3</a></td><td>Get the attribute value of <code>Attr</code> associated with <code>Key</code> for process Pid.</td></tr><tr><td valign="top"><a href="#get_attributes-1">get_attributes/1</a></td><td>Get attributes associated with registration.</td></tr><tr><td valign="top"><a href="#get_attributes-2">get_attributes/2</a></td><td>Returns the list of attributes associated with the registration.</td></tr><tr><td valign="top"><a href="#get_env-3">get_env/3</a></td><td>Equivalent to <a href="#get_env-4"><tt>get_env(Scope, App, Key, [app_env])</tt></a>.</td></tr><tr><td valign="top"><a href="#get_env-4">get_env/4</a></td><td>Read an environment value, potentially cached as a <code>gproc_env</code> property.</td></tr><tr><td valign="top"><a href="#get_set_env-3">get_set_env/3</a></td><td>Equivalent to <a href="#get_set_env-4"><tt>get_set_env(Scope, App, Key, [app_env])</tt></a>.</td></tr><tr><td valign="top"><a href="#get_set_env-4">get_set_env/4</a></td><td>Fetch and cache an environment value, if not already cached.</td></tr><tr><td valign="top"><a href="#get_value-1">get_value/1</a></td><td>Reads the value stored with a key registered to the current process.</td></tr><tr><td valign="top"><a href="#get_value-2">get_value/2</a></td><td>Reads the value stored with a key registered to the process Pid.</td></tr><tr><td valign="top"><a href="#give_away-2">give_away/2</a></td><td>Atomically transfers the key <code>From</code> to the process identified by <code>To</code>.</td></tr><tr><td valign="top"><a href="#goodbye-0">goodbye/0</a></td><td>Unregister all items of the calling process and inform gproc
 to forget about the calling process.</td></tr><tr><td valign="top"><a href="#i-0">i/0</a></td><td>Similar to the built-in shell command <code>i()</code> but inserts information
-about names and properties registered in Gproc, where applicable.</td></tr><tr><td valign="top"><a href="#info-1">info/1</a></td><td>Similar to <code>process_info(Pid)</code> but with additional gproc info.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td>Similar to process_info(Pid, Item), but with additional gproc info.</td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td>Behaves as ets:last(Tab) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#lookup_global_aggr_counter-1">lookup_global_aggr_counter/1</a></td><td>Lookup a global (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_global_counters-1">lookup_global_counters/1</a></td><td>Look up all global (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_global_name-1">lookup_global_name/1</a></td><td>Lookup a global unique name.</td></tr><tr><td valign="top"><a href="#lookup_global_properties-1">lookup_global_properties/1</a></td><td>Look up all global (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_local_aggr_counter-1">lookup_local_aggr_counter/1</a></td><td>Lookup a local (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_local_counters-1">lookup_local_counters/1</a></td><td>Look up all local (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_local_name-1">lookup_local_name/1</a></td><td>Lookup a local unique name.</td></tr><tr><td valign="top"><a href="#lookup_local_properties-1">lookup_local_properties/1</a></td><td>Look up all local (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_pid-1">lookup_pid/1</a></td><td>Lookup the Pid stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_pids-1">lookup_pids/1</a></td><td>Returns a list of pids with the published key Key.</td></tr><tr><td valign="top"><a href="#lookup_value-1">lookup_value/1</a></td><td>Lookup the value stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_values-1">lookup_values/1</a></td><td>Retrieve the <code>{Pid,Value}</code> pairs corresponding to Key.</td></tr><tr><td valign="top"><a href="#monitor-1">monitor/1</a></td><td>monitor a registered name
+about names and properties registered in Gproc, where applicable.</td></tr><tr><td valign="top"><a href="#info-1">info/1</a></td><td>Similar to <code>process_info(Pid)</code> but with additional gproc info.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td>Similar to process_info(Pid, Item), but with additional gproc info.</td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td>Behaves as ets:last(Tab) for a given type of registration.</td></tr><tr><td valign="top"><a href="#lookup_global_aggr_counter-1">lookup_global_aggr_counter/1</a></td><td>Lookup a global (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_global_counters-1">lookup_global_counters/1</a></td><td>Look up all global (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_global_name-1">lookup_global_name/1</a></td><td>Lookup a global unique name.</td></tr><tr><td valign="top"><a href="#lookup_global_properties-1">lookup_global_properties/1</a></td><td>Look up all global (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_local_aggr_counter-1">lookup_local_aggr_counter/1</a></td><td>Lookup a local (unique) aggregated counter and returns its value.</td></tr><tr><td valign="top"><a href="#lookup_local_counters-1">lookup_local_counters/1</a></td><td>Look up all local (non-unique) instances of a given Counter.</td></tr><tr><td valign="top"><a href="#lookup_local_name-1">lookup_local_name/1</a></td><td>Lookup a local unique name.</td></tr><tr><td valign="top"><a href="#lookup_local_properties-1">lookup_local_properties/1</a></td><td>Look up all local (non-unique) instances of a given Property.</td></tr><tr><td valign="top"><a href="#lookup_pid-1">lookup_pid/1</a></td><td>Lookup the Pid stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_pids-1">lookup_pids/1</a></td><td>Returns a list of pids with the published key Key.</td></tr><tr><td valign="top"><a href="#lookup_value-1">lookup_value/1</a></td><td>Lookup the value stored with a key.</td></tr><tr><td valign="top"><a href="#lookup_values-1">lookup_values/1</a></td><td>Retrieve the <code>{Pid,Value}</code> pairs corresponding to Key.</td></tr><tr><td valign="top"><a href="#monitor-1">monitor/1</a></td><td>monitor a registered name
 This function works much like erlang:monitor(process, Pid), but monitors
-a unique name registered via gproc.</td></tr><tr><td valign="top"><a href="#mreg-3">mreg/3</a></td><td>Register multiple {Key,Value} pairs of a given type and scope.</td></tr><tr><td valign="top"><a href="#munreg-3">munreg/3</a></td><td>Unregister multiple Key items of a given type and scope.</td></tr><tr><td valign="top"><a href="#nb_wait-1">nb_wait/1</a></td><td>Wait for a local name to be registered.</td></tr><tr><td valign="top"><a href="#nb_wait-2">nb_wait/2</a></td><td>Wait for a local name to be registered on <code>Node</code>.</td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td>Behaves as ets:next(Tab,Key) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td>Behaves as ets:prev(Tab,Key) for a given type of registration object.</td></tr><tr><td valign="top"><a href="#reg-1">reg/1</a></td><td>Equivalent to <a href="#reg-2"><tt>reg(Key, default(Key))</tt></a>.</td></tr><tr><td valign="top"><a href="#reg-2">reg/2</a></td><td>Register a name or property for the current process.</td></tr><tr><td valign="top"><a href="#reg_or_locate-1">reg_or_locate/1</a></td><td>Equivalent to <a href="#reg_or_locate-2"><tt>reg_or_locate(Key, default(Key))</tt></a>.</td></tr><tr><td valign="top"><a href="#reg_or_locate-2">reg_or_locate/2</a></td><td>Try registering a unique name, or return existing registration.</td></tr><tr><td valign="top"><a href="#reg_or_locate-3">reg_or_locate/3</a></td><td>Spawn a process with a registered name, or return existing registration.</td></tr><tr><td valign="top"><a href="#reg_shared-1">reg_shared/1</a></td><td>Register a resource, but don't tie it to a particular process.</td></tr><tr><td valign="top"><a href="#reg_shared-2">reg_shared/2</a></td><td>Register a resource, but don't tie it to a particular process.</td></tr><tr><td valign="top"><a href="#register_name-2">register_name/2</a></td><td>Behaviour support callback.</td></tr><tr><td valign="top"><a href="#reset_counter-1">reset_counter/1</a></td><td>Reads and resets a counter in a "thread-safe" way.</td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td>Perform a select operation on the process registry.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td>Perform a select operation with limited context on the process registry.</td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td>Like <a href="#select-2"><code>select/2</code></a> but returns Limit objects at a time.</td></tr><tr><td valign="top"><a href="#select_count-1">select_count/1</a></td><td>Equivalent to <a href="#select_count-2"><tt>select_count(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td>Perform a select_count operation on the process registry.</td></tr><tr><td valign="top"><a href="#send-2">send/2</a></td><td>Sends a message to the process, or processes, corresponding to Key.</td></tr><tr><td valign="top"><a href="#set_env-5">set_env/5</a></td><td>Updates the cached value as well as underlying environment.</td></tr><tr><td valign="top"><a href="#set_value-2">set_value/2</a></td><td>Sets the value of the registeration entry given by Key.</td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td>Starts the gproc server.</td></tr><tr><td valign="top"><a href="#table-0">table/0</a></td><td>Equivalent to <a href="#table-1"><tt>table({all, all})</tt></a>.</td></tr><tr><td valign="top"><a href="#table-1">table/1</a></td><td>Equivalent to <a href="#table-2"><tt>table(Context, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#table-2">table/2</a></td><td>QLC table generator for the gproc registry.</td></tr><tr><td valign="top"><a href="#unreg-1">unreg/1</a></td><td>Unregister a name or property.</td></tr><tr><td valign="top"><a href="#unreg_shared-1">unreg_shared/1</a></td><td>Unregister a shared resource.</td></tr><tr><td valign="top"><a href="#unregister_name-1">unregister_name/1</a></td><td>Equivalent to <tt>unreg / 1</tt>.</td></tr><tr><td valign="top"><a href="#update_counter-2">update_counter/2</a></td><td>Updates the counter registered as Key for the current process.</td></tr><tr><td valign="top"><a href="#update_counters-2">update_counters/2</a></td><td>Update a list of counters.</td></tr><tr><td valign="top"><a href="#update_shared_counter-2">update_shared_counter/2</a></td><td>Updates the shared counter registered as Key.</td></tr><tr><td valign="top"><a href="#where-1">where/1</a></td><td>Returns the pid registered as Key.</td></tr><tr><td valign="top"><a href="#whereis_name-1">whereis_name/1</a></td><td>Equivalent to <tt>where / 1</tt>.</td></tr><tr><td valign="top"><a href="#wide_await-3">wide_await/3</a></td><td>Wait for a local name to be registered on any of <code>Nodes</code>.</td></tr></table>
+a unique name registered via gproc.</td></tr><tr><td valign="top"><a href="#mreg-3">mreg/3</a></td><td>Register multiple {Key,Value} pairs of a given type and scope.</td></tr><tr><td valign="top"><a href="#munreg-3">munreg/3</a></td><td>Unregister multiple Key items of a given type and scope.</td></tr><tr><td valign="top"><a href="#nb_wait-1">nb_wait/1</a></td><td>Wait for a local name to be registered.</td></tr><tr><td valign="top"><a href="#nb_wait-2">nb_wait/2</a></td><td>Wait for a local name to be registered on <code>Node</code>.</td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td>Behaves as ets:next(Tab,Key) for a given type of registration.</td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td>Behaves as ets:prev(Tab,Key) for a given type of registration.</td></tr><tr><td valign="top"><a href="#reg-1">reg/1</a></td><td>Equivalent to <a href="#reg-2"><tt>reg(Key, default(Key))</tt></a>.</td></tr><tr><td valign="top"><a href="#reg-2">reg/2</a></td><td>Register a name or property for the current process.</td></tr><tr><td valign="top"><a href="#reg_or_locate-1">reg_or_locate/1</a></td><td>Equivalent to <a href="#reg_or_locate-2"><tt>reg_or_locate(Key, default(Key))</tt></a>.</td></tr><tr><td valign="top"><a href="#reg_or_locate-2">reg_or_locate/2</a></td><td>Try registering a unique name, or return existing registration.</td></tr><tr><td valign="top"><a href="#reg_or_locate-3">reg_or_locate/3</a></td><td>Spawn a process with a registered name, or return existing registration.</td></tr><tr><td valign="top"><a href="#reg_shared-1">reg_shared/1</a></td><td>Register a resource, but don't tie it to a particular process.</td></tr><tr><td valign="top"><a href="#reg_shared-2">reg_shared/2</a></td><td>Register a resource, but don't tie it to a particular process.</td></tr><tr><td valign="top"><a href="#register_name-2">register_name/2</a></td><td>Behaviour support callback.</td></tr><tr><td valign="top"><a href="#reset_counter-1">reset_counter/1</a></td><td>Reads and resets a counter in a "thread-safe" way.</td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td>Perform a select operation on the process registry.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td>Perform a select operation with limited context on the process registry.</td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td>Like <a href="#select-2"><code>select/2</code></a> but returns Limit objects at a time.</td></tr><tr><td valign="top"><a href="#select_count-1">select_count/1</a></td><td>Equivalent to <a href="#select_count-2"><tt>select_count(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td>Perform a select_count operation on the process registry.</td></tr><tr><td valign="top"><a href="#send-2">send/2</a></td><td>Sends a message to the process, or processes, corresponding to Key.</td></tr><tr><td valign="top"><a href="#set_attributes-2">set_attributes/2</a></td><td>Add/modify <code>{Key, Value}</code> attributes associated with a registration.</td></tr><tr><td valign="top"><a href="#set_attributes_shared-2">set_attributes_shared/2</a></td><td>Add/modify <code>{Key, Value}</code> attributes associated with a shared registration.</td></tr><tr><td valign="top"><a href="#set_env-5">set_env/5</a></td><td>Updates the cached value as well as underlying environment.</td></tr><tr><td valign="top"><a href="#set_value-2">set_value/2</a></td><td>Sets the value of the registration given by Key.</td></tr><tr><td valign="top"><a href="#set_value_shared-2">set_value_shared/2</a></td><td>Sets the value of the shared registration given by Key.</td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td>Starts the gproc server.</td></tr><tr><td valign="top"><a href="#table-0">table/0</a></td><td>Equivalent to <a href="#table-1"><tt>table({all, all})</tt></a>.</td></tr><tr><td valign="top"><a href="#table-1">table/1</a></td><td>Equivalent to <a href="#table-2"><tt>table(Context, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#table-2">table/2</a></td><td>QLC table generator for the gproc registry.</td></tr><tr><td valign="top"><a href="#unreg-1">unreg/1</a></td><td>Unregister a name or property.</td></tr><tr><td valign="top"><a href="#unreg_shared-1">unreg_shared/1</a></td><td>Unregister a shared resource.</td></tr><tr><td valign="top"><a href="#unregister_name-1">unregister_name/1</a></td><td>Equivalent to <tt>unreg / 1</tt>.</td></tr><tr><td valign="top"><a href="#update_counter-2">update_counter/2</a></td><td>Updates the counter registered as Key for the current process.</td></tr><tr><td valign="top"><a href="#update_counter-3">update_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#update_counters-2">update_counters/2</a></td><td>Update a list of counters.</td></tr><tr><td valign="top"><a href="#update_shared_counter-2">update_shared_counter/2</a></td><td>Updates the shared counter registered as Key.</td></tr><tr><td valign="top"><a href="#where-1">where/1</a></td><td>Returns the pid registered as Key.</td></tr><tr><td valign="top"><a href="#whereis_name-1">whereis_name/1</a></td><td>Equivalent to <tt>where / 1</tt>.</td></tr><tr><td valign="top"><a href="#wide_await-3">wide_await/3</a></td><td>Wait for a local name to be registered on any of <code>Nodes</code>.</td></tr></table>
 
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="add_global_aggr_counter-1"></a>
 
-###add_global_aggr_counter/1##
-
+### add_global_aggr_counter/1 ###
 
 `add_global_aggr_counter(Name) -> any()`
 
 Equivalent to [`reg({a, g, Name})`](#reg-1).
 
-Registers a global (unique) aggregated counter.<a name="add_global_counter-2"></a>
-
-###add_global_counter/2##
+Registers a global (unique) aggregated counter.
+<a name="add_global_counter-2"></a>
 
+### add_global_counter/2 ###
 
 `add_global_counter(Name, Initial) -> any()`
 
-Registers a global (non-unique) counter. @equiv reg({c,g,Name},Value)<a name="add_global_name-1"></a>
-
-###add_global_name/1##
+Registers a global (non-unique) counter. @equiv reg({c,g,Name},Value)
+<a name="add_global_name-1"></a>
 
+### add_global_name/1 ###
 
 `add_global_name(Name) -> any()`
 
-Registers a global (unique) name. @equiv reg({n,g,Name})<a name="add_global_property-2"></a>
-
-###add_global_property/2##
+Registers a global (unique) name. @equiv reg({n,g,Name})
+<a name="add_global_property-2"></a>
 
+### add_global_property/2 ###
 
 `add_global_property(Name, Value) -> any()`
 
-Registers a global (non-unique) property. @equiv reg({p,g,Name},Value)<a name="add_local_aggr_counter-1"></a>
-
-###add_local_aggr_counter/1##
+Registers a global (non-unique) property. @equiv reg({p,g,Name},Value)
+<a name="add_local_aggr_counter-1"></a>
 
+### add_local_aggr_counter/1 ###
 
 `add_local_aggr_counter(Name) -> any()`
 
 Equivalent to [`reg({a, l, Name})`](#reg-1).
 
-Registers a local (unique) aggregated counter.<a name="add_local_counter-2"></a>
-
-###add_local_counter/2##
+Registers a local (unique) aggregated counter.
+<a name="add_local_counter-2"></a>
 
+### add_local_counter/2 ###
 
 `add_local_counter(Name, Initial) -> any()`
 
-Registers a local (non-unique) counter. @equiv reg({c,l,Name},Value)<a name="add_local_name-1"></a>
-
-###add_local_name/1##
+Registers a local (non-unique) counter. @equiv reg({c,l,Name},Value)
+<a name="add_local_name-1"></a>
 
+### add_local_name/1 ###
 
 `add_local_name(Name) -> any()`
 
-Registers a local (unique) name. @equiv reg({n,l,Name})<a name="add_local_property-2"></a>
-
-###add_local_property/2##
+Registers a local (unique) name. @equiv reg({n,l,Name})
+<a name="add_local_property-2"></a>
 
+### add_local_property/2 ###
 
 `add_local_property(Name, Value) -> any()`
 
-Registers a local (non-unique) property. @equiv reg({p,l,Name},Value)<a name="add_shared_local_counter-2"></a>
-
-###add_shared_local_counter/2##
+Registers a local (non-unique) property. @equiv reg({p,l,Name},Value)
+<a name="add_shared_local_counter-2"></a>
 
+### add_shared_local_counter/2 ###
 
 `add_shared_local_counter(Name, Initial) -> any()`
 
 Equivalent to [`reg_shared({c, l, Name}, Value)`](#reg_shared-2).
 
-Registers a local shared (unique) counter.<a name="audit_process-1"></a>
+Registers a local shared (unique) counter.
+<a name="audit_process-1"></a>
+
+### audit_process/1 ###
 
-###audit_process/1##
 
+<pre><code>
+audit_process(Pid::pid()) -&gt; ok
+</code></pre>
 
-<pre>audit_process(Pid::pid()) -&gt; ok</pre>
 <br></br>
 
 
+
 <a name="await-1"></a>
 
-###await/1##
+### await/1 ###
+
 
+<pre><code>
+await(Key::<a href="#type-key">key()</a>) -&gt; {pid(), Value}
+</code></pre>
 
-<pre>await(Key::<a href="#type-key">key()</a>) -> {pid(), Value}</pre>
 <br></br>
 
 
-Equivalent to [`await(Key, infinity)`](#await-2).<a name="await-2"></a>
+Equivalent to [`await(Key, infinity)`](#await-2).
+<a name="await-2"></a>
 
-###await/2##
+### await/2 ###
 
 
-<pre>await(Key::<a href="#type-key">key()</a>, Timeout) -> {pid(), Value}</pre>
-<ul class="definitions"><li><pre>Timeout = integer() | infinity</pre></li></ul>
+<pre><code>
+await(Key::<a href="#type-key">key()</a>, Timeout) -&gt; {pid(), Value}
+</code></pre>
+
+<ul class="definitions"><li><code>Timeout = integer() | infinity</code></li></ul>
 
 Wait for a local name to be registered.
 The function raises an exception if the timeout expires. Timeout must be
@@ -320,128 +403,234 @@ either an interger > 0 or 'infinity'.
 A small optimization: we first perform a lookup, to see if the name
 is already registered. This way, the cost of the operation will be
 roughly the same as of where/1 in the case where the name is already
-registered (the difference: await/2 also returns the value).<a name="await-3"></a>
+registered (the difference: await/2 also returns the value).
+<a name="await-3"></a>
+
+### await/3 ###
 
-###await/3##
 
+<pre><code>
+await(Node::node(), Key::<a href="#type-key">key()</a>, Timeout) -&gt; {pid(), Value}
+</code></pre>
 
-<pre>await(Node::node(), Key::<a href="#type-key">key()</a>, Timeout) -> {pid(), Value}</pre>
-<ul class="definitions"><li><pre>Timeout = integer() | infinity</pre></li></ul>
+<ul class="definitions"><li><code>Timeout = integer() | infinity</code></li></ul>
 
 Wait for a local name to be registered on `Node`.
 This function works exactly like [`await/2`](#await-2), but queries a remote
 node instead. An exception is thrown if `Node` cannot be reached. If gproc
 is not running on a given node, this is treated the same as the node being
-down.<a name="bcast-2"></a>
+down.
+<a name="bcast-2"></a>
+
+### bcast/2 ###
 
-###bcast/2##
 
+<pre><code>
+bcast(Key::<a href="#type-key">key()</a>, Msg::any()) -&gt; Msg
+</code></pre>
 
-<pre>bcast(Key::<a href="#type-key">key()</a>, Msg::any()) -> Msg</pre>
 <br></br>
 
 
-Equivalent to [`bcast(nodes(), Key, Msg)`](#bcast-3).<a name="bcast-3"></a>
+Equivalent to [`bcast(nodes(), Key, Msg)`](#bcast-3).
+<a name="bcast-3"></a>
 
-###bcast/3##
+### bcast/3 ###
 
 
-<pre>bcast(Nodes::[atom()], Key::<a href="#type-key">key()</a>, Msg::any()) -> Msg</pre>
-<br></br>
+<pre><code>
+bcast(Nodes::[atom()], Key::<a href="#type-key">key()</a>, Msg::any()) -&gt; Msg
+</code></pre>
 
+<br></br>
 
 
 
 Sends a message to processes corresponding to Key on Nodes.
 
+
 This function complements `send/2` and works on locally registered resources
 that `send/2` supports. Messages are routed via a special broadcast server
 on each node to ensure that ordering is preserved. Distributed delivery
 is asynchronous and carries the same guarantees as normal message passing
 (with the added proviso that the broadcast server also needs to be available).
 
-__See also:__ [send/2](#send-2).<a name="cancel_wait-2"></a>
+__See also:__ [send/2](#send-2).
+<a name="cancel_wait-2"></a>
 
-###cancel_wait/2##
+### cancel_wait/2 ###
 
 
-<pre>cancel_wait(Key::<a href="#type-key">key()</a>, Ref) -> ok</pre>
-<ul class="definitions"><li><pre>Ref = all | reference()</pre></li></ul>
+<pre><code>
+cancel_wait(Key::<a href="#type-key">key()</a>, Ref) -&gt; ok
+</code></pre>
 
+<ul class="definitions"><li><code>Ref = all | reference()</code></li></ul>
 
 
 Cancels a previous call to nb_wait/1
 
+
 If `Ref = all`, all wait requests on `Key` from the calling process
-are canceled.<a name="cancel_wait-3"></a>
+are canceled.
+<a name="cancel_wait-3"></a>
 
-###cancel_wait/3##
+### cancel_wait/3 ###
 
 
-<pre>cancel_wait(Node::node(), Key::<a href="#type-key">key()</a>, Ref) -> ok</pre>
-<ul class="definitions"><li><pre>Ref = all | reference()</pre></li></ul>
+<pre><code>
+cancel_wait(Node::node(), Key::<a href="#type-key">key()</a>, Ref) -&gt; ok
+</code></pre>
 
+<ul class="definitions"><li><code>Ref = all | reference()</code></li></ul>
 
 
 Cancels a previous call to nb_wait/2
 
-This function works just like [`cancel_wait/2`](#cancel_wait-2), but talks to a remote
-node.<a name="cancel_wait_or_monitor-1"></a>
 
-###cancel_wait_or_monitor/1##
+This function works just like [`cancel_wait/2`](#cancel_wait-2), but talks to a remote
+node.
+<a name="cancel_wait_or_monitor-1"></a>
 
+### cancel_wait_or_monitor/1 ###
 
 `cancel_wait_or_monitor(Key) -> any()`
 
-<a name="default-1"></a>
 
-###default/1##
+<a name="default-1"></a>
 
+### default/1 ###
 
 `default(X1) -> any()`
 
+
 <a name="demonitor-2"></a>
 
-###demonitor/2##
+### demonitor/2 ###
+
 
+<pre><code>
+demonitor(Key::<a href="#type-key">key()</a>, Ref::reference()) -&gt; ok
+</code></pre>
 
-<pre>demonitor(Key::<a href="#type-key">key()</a>, Ref::reference()) -> ok</pre>
 <br></br>
 
 
 Remove a monitor on a registered name
 This function is the reverse of monitor/1. It removes a monitor previously
-set on a unique name. This function always succeeds given legal input.<a name="first-1"></a>
+set on a unique name. This function always succeeds given legal input.
+<a name="first-1"></a>
 
-###first/1##
+### first/1 ###
 
 
-<pre>first(Context::<a href="#type-context">context()</a>) -> <a href="#type-key">key()</a> | '$end_of_table'</pre>
+<pre><code>
+first(Context::<a href="#type-context">context()</a>) -&gt; <a href="#type-key">key()</a> | '$end_of_table'
+</code></pre>
+
 <br></br>
 
 
 
+Behaves as ets:first(Tab) for a given type of registration.
 
-Behaves as ets:first(Tab) for a given type of registration object.
 
 See [`http://www.erlang.org/doc/man/ets.html#first-1`](http://www.erlang.org/doc/man/ets.html#first-1).
-The registry behaves as an ordered_set table.<a name="get_env-3"></a>
+The registry behaves as an ordered_set table.
+<a name="get_attribute-2"></a>
+
+### get_attribute/2 ###
+
+
+<pre><code>
+get_attribute(Key, Attribute::atom()) -&gt; Value
+</code></pre>
 
-###get_env/3##
+<br></br>
+
+
+
+Get attribute value of `Attr` associated with `Key` for most likely Pid.
+
+
+The most likely Pid in this case is `self()` for properties and counters,
+and the current registration holder in case of names or aggregated counters.
+An exception is raised if `Key` is not registered for the given process.
+<a name="get_attribute-3"></a>
+
+### get_attribute/3 ###
+
+
+<pre><code>
+get_attribute(Key, Pid::pid() | shared, Attr::atom()) -&gt; Value
+</code></pre>
+
+<br></br>
+
+
+
+Get the attribute value of `Attr` associated with `Key` for process Pid.
+
+
+If `Pid == shared`, the attribute of a shared key (see [`reg_shared/1`](#reg_shared-1))
+will be read.
+<a name="get_attributes-1"></a>
+
+### get_attributes/1 ###
+
+
+<pre><code>
+get_attributes(Key::<a href="#type-key">key()</a>) -&gt; [{K, V}]
+</code></pre>
+
+<br></br>
+
+
+Equivalent to [`get_attributes(Key, self())`](#get_attributes-2).
+
+Get attributes associated with registration.
+<a name="get_attributes-2"></a>
+
+### get_attributes/2 ###
+
+
+<pre><code>
+get_attributes(Key::<a href="#type-key">key()</a>, Pid::pid() | shared) -&gt; [{K, V}]
+</code></pre>
+
+<br></br>
+
+
+
+Returns the list of attributes associated with the registration.
+
+
+This function raises a `badarg` exception if there is no corresponding
+registration.
 
+<a name="get_env-3"></a>
+
+### get_env/3 ###
+
+
+<pre><code>
+get_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom()) -&gt; term()
+</code></pre>
 
-<pre>get_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom()) -> term()</pre>
 <br></br>
 
 
-Equivalent to [`get_env(Scope, App, Key, [app_env])`](#get_env-4).<a name="get_env-4"></a>
+Equivalent to [`get_env(Scope, App, Key, [app_env])`](#get_env-4).
+<a name="get_env-4"></a>
 
-###get_env/4##
+### get_env/4 ###
 
 
-<pre>get_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom(), Strategy) -> term()</pre>
-<ul class="definitions"><li><pre>Strategy = [Alternative]</pre></li><li><pre>Alternative = app_env | os_env | inherit | {inherit, pid()} | {inherit, <a href="#type-unique_id">unique_id()</a>} | init_arg | {mnesia, ActivityType, Oid, Pos} | {default, term()} | error</pre></li></ul>
+<pre><code>
+get_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom(), Strategy) -&gt; term()
+</code></pre>
 
+<ul class="definitions"><li><code>Strategy = [Alternative]</code></li><li><code>Alternative = app_env | os_env | inherit | {inherit, pid()} | {inherit, <a href="#type-unique_id">unique_id()</a>} | init_arg | {mnesia, ActivityType, Oid, Pos} | {default, term()} | error</code></li></ul>
 
 
 Read an environment value, potentially cached as a `gproc_env` property.
@@ -487,71 +676,91 @@ whichever comes first
 * The last instance of `{default, Value}`, or `undefined`, if there is no
 matching alternative, default or `error` entry in the list.
 
+
 The `error` option can be used to assert that a value has been previously
 cached. Alternatively, it can be used to assert that a value is either cached
 or at least defined somewhere,
-e.g. `get_env(l, mnesia, dir, [app_env, error])`.<a name="get_set_env-3"></a>
+e.g. `get_env(l, mnesia, dir, [app_env, error])`.
+<a name="get_set_env-3"></a>
+
+### get_set_env/3 ###
 
-###get_set_env/3##
 
+<pre><code>
+get_set_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom()) -&gt; term()
+</code></pre>
 
-<pre>get_set_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom()) -> term()</pre>
 <br></br>
 
 
-Equivalent to [`get_set_env(Scope, App, Key, [app_env])`](#get_set_env-4).<a name="get_set_env-4"></a>
+Equivalent to [`get_set_env(Scope, App, Key, [app_env])`](#get_set_env-4).
+<a name="get_set_env-4"></a>
 
-###get_set_env/4##
+### get_set_env/4 ###
 
 
-<pre>get_set_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom(), Strategy) -> Value</pre>
-<br></br>
+<pre><code>
+get_set_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom(), Strategy) -&gt; Value
+</code></pre>
 
+<br></br>
 
 
 
 Fetch and cache an environment value, if not already cached.
 
+
 This function does the same thing as [`get_env/4`](#get_env-4), but also updates the
 cache. Note that the cache will be updated even if the result of the lookup
 is `undefined`.
 
 
-__See also:__ [get_env/4](#get_env-4).<a name="get_value-1"></a>
+__See also:__ [get_env/4](#get_env-4).
+<a name="get_value-1"></a>
 
-###get_value/1##
+### get_value/1 ###
 
 
-<pre>get_value(Key) -&gt; Value</pre>
-<br></br>
+<pre><code>
+get_value(Key) -&gt; Value
+</code></pre>
 
+<br></br>
 
 
 
 Reads the value stored with a key registered to the current process.
 
-If no such key is registered to the current process, this function exits.<a name="get_value-2"></a>
 
-###get_value/2##
+If no such key is registered to the current process, this function exits.
+<a name="get_value-2"></a>
 
+### get_value/2 ###
 
-<pre>get_value(Key, Pid) -&gt; Value</pre>
-<br></br>
 
+<pre><code>
+get_value(Key, Pid) -&gt; Value
+</code></pre>
+
+<br></br>
 
 
 
 Reads the value stored with a key registered to the process Pid.
 
+
 If `Pid == shared`, the value of a shared key (see [`reg_shared/1`](#reg_shared-1))
-will be read.<a name="give_away-2"></a>
+will be read.
+<a name="give_away-2"></a>
 
-###give_away/2##
+### give_away/2 ###
 
 
-<pre>give_away(From::<a href="#type-key">key()</a>, To::pid() | <a href="#type-key">key()</a>) -> undefined | pid()</pre>
-<br></br>
+<pre><code>
+give_away(From::<a href="#type-key">key()</a>, To::pid() | <a href="#type-key">key()</a>) -&gt; undefined | pid()
+</code></pre>
 
+<br></br>
 
 
 
@@ -574,227 +783,304 @@ and the `From` key is effectively unregistered.
 It is allowed to give away a key to oneself, but of course, this operation
 will have no effect.
 
+
 Fails with `badarg` if the calling process does not have a `From` key
-registered.<a name="goodbye-0"></a>
+registered.
+<a name="goodbye-0"></a>
 
-###goodbye/0##
+### goodbye/0 ###
 
 
-<pre>goodbye() -&gt; ok</pre>
-<br></br>
+<pre><code>
+goodbye() -&gt; ok
+</code></pre>
 
+<br></br>
 
 
 
 Unregister all items of the calling process and inform gproc
 to forget about the calling process.
 
+
 This function is more efficient than letting gproc perform these
-cleanup operations.<a name="i-0"></a>
+cleanup operations.
+<a name="i-0"></a>
 
-###i/0##
+### i/0 ###
 
 
-<pre>i() -&gt; ok</pre>
+<pre><code>
+i() -&gt; ok
+</code></pre>
+
 <br></br>
 
 
 Similar to the built-in shell command `i()` but inserts information
-about names and properties registered in Gproc, where applicable.<a name="info-1"></a>
+about names and properties registered in Gproc, where applicable.
+<a name="info-1"></a>
 
-###info/1##
+### info/1 ###
 
 
-<pre>info(Pid::pid()) -&gt; ProcessInfo</pre>
-<ul class="definitions"><li><pre>ProcessInfo = [{gproc, [{Key, Value}]} | ProcessInfo]</pre></li></ul>
+<pre><code>
+info(Pid::pid()) -&gt; ProcessInfo
+</code></pre>
 
+<ul class="definitions"><li><code>ProcessInfo = [{gproc, [{Key, Value}]} | ProcessInfo]</code></li></ul>
 
 
 Similar to `process_info(Pid)` but with additional gproc info.
 
+
 Returns the same information as process_info(Pid), but with the
 addition of a `gproc` information item, containing the `{Key,Value}`
-pairs registered to the process.<a name="info-2"></a>
+pairs registered to the process.
+<a name="info-2"></a>
 
-###info/2##
+### info/2 ###
 
 
-<pre>info(Pid::pid(), Item::atom()) -&gt; {Item, Info}</pre>
-<br></br>
+<pre><code>
+info(Pid::pid(), Item::atom()) -&gt; {Item, Info}
+</code></pre>
 
+<br></br>
 
 
 
 Similar to process_info(Pid, Item), but with additional gproc info.
 
+
 For `Item = gproc`, this function returns a list of `{Key, Value}` pairs
 registered to the process Pid. For other values of Item, it returns the
-same as [`http://www.erlang.org/doc/man/erlang.html#process_info-2`](http://www.erlang.org/doc/man/erlang.html#process_info-2).<a name="last-1"></a>
+same as [`http://www.erlang.org/doc/man/erlang.html#process_info-2`](http://www.erlang.org/doc/man/erlang.html#process_info-2).
+<a name="last-1"></a>
+
+### last/1 ###
 
-###last/1##
 
+<pre><code>
+last(Context::<a href="#type-context">context()</a>) -&gt; <a href="#type-key">key()</a> | '$end_of_table'
+</code></pre>
 
-<pre>last(Context::<a href="#type-context">context()</a>) -> <a href="#type-key">key()</a> | '$end_of_table'</pre>
 <br></br>
 
 
 
+Behaves as ets:last(Tab) for a given type of registration.
 
-Behaves as ets:last(Tab) for a given type of registration object.
 
 See [`http://www.erlang.org/doc/man/ets.html#last-1`](http://www.erlang.org/doc/man/ets.html#last-1).
-The registry behaves as an ordered_set table.<a name="lookup_global_aggr_counter-1"></a>
+The registry behaves as an ordered_set table.
+<a name="lookup_global_aggr_counter-1"></a>
 
-###lookup_global_aggr_counter/1##
+### lookup_global_aggr_counter/1 ###
 
 
-<pre>lookup_global_aggr_counter(Name::any()) -&gt; integer()</pre>
+<pre><code>
+lookup_global_aggr_counter(Name::any()) -&gt; integer()
+</code></pre>
+
 <br></br>
 
 
 Equivalent to [`where({a, g, Name})`](#where-1).
 
 Lookup a global (unique) aggregated counter and returns its value.
-Fails if there is no such object.<a name="lookup_global_counters-1"></a>
+Fails if there is no such object.
+<a name="lookup_global_counters-1"></a>
+
+### lookup_global_counters/1 ###
 
-###lookup_global_counters/1##
 
+<pre><code>
+lookup_global_counters(Counter::any()) -&gt; [{pid(), Value::integer()}]
+</code></pre>
 
-<pre>lookup_global_counters(Counter::any()) -&gt; [{pid(), Value::integer()}]</pre>
 <br></br>
 
 
 Equivalent to [`lookup_values({c, g, Counter})`](#lookup_values-1).
 
 Look up all global (non-unique) instances of a given Counter.
-Returns a list of {Pid, Value} tuples for all matching objects.<a name="lookup_global_name-1"></a>
+Returns a list of {Pid, Value} tuples for all matching objects.
+<a name="lookup_global_name-1"></a>
 
-###lookup_global_name/1##
+### lookup_global_name/1 ###
 
 
-<pre>lookup_global_name(Name::any()) -&gt; pid()</pre>
+<pre><code>
+lookup_global_name(Name::any()) -&gt; pid()
+</code></pre>
+
 <br></br>
 
 
 Equivalent to [`where({n, g, Name})`](#where-1).
 
-Lookup a global unique name. Fails if there is no such name.<a name="lookup_global_properties-1"></a>
+Lookup a global unique name. Fails if there is no such name.
+<a name="lookup_global_properties-1"></a>
+
+### lookup_global_properties/1 ###
 
-###lookup_global_properties/1##
 
+<pre><code>
+lookup_global_properties(Property::any()) -&gt; [{pid(), Value}]
+</code></pre>
 
-<pre>lookup_global_properties(Property::any()) -&gt; [{pid(), Value}]</pre>
 <br></br>
 
 
 Equivalent to [`lookup_values({p, g, Property})`](#lookup_values-1).
 
 Look up all global (non-unique) instances of a given Property.
-Returns a list of {Pid, Value} tuples for all matching objects.<a name="lookup_local_aggr_counter-1"></a>
+Returns a list of {Pid, Value} tuples for all matching objects.
+<a name="lookup_local_aggr_counter-1"></a>
 
-###lookup_local_aggr_counter/1##
+### lookup_local_aggr_counter/1 ###
 
 
-<pre>lookup_local_aggr_counter(Name::any()) -&gt; integer()</pre>
+<pre><code>
+lookup_local_aggr_counter(Name::any()) -&gt; integer()
+</code></pre>
+
 <br></br>
 
 
 Equivalent to [`where({a, l, Name})`](#where-1).
 
 Lookup a local (unique) aggregated counter and returns its value.
-Fails if there is no such object.<a name="lookup_local_counters-1"></a>
+Fails if there is no such object.
+<a name="lookup_local_counters-1"></a>
+
+### lookup_local_counters/1 ###
 
-###lookup_local_counters/1##
 
+<pre><code>
+lookup_local_counters(Counter::any()) -&gt; [{pid(), Value::integer()}]
+</code></pre>
 
-<pre>lookup_local_counters(Counter::any()) -&gt; [{pid(), Value::integer()}]</pre>
 <br></br>
 
 
 Equivalent to [`lookup_values({c, l, Counter})`](#lookup_values-1).
 
 Look up all local (non-unique) instances of a given Counter.
-Returns a list of {Pid, Value} tuples for all matching objects.<a name="lookup_local_name-1"></a>
+Returns a list of {Pid, Value} tuples for all matching objects.
+<a name="lookup_local_name-1"></a>
 
-###lookup_local_name/1##
+### lookup_local_name/1 ###
 
 
-<pre>lookup_local_name(Name::any()) -&gt; pid()</pre>
+<pre><code>
+lookup_local_name(Name::any()) -&gt; pid()
+</code></pre>
+
 <br></br>
 
 
 Equivalent to [`where({n, l, Name})`](#where-1).
 
-Lookup a local unique name. Fails if there is no such name.<a name="lookup_local_properties-1"></a>
+Lookup a local unique name. Fails if there is no such name.
+<a name="lookup_local_properties-1"></a>
+
+### lookup_local_properties/1 ###
 
-###lookup_local_properties/1##
 
+<pre><code>
+lookup_local_properties(Property::any()) -&gt; [{pid(), Value}]
+</code></pre>
 
-<pre>lookup_local_properties(Property::any()) -&gt; [{pid(), Value}]</pre>
 <br></br>
 
 
 Equivalent to [`lookup_values({p, l, Property})`](#lookup_values-1).
 
 Look up all local (non-unique) instances of a given Property.
-Returns a list of {Pid, Value} tuples for all matching objects.<a name="lookup_pid-1"></a>
+Returns a list of {Pid, Value} tuples for all matching objects.
+<a name="lookup_pid-1"></a>
 
-###lookup_pid/1##
+### lookup_pid/1 ###
 
 
-<pre>lookup_pid(Key) -&gt; Pid</pre>
+<pre><code>
+lookup_pid(Key) -&gt; Pid
+</code></pre>
+
 <br></br>
 
 
 Lookup the Pid stored with a key.
+
 <a name="lookup_pids-1"></a>
 
-###lookup_pids/1##
+### lookup_pids/1 ###
 
 
-<pre>lookup_pids(Key::<a href="#type-key">key()</a>) -> [pid()]</pre>
-<br></br>
+<pre><code>
+lookup_pids(Key::<a href="#type-key">key()</a>) -&gt; [pid()]
+</code></pre>
 
+<br></br>
 
 
 
 Returns a list of pids with the published key Key
 
-If the type of registration entry is either name or aggregated counter,
+
+
+If the type of registration is either name or aggregated counter,
 this function will return either an empty list, or a list of one pid.
-For non-unique types, the return value can be a list of any length.<a name="lookup_value-1"></a>
+For non-unique types, the return value can be a list of any length.
+
+
+Note: shared resources are not associated with any pid, and will
+therefore be excluded.
+<a name="lookup_value-1"></a>
+
+### lookup_value/1 ###
 
-###lookup_value/1##
 
+<pre><code>
+lookup_value(Key) -&gt; Value
+</code></pre>
 
-<pre>lookup_value(Key) -&gt; Value</pre>
 <br></br>
 
 
 Lookup the value stored with a key.
+
 <a name="lookup_values-1"></a>
 
-###lookup_values/1##
+### lookup_values/1 ###
 
 
-<pre>lookup_values(Key::<a href="#type-key">key()</a>) -> [{pid(), Value}]</pre>
-<br></br>
+<pre><code>
+lookup_values(Key::<a href="#type-key">key()</a>) -&gt; [{pid(), Value}]
+</code></pre>
 
+<br></br>
 
 
 
 Retrieve the `{Pid,Value}` pairs corresponding to Key.
 
+
 Key refer to any type of registry object. If it refers to a unique
 object, the list will be of length 0 or 1. If it refers to a non-unique
-object, the return value can be a list of any length.<a name="monitor-1"></a>
+object, the return value can be a list of any length.
+<a name="monitor-1"></a>
 
-###monitor/1##
+### monitor/1 ###
 
 
-<pre>monitor(Key::<a href="#type-key">key()</a>) -> reference()</pre>
-<br></br>
+<pre><code>
+monitor(Key::<a href="#type-key">key()</a>) -&gt; reference()
+</code></pre>
 
+<br></br>
 
 
 
@@ -804,139 +1090,183 @@ a unique name registered via gproc. A message, `{gproc, unreg, Ref, Key}`
 will be sent to the requesting process, if the name is unregistered or
 the registered process dies.
 
-If the name is not yet registered, the same message is sent immediately.<a name="mreg-3"></a>
 
-###mreg/3##
+If the name is not yet registered, the same message is sent immediately.
+<a name="mreg-3"></a>
 
+### mreg/3 ###
 
-<pre>mreg(T::<a href="#type-type">type()</a>, C::<a href="#type-scope">scope()</a>, KVL::[{Key::any(), Value::any()}]) -> true</pre>
-<br></br>
 
+<pre><code>
+mreg(T::<a href="#type-type">type()</a>, C::<a href="#type-scope">scope()</a>, KVL::[{Key::any(), Value::any()}]) -&gt; true
+</code></pre>
+
+<br></br>
 
 
 
 Register multiple {Key,Value} pairs of a given type and scope.
 
+
 This function is more efficient than calling [`reg/2`](#reg-2) repeatedly.
 It is also atomic in regard to unique names; either all names are registered
-or none are.<a name="munreg-3"></a>
+or none are.
+<a name="munreg-3"></a>
 
-###munreg/3##
+### munreg/3 ###
 
 
-<pre>munreg(T::<a href="#type-type">type()</a>, C::<a href="#type-scope">scope()</a>, L::[Key::any()]) -> true</pre>
-<br></br>
+<pre><code>
+munreg(T::<a href="#type-type">type()</a>, C::<a href="#type-scope">scope()</a>, L::[Key::any()]) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
 Unregister multiple Key items of a given type and scope.
 
+
 This function is usually more efficient than calling [`unreg/1`](#unreg-1)
-repeatedly.<a name="nb_wait-1"></a>
+repeatedly.
+<a name="nb_wait-1"></a>
 
-###nb_wait/1##
+### nb_wait/1 ###
 
 
-<pre>nb_wait(Key::<a href="#type-key">key()</a>) -> Ref</pre>
+<pre><code>
+nb_wait(Key::<a href="#type-key">key()</a>) -&gt; Ref
+</code></pre>
+
 <br></br>
 
 
 Wait for a local name to be registered.
 The caller can expect to receive a message,
-{gproc, Ref, registered, {Key, Pid, Value}}, once the name is registered.<a name="nb_wait-2"></a>
+{gproc, Ref, registered, {Key, Pid, Value}}, once the name is registered.
+<a name="nb_wait-2"></a>
+
+### nb_wait/2 ###
 
-###nb_wait/2##
 
+<pre><code>
+nb_wait(Node::node(), Key::<a href="#type-key">key()</a>) -&gt; Ref
+</code></pre>
 
-<pre>nb_wait(Node::node(), Key::<a href="#type-key">key()</a>) -> Ref</pre>
 <br></br>
 
 
 Wait for a local name to be registered on `Node`.
 The caller can expect to receive a message,
-{gproc, Ref, registered, {Key, Pid, Value}}, once the name is registered.<a name="next-2"></a>
+{gproc, Ref, registered, {Key, Pid, Value}}, once the name is registered.
+<a name="next-2"></a>
 
-###next/2##
+### next/2 ###
 
 
-<pre>next(Context::<a href="#type-context">context()</a>, Key::<a href="#type-key">key()</a>) -> <a href="#type-key">key()</a> | '$end_of_table'</pre>
+<pre><code>
+next(Context::<a href="#type-context">context()</a>, Key::<a href="#type-key">key()</a>) -&gt; <a href="#type-key">key()</a> | '$end_of_table'
+</code></pre>
+
 <br></br>
 
 
 
+Behaves as ets:next(Tab,Key) for a given type of registration.
 
-Behaves as ets:next(Tab,Key) for a given type of registration object.
 
 See [`http://www.erlang.org/doc/man/ets.html#next-2`](http://www.erlang.org/doc/man/ets.html#next-2).
-The registry behaves as an ordered_set table.<a name="prev-2"></a>
+The registry behaves as an ordered_set table.
+<a name="prev-2"></a>
+
+### prev/2 ###
 
-###prev/2##
 
+<pre><code>
+prev(Context::<a href="#type-context">context()</a>, Key::<a href="#type-key">key()</a>) -&gt; <a href="#type-key">key()</a> | '$end_of_table'
+</code></pre>
 
-<pre>prev(Context::<a href="#type-context">context()</a>, Key::<a href="#type-key">key()</a>) -> <a href="#type-key">key()</a> | '$end_of_table'</pre>
 <br></br>
 
 
 
+Behaves as ets:prev(Tab,Key) for a given type of registration.
 
-Behaves as ets:prev(Tab,Key) for a given type of registration object.
 
 See [`http://www.erlang.org/doc/man/ets.html#prev-2`](http://www.erlang.org/doc/man/ets.html#prev-2).
-The registry behaves as an ordered_set table.<a name="reg-1"></a>
+The registry behaves as an ordered_set table.
+<a name="reg-1"></a>
 
-###reg/1##
+### reg/1 ###
 
 
-<pre>reg(Key::<a href="#type-key">key()</a>) -> true</pre>
+<pre><code>
+reg(Key::<a href="#type-key">key()</a>) -&gt; true
+</code></pre>
+
 <br></br>
 
 
-Equivalent to [`reg(Key, default(Key))`](#reg-2).<a name="reg-2"></a>
+Equivalent to [`reg(Key, default(Key))`](#reg-2).
+<a name="reg-2"></a>
 
-###reg/2##
+### reg/2 ###
 
 
-<pre>reg(Key::<a href="#type-key">key()</a>, Value) -> true</pre>
-<br></br>
+<pre><code>
+reg(Key::<a href="#type-key">key()</a>, Value) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
 Register a name or property for the current process
 
+
+
 <a name="reg_or_locate-1"></a>
 
-###reg_or_locate/1##
+### reg_or_locate/1 ###
+
 
+<pre><code>
+reg_or_locate(Key::<a href="#type-key">key()</a>) -&gt; true
+</code></pre>
 
-<pre>reg_or_locate(Key::<a href="#type-key">key()</a>) -> true</pre>
 <br></br>
 
 
-Equivalent to [`reg_or_locate(Key, default(Key))`](#reg_or_locate-2).<a name="reg_or_locate-2"></a>
+Equivalent to [`reg_or_locate(Key, default(Key))`](#reg_or_locate-2).
+<a name="reg_or_locate-2"></a>
 
-###reg_or_locate/2##
+### reg_or_locate/2 ###
 
 
-<pre>reg_or_locate(Key::<a href="#type-key">key()</a>, Value) -> {pid(), NewValue}</pre>
-<br></br>
+<pre><code>
+reg_or_locate(Key::<a href="#type-key">key()</a>, Value) -&gt; {pid(), NewValue}
+</code></pre>
 
+<br></br>
 
 
 
 Try registering a unique name, or return existing registration.
 
+
 This function tries to register the name `Key`, if available.
-If such a registration already exists, the pid and value of
-the current registration is returned instead.<a name="reg_or_locate-3"></a>
+If such a registration object already exists, the pid and value of
+the current registration is returned instead.
+<a name="reg_or_locate-3"></a>
 
-###reg_or_locate/3##
+### reg_or_locate/3 ###
 
 
-<pre>reg_or_locate(Key::<a href="#type-key">key()</a>, Value, Fun::function()) -> {pid(), NewValue}</pre>
-<br></br>
+<pre><code>
+reg_or_locate(Key::<a href="#type-key">key()</a>, Value, Fun::function()) -&gt; {pid(), NewValue}
+</code></pre>
 
+<br></br>
 
 
 
@@ -948,30 +1278,38 @@ This function checks whether a local name is registered; if not, it spawns
 a new process (with `spawn(Fun)`) and gives it the name.
 The pid and value of the resulting registration is returned.
 
+
 This function is only available for local registration. While it could
 theoretically be done in the global case, the spawning of a new process
-on a remote node by the leader instance is more problematic.<a name="reg_shared-1"></a>
+on a remote node by the leader instance is more problematic.
+<a name="reg_shared-1"></a>
 
-###reg_shared/1##
+### reg_shared/1 ###
 
 
-<pre>reg_shared(Key::<a href="#type-key">key()</a>) -> true</pre>
-<br></br>
+<pre><code>
+reg_shared(Key::<a href="#type-key">key()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
 Register a resource, but don't tie it to a particular process.
 
+
 `reg_shared({c,l,C}) -> reg_shared({c,l,C}, 0).`
-`reg_shared({a,l,A}) -> reg_shared({a,l,A}, undefined).`<a name="reg_shared-2"></a>
+`reg_shared({a,l,A}) -> reg_shared({a,l,A}, undefined).`
+<a name="reg_shared-2"></a>
 
-###reg_shared/2##
+### reg_shared/2 ###
 
 
-<pre>reg_shared(Key::<a href="#type-key">key()</a>, Value) -> true</pre>
-<br></br>
+<pre><code>
+reg_shared(Key::<a href="#type-key">key()</a>, Value) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
@@ -983,42 +1321,54 @@ Shared resources are all unique. They remain until explicitly unregistered
 (using [`unreg_shared/1`](#unreg_shared-1)). The types of shared resources currently
 supported are `counter` and `aggregated counter`. In listings and query
 results, shared resources appear as other similar resources, except that
-`Pid == shared`. To wit, update_counter({c,l,myCounter}, 1, shared) would
+`Pid == shared`. To wit, update_counter({c,l,myCounter}, shared, 1) would
 increment the shared counter `myCounter` with 1, provided it exists.
 
+
 A shared aggregated counter will track updates in exactly the same way as
-an aggregated counter which is owned by a process.<a name="register_name-2"></a>
+an aggregated counter which is owned by a process.
+<a name="register_name-2"></a>
+
+### register_name/2 ###
 
-###register_name/2##
 
+<pre><code>
+register_name(Name::<a href="#type-key">key()</a>, Pid::pid()) -&gt; yes | no
+</code></pre>
 
-<pre>register_name(Name::<a href="#type-key">key()</a>, Pid::pid()) -> yes | no</pre>
 <br></br>
 
 
-Behaviour support callback<a name="reset_counter-1"></a>
+Behaviour support callback
+<a name="reset_counter-1"></a>
 
-###reset_counter/1##
+### reset_counter/1 ###
 
 
-<pre>reset_counter(Key) -&gt; {ValueBefore, ValueAfter}</pre>
-<ul class="definitions"><li><pre>Key = {c, Scope, Name}</pre></li><li><pre>Scope = l | g</pre></li><li><pre>ValueBefore = integer()</pre></li><li><pre>ValueAfter = integer()</pre></li></ul>
+<pre><code>
+reset_counter(Key) -&gt; {ValueBefore, ValueAfter}
+</code></pre>
 
+<ul class="definitions"><li><code>Key = {c, Scope, Name}</code></li><li><code>Scope = l | g</code></li><li><code>ValueBefore = integer()</code></li><li><code>ValueAfter = integer()</code></li></ul>
 
 
 Reads and resets a counter in a "thread-safe" way
 
+
 This function reads the current value of a counter and then resets it to its
 initial value. The reset operation is done using [`update_counter/2`](#update_counter-2),
 which allows for concurrent calls to [`update_counter/2`](#update_counter-2) without losing
-updates. Aggregated counters are updated accordingly.<a name="select-1"></a>
+updates. Aggregated counters are updated accordingly.
+<a name="select-1"></a>
 
-###select/1##
+### select/1 ###
 
 
-<pre>select(Continuation::Arg) -&gt; [Match] | {[Match], Continuation} | '$end_of_table'</pre>
-<ul class="definitions"><li><pre>Arg = Continuation | <a href="#type-sel_pattern">sel_pattern()</a></pre></li><li><pre>Match = {Key, Pid, Value}</pre></li></ul>
+<pre><code>
+select(Continuation::Arg) -&gt; [Match] | {[Match], Continuation} | '$end_of_table'
+</code></pre>
 
+<ul class="definitions"><li><code>Arg = Continuation | <a href="#type-sel_pattern">sel_pattern()</a></code></li><li><code>Match = {Key, Pid, Value}</code></li></ul>
 
 
 Perform a select operation on the process registry
@@ -1041,17 +1391,21 @@ When only a pattern as single argument is given, both global and local scope,
 as well as all types of object can be searched. Note that the pattern may
 still limit the select operation so that scanning the entire table is avoided.
 
+
 The physical representation in the registry may differ from the above,
 but the select patterns are transformed appropriately. The logical
 representation for the gproc select operations is given by
-<code><a href="#type-headpat">headpat()</a></code>.<a name="select-2"></a>
+<code><a href="#type-headpat">headpat()</a></code>.
+<a name="select-2"></a>
 
-###select/2##
+### select/2 ###
 
 
-<pre>select(Context::<a href="#type-context">context()</a>, Pat::<a href="#type-sel_pattern">sel_pattern()</a>) -> [{Key, Pid, Value}]</pre>
-<br></br>
+<pre><code>
+select(Context::<a href="#type-context">context()</a>, Pat::<a href="#type-sel_pattern">sel_pattern()</a>) -&gt; [{Key, Pid, Value}]
+</code></pre>
 
+<br></br>
 
 
 
@@ -1062,6 +1416,7 @@ Perform a select operation with limited context on the process registry
 The physical representation in the registry may differ from the above,
 but the select patterns are transformed appropriately.
 
+
 Note that limiting the context is just a convenience function, allowing you
 to write a simpler select pattern and still avoid searching the entire
 registry. Whenever variables are used in the head pattern, this will result
@@ -1069,66 +1424,131 @@ in a wider scan, even if the values are restricted through a guard (e.g.
 `select([{'$1','$2','$3'}, [{'==', '$1', p}], ...])` will count as a wild
 pattern on the key and result in a full scan). In this case, specifying a
 Context will allow gproc to perform some variable substitution and ensure
-that the scan is limited.<a name="select-3"></a>
+that the scan is limited.
+<a name="select-3"></a>
 
-###select/3##
+### select/3 ###
 
 
-<pre>select(Context::<a href="#type-context">context()</a>, Pat::<a href="#type-sel_patten">sel_patten()</a>, Limit::integer()) -> {[Match], Continuation} | '$end_of_table'</pre>
-<br></br>
+<pre><code>
+select(Context::<a href="#type-context">context()</a>, Pat::<a href="#type-sel_patten">sel_patten()</a>, Limit::integer()) -&gt; {[Match], Continuation} | '$end_of_table'
+</code></pre>
 
+<br></br>
 
 
 
 Like [`select/2`](#select-2) but returns Limit objects at a time.
 
-See [`http://www.erlang.org/doc/man/ets.html#select-3`](http://www.erlang.org/doc/man/ets.html#select-3).<a name="select_count-1"></a>
 
-###select_count/1##
+See [`http://www.erlang.org/doc/man/ets.html#select-3`](http://www.erlang.org/doc/man/ets.html#select-3).
+<a name="select_count-1"></a>
+
+### select_count/1 ###
+
 
+<pre><code>
+select_count(Pat::<a href="#type-sel_pattern">sel_pattern()</a>) -&gt; [<a href="#type-sel_object">sel_object()</a>]
+</code></pre>
 
-<pre>select_count(Pat::<a href="#type-sel_pattern">sel_pattern()</a>) -> [<a href="#type-sel_object">sel_object()</a>]</pre>
 <br></br>
 
 
-Equivalent to [`select_count(all, Pat)`](#select_count-2).<a name="select_count-2"></a>
+Equivalent to [`select_count(all, Pat)`](#select_count-2).
+<a name="select_count-2"></a>
 
-###select_count/2##
+### select_count/2 ###
 
 
-<pre>select_count(Context::<a href="#type-context">context()</a>, Pat::<a href="#type-sel_pattern">sel_pattern()</a>) -> [{Key, Pid, Value}]</pre>
-<br></br>
+<pre><code>
+select_count(Context::<a href="#type-context">context()</a>, Pat::<a href="#type-sel_pattern">sel_pattern()</a>) -&gt; [{Key, Pid, Value}]
+</code></pre>
 
+<br></br>
 
 
 
 Perform a select_count operation on the process registry.
 
+
 The physical representation in the registry may differ from the above,
-but the select patterns are transformed appropriately.<a name="send-2"></a>
+but the select patterns are transformed appropriately.
+<a name="send-2"></a>
 
-###send/2##
+### send/2 ###
 
 
-<pre>send(Key::<a href="#type-key">key()</a>, Msg::any()) -> Msg</pre>
-<br></br>
+<pre><code>
+send(Key::<a href="#type-key">key()</a>, Msg::any()) -&gt; Msg
+</code></pre>
 
+<br></br>
 
 
 
 Sends a message to the process, or processes, corresponding to Key.
 
+
 If Key belongs to a unique object (name or aggregated counter), this
 function will send a message to the corresponding process, or fail if there
 is no such process. If Key is for a non-unique object type (counter or
-property), Msg will be send to all processes that have such an object.<a name="set_env-5"></a>
+property), Msg will be send to all processes that have such an object.
+<a name="set_attributes-2"></a>
+
+### set_attributes/2 ###
+
+
+<pre><code>
+set_attributes(Key::<a href="#type-key">key()</a>, Props::[{atom(), any()}]) -&gt; true
+</code></pre>
+
+<br></br>
+
+
 
-###set_env/5##
+Add/modify `{Key, Value}` attributes associated with a registration.
 
 
-<pre>set_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom(), Value::term(), Strategy) -> Value</pre>
-<ul class="definitions"><li><pre>Strategy = [Alternative]</pre></li><li><pre>Alternative = app_env | os_env | {os_env, VAR} | {mnesia, ActivityType, Oid, Pos}</pre></li></ul>
 
+Gproc registration objects can have `{Key, Value}` attributes associated with
+them. These are stored in a way that doesn't affect the cost of name lookup.
+
+
+Attributs can be retrieved using `gproc:get_attribute/3` or
+`gproc:get_attributes/2`.
+<a name="set_attributes_shared-2"></a>
+
+### set_attributes_shared/2 ###
+
+
+<pre><code>
+set_attributes_shared(Key::<a href="#type-key">key()</a>, Props::[{K, V}]) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+
+Add/modify `{Key, Value}` attributes associated with a shared registration.
+
+
+
+Gproc registration objects can have `{Key, Value}` attributes associated with
+them. These are stored in a way that doesn't affect the cost of name lookup.
+
+
+Attributes can be retrieved using `gproc:get_attribute/3` or
+`gproc:get_attributes/2`.
+<a name="set_env-5"></a>
+
+### set_env/5 ###
+
+
+<pre><code>
+set_env(Scope::<a href="#type-scope">scope()</a>, App::atom(), Key::atom(), Value::term(), Strategy) -&gt; Value
+</code></pre>
+
+<ul class="definitions"><li><code>Strategy = [Alternative]</code></li><li><code>Alternative = app_env | os_env | {os_env, VAR} | {mnesia, ActivityType, Oid, Pos}</code></li></ul>
 
 
 Updates the cached value as well as underlying environment.
@@ -1140,68 +1560,111 @@ environment outside gproc. This function modifies the cached value, and then
 proceeds to update the underlying environment (OS environment variable or
 application environment variable).
 
+
 When the `mnesia` alternative is used, gproc will try to update any existing
 object, changing only the `Pos` position. If no such object exists, it will
 create a new object, setting any other attributes (except `Pos` and the key)
-to `undefined`.<a name="set_value-2"></a>
+to `undefined`.
+<a name="set_value-2"></a>
 
-###set_value/2##
+### set_value/2 ###
 
 
-<pre>set_value(Key::<a href="#type-key">key()</a>, Value) -> true</pre>
-<br></br>
+<pre><code>
+set_value(Key::<a href="#type-key">key()</a>, Value) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
-Sets the value of the registeration entry given by Key
+Sets the value of the registration given by Key
 
 
 
 Key is assumed to exist and belong to the calling process.
 If it doesn't, this function will exit.
 
+
 Value can be any term, unless the object is a counter, in which case
-it must be an integer.<a name="start_link-0"></a>
+it must be an integer.
+<a name="set_value_shared-2"></a>
+
+### set_value_shared/2 ###
 
-###start_link/0##
 
+<pre><code>
+set_value_shared(Key::<a href="#type-key">key()</a>, Value) -&gt; true
+</code></pre>
 
-<pre>start_link() -&gt; {ok, pid()}</pre>
 <br></br>
 
 
 
+Sets the value of the shared registration given by Key
+
+
+
+Key is assumed to exist as a shared entity.
+If it doesn't, this function will exit.
+
+
+Value can be any term, unless the object is a counter, in which case
+it must be an integer.
+<a name="start_link-0"></a>
+
+### start_link/0 ###
+
+
+<pre><code>
+start_link() -&gt; {ok, pid()}
+</code></pre>
+
+<br></br>
+
+
 
 Starts the gproc server.
 
+
 This function is intended to be called from gproc_sup, as part of
-starting the gproc application.<a name="table-0"></a>
+starting the gproc application.
+<a name="table-0"></a>
 
-###table/0##
+### table/0 ###
 
 
-<pre>table() -&gt; any()</pre>
+<pre><code>
+table() -&gt; any()
+</code></pre>
+
 <br></br>
 
 
-Equivalent to [`table({all, all})`](#table-1).<a name="table-1"></a>
+Equivalent to [`table({all, all})`](#table-1).
+<a name="table-1"></a>
+
+### table/1 ###
 
-###table/1##
 
+<pre><code>
+table(Context::<a href="#type-context">context()</a>) -&gt; any()
+</code></pre>
 
-<pre>table(Context::<a href="#type-context">context()</a>) -> any()</pre>
 <br></br>
 
 
-Equivalent to [`table(Context, [])`](#table-2).<a name="table-2"></a>
+Equivalent to [`table(Context, [])`](#table-2).
+<a name="table-2"></a>
 
-###table/2##
+### table/2 ###
 
 
-<pre>table(Context::<a href="#type-context">context()</a>, Opts) -> any()</pre>
-<br></br>
+<pre><code>
+table(Context::<a href="#type-context">context()</a>, Opts) -&gt; any()
+</code></pre>
 
+<br></br>
 
 
 
@@ -1209,6 +1672,7 @@ QLC table generator for the gproc registry.
 Context specifies which subset of the registry should be queried.
 See [`http://www.erlang.org/doc/man/qlc.html`](http://www.erlang.org/doc/man/qlc.html).
 
+
 NOTE: By default, the gproc table generator will not filter out entries
 belonging to processes that have just died, but which have yet to be cleared
 out of the registry. Use the option `check_pids` (or `{check_pids, true}`)
@@ -1216,39 +1680,50 @@ if you want to filter out dead entries already in the query. There will be
 some overhead associated with doing so, and given that the process monitoring
 is asynchronous, there can never be any guarantee that there are no dead
 entries in the list by the time your program processes it.
+
 <a name="unreg-1"></a>
 
-###unreg/1##
+### unreg/1 ###
 
 
-<pre>unreg(Key::<a href="#type-key">key()</a>) -> true</pre>
+<pre><code>
+unreg(Key::<a href="#type-key">key()</a>) -&gt; true
+</code></pre>
+
 <br></br>
 
 
-Unregister a name or property.<a name="unreg_shared-1"></a>
+Unregister a name or property.
+<a name="unreg_shared-1"></a>
 
-###unreg_shared/1##
+### unreg_shared/1 ###
 
 
-<pre>unreg_shared(Key::<a href="#type-key">key()</a>) -> true</pre>
-<br></br>
+<pre><code>
+unreg_shared(Key::<a href="#type-key">key()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
-Unregister a shared resource.<a name="unregister_name-1"></a>
 
-###unregister_name/1##
+Unregister a shared resource.
+<a name="unregister_name-1"></a>
 
+### unregister_name/1 ###
 
 `unregister_name(Key) -> any()`
 
-Equivalent to `unreg / 1`.<a name="update_counter-2"></a>
+Equivalent to `unreg / 1`.
+<a name="update_counter-2"></a>
 
-###update_counter/2##
+### update_counter/2 ###
 
 
-<pre>update_counter(Key::<a href="#type-key">key()</a>, Incr::<a href="#type-increment">increment()</a>) -> integer()</pre>
-<br></br>
+<pre><code>
+update_counter(Key::<a href="#type-key">key()</a>, Incr::<a href="#type-increment">increment()</a>) -&gt; integer()
+</code></pre>
 
+<br></br>
 
 
 
@@ -1258,18 +1733,39 @@ Updates the counter registered as Key for the current process.
 
 This function works almost exactly like ets:update_counter/3
 (see [`http://www.erlang.org/doc/man/ets.html#update_counter-3`](http://www.erlang.org/doc/man/ets.html#update_counter-3)), but
-will fail if the type of object referred to by Key is not a counter.
+will fail if the type of object referred to by Key is not a counter or
+a unique name (update_counter/2 can be performed on names as well, but they
+do not count as counter objects, and do not affect aggregated counters).
+
+
 
 Aggregated counters with the same name will be updated automatically.
 The `UpdateOp` patterns are the same as for `ets:update_counter/3`, except
-that the position is omitted; in gproc, the value position is always `3`.<a name="update_counters-2"></a>
+that the position is omitted; in gproc, the value position is always `3`.
 
-###update_counters/2##
 
+If `Key` refers to a unique name, the operation will depend on the value
+part of the registration being an integer(). While non-integer values are
+not permitted at all for counter objects, it is the user's responsibility to
+ensure that a name, on which `update_counter/2` is to be performed, has the
+appropriate value type.
+<a name="update_counter-3"></a>
+
+### update_counter/3 ###
+
+`update_counter(Key, Pid, Incr) -> any()`
 
-<pre>update_counters(X1::<a href="#type-scope">scope()</a>, Cs::[{<a href="#type-key">key()</a>, pid(), <a href="#type-increment">increment()</a>}]) -> [{<a href="#type-key">key()</a>, pid(), integer()}]</pre>
-<br></br>
 
+<a name="update_counters-2"></a>
+
+### update_counters/2 ###
+
+
+<pre><code>
+update_counters(X1::<a href="#type-scope">scope()</a>, Cs::[{<a href="#type-key">key()</a>, pid(), <a href="#type-increment">increment()</a>}]) -&gt; [{<a href="#type-key">key()</a>, pid(), integer()}]
+</code></pre>
+
+<br></br>
 
 
 
@@ -1281,14 +1777,18 @@ This function is not atomic, except (in a sense) for global counters. For local
 it is more of a convenience function. For global counters, it is much more efficient
 than calling `gproc:update_counter/2` for each individual counter.
 
-The return value is the corresponding list of `[{Counter, Pid, NewValue}]`.<a name="update_shared_counter-2"></a>
 
-###update_shared_counter/2##
+The return value is the corresponding list of `[{Counter, Pid, NewValue}]`.
+<a name="update_shared_counter-2"></a>
+
+### update_shared_counter/2 ###
 
 
-<pre>update_shared_counter(Key::<a href="#type-key">key()</a>, Incr) -> integer() | [integer()]</pre>
-<ul class="definitions"><li><pre>Incr = IncrVal | UpdateOp | [UpdateOp]</pre></li><li><pre>UpdateOp = IncrVal | {IncrVal, Threshold, SetValue}</pre></li><li><pre>IncrVal = integer()</pre></li></ul>
+<pre><code>
+update_shared_counter(Key::<a href="#type-key">key()</a>, Incr) -&gt; integer() | [integer()]
+</code></pre>
 
+<ul class="definitions"><li><code>Incr = IncrVal | UpdateOp | [UpdateOp]</code></li><li><code>UpdateOp = IncrVal | {IncrVal, Threshold, SetValue}</code></li><li><code>IncrVal = integer()</code></li></ul>
 
 
 Updates the shared counter registered as Key.
@@ -1299,38 +1799,46 @@ This function works almost exactly like ets:update_counter/3
 (see [`http://www.erlang.org/doc/man/ets.html#update_counter-3`](http://www.erlang.org/doc/man/ets.html#update_counter-3)), but
 will fail if the type of object referred to by Key is not a counter.
 
+
 Aggregated counters with the same name will be updated automatically.
 The `UpdateOp` patterns are the same as for `ets:update_counter/3`, except
-that the position is omitted; in gproc, the value position is always `3`.<a name="where-1"></a>
+that the position is omitted; in gproc, the value position is always `3`.
+<a name="where-1"></a>
 
-###where/1##
+### where/1 ###
 
 
-<pre>where(Key::<a href="#type-key">key()</a>) -> pid()</pre>
-<br></br>
+<pre><code>
+where(Key::<a href="#type-key">key()</a>) -&gt; pid()
+</code></pre>
 
+<br></br>
 
 
 
 Returns the pid registered as Key
 
-The type of registration entry must be either name or aggregated counter.
-Otherwise this function will exit. Use [`lookup_pids/1`](#lookup_pids-1) in these
-cases.<a name="whereis_name-1"></a>
 
-###whereis_name/1##
+The type of registration must be either name or aggregated counter.
+Otherwise this function will exit. Use [`lookup_pids/1`](#lookup_pids-1) in these
+cases.
+<a name="whereis_name-1"></a>
 
+### whereis_name/1 ###
 
 `whereis_name(Key) -> any()`
 
-Equivalent to `where / 1`.<a name="wide_await-3"></a>
+Equivalent to `where / 1`.
+<a name="wide_await-3"></a>
 
-###wide_await/3##
+### wide_await/3 ###
 
 
-<pre>wide_await(Nodes::[node()], Key::<a href="#type-key">key()</a>, Timeout) -> {pid(), Value}</pre>
-<ul class="definitions"><li><pre>Timeout = integer() | infinity</pre></li></ul>
+<pre><code>
+wide_await(Nodes::[node()], Key::<a href="#type-key">key()</a>, Timeout) -&gt; {pid(), Value}
+</code></pre>
 
+<ul class="definitions"><li><code>Timeout = integer() | infinity</code></li></ul>
 
 
 Wait for a local name to be registered on any of `Nodes`.
@@ -1339,5 +1847,6 @@ the `Nodes` list at the same time. The first node to respond with a
 process registered as `Key` will provide the result. Other results are
 ignored. `Key` must be a unique name with local scope, i.e. `{n,l,Name}`.
 
+
 An exception is thrown upon timeout, or if no node can be reached (if gproc is
-not running on a given node, this is treated the same as the node being down).
+not running on a given node, this is treated the same as the node being down).

+ 11 - 11
doc/gproc_app.md

@@ -1,13 +1,13 @@
 
 
-#Module gproc_app#
+# Module gproc_app #
 * [Function Index](#index)
 * [Function Details](#functions)
 
+__Behaviours:__ [`application`](application.md).
+<a name="index"></a>
 
-__Behaviours:__ [`application`](application.md).<a name="index"></a>
-
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#start-0">start/0</a></td><td></td></tr><tr><td valign="top"><a href="#start-2">start/2</a></td><td></td></tr><tr><td valign="top"><a href="#stop-1">stop/1</a></td><td></td></tr></table>
@@ -15,26 +15,26 @@ __Behaviours:__ [`application`](application.md).<a name="index"></a>
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="start-0"></a>
 
-###start/0##
-
+### start/0 ###
 
 `start() -> any()`
 
-<a name="start-2"></a>
 
-###start/2##
+<a name="start-2"></a>
 
+### start/2 ###
 
 `start(Type, StartArgs) -> any()`
 
-<a name="stop-1"></a>
 
-###stop/1##
+<a name="stop-1"></a>
 
+### stop/1 ###
 
 `stop(State) -> any()`
 
+

+ 20 - 20
doc/gproc_bcast.md

@@ -1,6 +1,6 @@
 
 
-#Module gproc_bcast#
+# Module gproc_bcast #
 * [Description](#description)
 * [Function Index](#index)
 * [Function Details](#functions)
@@ -8,12 +8,12 @@
 
 Gproc message broadcast server
 This module is used to support gproc:bcast(Key, Msg).
-
 __Behaviours:__ [`gen_server`](gen_server.md).
 
-__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).<a name="description"></a>
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
 
-##Description##
+## Description ##
 
 
 gproc:bcast/2 allows for e.g. distributed publish/subscribe, without
@@ -21,7 +21,7 @@ having to resort to global property registration.
 To ensure that erlang's message ordering guarantees are kept, all sends
 are channeled through a broadcast server on each node.<a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#code_change-3">code_change/3</a></td><td></td></tr><tr><td valign="top"><a href="#handle_call-3">handle_call/3</a></td><td></td></tr><tr><td valign="top"><a href="#handle_cast-2">handle_cast/2</a></td><td></td></tr><tr><td valign="top"><a href="#handle_info-2">handle_info/2</a></td><td></td></tr><tr><td valign="top"><a href="#init-1">init/1</a></td><td></td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td></td></tr><tr><td valign="top"><a href="#terminate-2">terminate/2</a></td><td></td></tr></table>
@@ -29,54 +29,54 @@ are channeled through a broadcast server on each node.<a name="index"></a>
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="code_change-3"></a>
 
-###code_change/3##
-
+### code_change/3 ###
 
 `code_change(X1, S, X3) -> any()`
 
-<a name="handle_call-3"></a>
 
-###handle_call/3##
+<a name="handle_call-3"></a>
 
+### handle_call/3 ###
 
 `handle_call(X1, X2, S) -> any()`
 
-<a name="handle_cast-2"></a>
 
-###handle_cast/2##
+<a name="handle_cast-2"></a>
 
+### handle_cast/2 ###
 
 `handle_cast(X1, S) -> any()`
 
-<a name="handle_info-2"></a>
 
-###handle_info/2##
+<a name="handle_info-2"></a>
 
+### handle_info/2 ###
 
 `handle_info(X1, S) -> any()`
 
-<a name="init-1"></a>
 
-###init/1##
+<a name="init-1"></a>
 
+### init/1 ###
 
 `init(X1) -> any()`
 
-<a name="start_link-0"></a>
 
-###start_link/0##
+<a name="start_link-0"></a>
 
+### start_link/0 ###
 
 `start_link() -> any()`
 
-<a name="terminate-2"></a>
 
-###terminate/2##
+<a name="terminate-2"></a>
 
+### terminate/2 ###
 
 `terminate(X1, X2) -> any()`
 
+

+ 104 - 82
doc/gproc_dist.md

@@ -1,26 +1,26 @@
 
 
-#Module gproc_dist#
+# Module gproc_dist #
 * [Description](#description)
 * [Function Index](#index)
 * [Function Details](#functions)
 
 
 Extended process registry.
-
 __Behaviours:__ [`gen_leader`](/Users/uwiger/FL/git/gen_leader/doc/gen_leader.md).
 
-__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).<a name="description"></a>
-
-##Description##
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
 
+## Description ##
 
 This module implements an extended process registry
 
 
-For a detailed description, see gproc/doc/erlang07-wiger.pdf.<a name="index"></a>
+For a detailed description, see gproc/doc/erlang07-wiger.pdf.
+<a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#code_change-4">code_change/4</a></td><td></td></tr><tr><td valign="top"><a href="#elected-2">elected/2</a></td><td></td></tr><tr><td valign="top"><a href="#elected-3">elected/3</a></td><td></td></tr><tr><td valign="top"><a href="#from_leader-3">from_leader/3</a></td><td></td></tr><tr><td valign="top"><a href="#get_leader-0">get_leader/0</a></td><td>Returns the node of the current gproc leader.</td></tr><tr><td valign="top"><a href="#give_away-2">give_away/2</a></td><td></td></tr><tr><td valign="top"><a href="#handle_DOWN-3">handle_DOWN/3</a></td><td></td></tr><tr><td valign="top"><a href="#handle_call-4">handle_call/4</a></td><td></td></tr><tr><td valign="top"><a href="#handle_cast-3">handle_cast/3</a></td><td></td></tr><tr><td valign="top"><a href="#handle_info-2">handle_info/2</a></td><td></td></tr><tr><td valign="top"><a href="#handle_info-3">handle_info/3</a></td><td></td></tr><tr><td valign="top"><a href="#handle_leader_call-4">handle_leader_call/4</a></td><td></td></tr><tr><td valign="top"><a href="#handle_leader_cast-3">handle_leader_cast/3</a></td><td></td></tr><tr><td valign="top"><a href="#init-1">init/1</a></td><td></td></tr><tr><td valign="top"><a href="#leader_call-1">leader_call/1</a></td><td></td></tr><tr><td valign="top"><a href="#leader_cast-1">leader_cast/1</a></td><td></td></tr><tr><td valign="top"><a href="#mreg-2">mreg/2</a></td><td></td></tr><tr><td valign="top"><a href="#munreg-2">munreg/2</a></td><td></td></tr><tr><td valign="top"><a href="#reg-1">reg/1</a></td><td></td></tr><tr><td valign="top"><a href="#reg-2">reg/2</a></td><td>
@@ -28,152 +28,155 @@ Class = n  - unique name
 | p  - non-unique property
 | c  - counter
 | a  - aggregated counter
-Scope = l | g (global or local).</td></tr><tr><td valign="top"><a href="#reg_or_locate-3">reg_or_locate/3</a></td><td></td></tr><tr><td valign="top"><a href="#reg_shared-2">reg_shared/2</a></td><td></td></tr><tr><td valign="top"><a href="#reset_counter-1">reset_counter/1</a></td><td></td></tr><tr><td valign="top"><a href="#set_value-2">set_value/2</a></td><td></td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td></td></tr><tr><td valign="top"><a href="#start_link-1">start_link/1</a></td><td></td></tr><tr><td valign="top"><a href="#surrendered-3">surrendered/3</a></td><td></td></tr><tr><td valign="top"><a href="#sync-0">sync/0</a></td><td>Synchronize with the gproc leader.</td></tr><tr><td valign="top"><a href="#terminate-2">terminate/2</a></td><td></td></tr><tr><td valign="top"><a href="#unreg-1">unreg/1</a></td><td></td></tr><tr><td valign="top"><a href="#unreg_shared-1">unreg_shared/1</a></td><td></td></tr><tr><td valign="top"><a href="#update_counter-2">update_counter/2</a></td><td></td></tr><tr><td valign="top"><a href="#update_counters-1">update_counters/1</a></td><td></td></tr><tr><td valign="top"><a href="#update_shared_counter-2">update_shared_counter/2</a></td><td></td></tr></table>
+Scope = l | g (global or local).</td></tr><tr><td valign="top"><a href="#reg_or_locate-3">reg_or_locate/3</a></td><td></td></tr><tr><td valign="top"><a href="#reg_shared-2">reg_shared/2</a></td><td></td></tr><tr><td valign="top"><a href="#reset_counter-1">reset_counter/1</a></td><td></td></tr><tr><td valign="top"><a href="#set_attributes-2">set_attributes/2</a></td><td></td></tr><tr><td valign="top"><a href="#set_attributes_shared-2">set_attributes_shared/2</a></td><td></td></tr><tr><td valign="top"><a href="#set_value-2">set_value/2</a></td><td></td></tr><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td></td></tr><tr><td valign="top"><a href="#start_link-1">start_link/1</a></td><td></td></tr><tr><td valign="top"><a href="#surrendered-3">surrendered/3</a></td><td></td></tr><tr><td valign="top"><a href="#sync-0">sync/0</a></td><td>Synchronize with the gproc leader.</td></tr><tr><td valign="top"><a href="#terminate-2">terminate/2</a></td><td></td></tr><tr><td valign="top"><a href="#unreg-1">unreg/1</a></td><td></td></tr><tr><td valign="top"><a href="#unreg_shared-1">unreg_shared/1</a></td><td></td></tr><tr><td valign="top"><a href="#update_counter-3">update_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#update_counters-1">update_counters/1</a></td><td></td></tr><tr><td valign="top"><a href="#update_shared_counter-2">update_shared_counter/2</a></td><td></td></tr></table>
 
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="code_change-4"></a>
 
-###code_change/4##
-
+### code_change/4 ###
 
 `code_change(FromVsn, S, Extra, E) -> any()`
 
-<a name="elected-2"></a>
 
-###elected/2##
+<a name="elected-2"></a>
 
+### elected/2 ###
 
 `elected(S, E) -> any()`
 
-<a name="elected-3"></a>
 
-###elected/3##
+<a name="elected-3"></a>
 
+### elected/3 ###
 
 `elected(S, E, Node) -> any()`
 
-<a name="from_leader-3"></a>
 
-###from_leader/3##
+<a name="from_leader-3"></a>
 
+### from_leader/3 ###
 
 `from_leader(Ops, S, E) -> any()`
 
+
 <a name="get_leader-0"></a>
 
-###get_leader/0##
+### get_leader/0 ###
 
 
-<pre>get_leader() -&gt; node()</pre>
-<br></br>
+<pre><code>
+get_leader() -&gt; node()
+</code></pre>
 
+<br></br>
 
-Returns the node of the current gproc leader.<a name="give_away-2"></a>
 
-###give_away/2##
+Returns the node of the current gproc leader.
+<a name="give_away-2"></a>
 
+### give_away/2 ###
 
 `give_away(Key, To) -> any()`
 
-<a name="handle_DOWN-3"></a>
 
-###handle_DOWN/3##
+<a name="handle_DOWN-3"></a>
 
+### handle_DOWN/3 ###
 
 `handle_DOWN(Node, S, E) -> any()`
 
-<a name="handle_call-4"></a>
 
-###handle_call/4##
+<a name="handle_call-4"></a>
 
+### handle_call/4 ###
 
 `handle_call(X1, X2, S, E) -> any()`
 
-<a name="handle_cast-3"></a>
 
-###handle_cast/3##
+<a name="handle_cast-3"></a>
 
+### handle_cast/3 ###
 
 `handle_cast(Msg, S, X3) -> any()`
 
-<a name="handle_info-2"></a>
 
-###handle_info/2##
+<a name="handle_info-2"></a>
 
+### handle_info/2 ###
 
 `handle_info(X1, S) -> any()`
 
-<a name="handle_info-3"></a>
 
-###handle_info/3##
+<a name="handle_info-3"></a>
 
+### handle_info/3 ###
 
 `handle_info(Msg, S, E) -> any()`
 
-<a name="handle_leader_call-4"></a>
 
-###handle_leader_call/4##
+<a name="handle_leader_call-4"></a>
 
+### handle_leader_call/4 ###
 
 `handle_leader_call(X1, From, State, E) -> any()`
 
-<a name="handle_leader_cast-3"></a>
 
-###handle_leader_cast/3##
+<a name="handle_leader_cast-3"></a>
 
+### handle_leader_cast/3 ###
 
 `handle_leader_cast(X1, S, E) -> any()`
 
-<a name="init-1"></a>
 
-###init/1##
+<a name="init-1"></a>
 
+### init/1 ###
 
 `init(Opts) -> any()`
 
-<a name="leader_call-1"></a>
 
-###leader_call/1##
+<a name="leader_call-1"></a>
 
+### leader_call/1 ###
 
 `leader_call(Req) -> any()`
 
-<a name="leader_cast-1"></a>
 
-###leader_cast/1##
+<a name="leader_cast-1"></a>
 
+### leader_cast/1 ###
 
 `leader_cast(Msg) -> any()`
 
-<a name="mreg-2"></a>
 
-###mreg/2##
+<a name="mreg-2"></a>
 
+### mreg/2 ###
 
 `mreg(T, KVL) -> any()`
 
-<a name="munreg-2"></a>
 
-###munreg/2##
+<a name="munreg-2"></a>
 
+### munreg/2 ###
 
 `munreg(T, Keys) -> any()`
 
-<a name="reg-1"></a>
 
-###reg/1##
+<a name="reg-1"></a>
 
+### reg/1 ###
 
 `reg(Key) -> any()`
 
-<a name="reg-2"></a>
 
-###reg/2##
+<a name="reg-2"></a>
 
+### reg/2 ###
 
 `reg(Key, Value) -> any()`
 
@@ -182,68 +185,86 @@ Class = n  - unique name
 | p  - non-unique property
 | c  - counter
 | a  - aggregated counter
-Scope = l | g (global or local)<a name="reg_or_locate-3"></a>
-
-###reg_or_locate/3##
+Scope = l | g (global or local)
+<a name="reg_or_locate-3"></a>
 
+### reg_or_locate/3 ###
 
 `reg_or_locate(Key, Value, Pid) -> any()`
 
-<a name="reg_shared-2"></a>
 
-###reg_shared/2##
+<a name="reg_shared-2"></a>
 
+### reg_shared/2 ###
 
 `reg_shared(Key, Value) -> any()`
 
-<a name="reset_counter-1"></a>
 
-###reset_counter/1##
+<a name="reset_counter-1"></a>
 
+### reset_counter/1 ###
 
 `reset_counter(Key) -> any()`
 
-<a name="set_value-2"></a>
 
-###set_value/2##
+<a name="set_attributes-2"></a>
+
+### set_attributes/2 ###
+
+`set_attributes(Key, Attrs) -> any()`
 
 
+<a name="set_attributes_shared-2"></a>
+
+### set_attributes_shared/2 ###
+
+`set_attributes_shared(Key, Attrs) -> any()`
+
+
+<a name="set_value-2"></a>
+
+### set_value/2 ###
+
 `set_value(Key, Value) -> any()`
 
-<a name="start_link-0"></a>
 
-###start_link/0##
+<a name="start_link-0"></a>
 
+### start_link/0 ###
 
 `start_link() -> any()`
 
-<a name="start_link-1"></a>
 
-###start_link/1##
+<a name="start_link-1"></a>
 
+### start_link/1 ###
 
 `start_link(Nodes) -> any()`
 
-<a name="surrendered-3"></a>
 
-###surrendered/3##
+<a name="surrendered-3"></a>
 
+### surrendered/3 ###
 
 `surrendered(State, X2, E) -> any()`
 
+
 <a name="sync-0"></a>
 
-###sync/0##
+### sync/0 ###
 
 
-<pre>sync() -&gt; true</pre>
-<br></br>
+<pre><code>
+sync() -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
 Synchronize with the gproc leader
 
+
 This function can be used to ensure that data has been replicated from the
 leader to the current node. It does so by asking the leader to ping all
 live participating nodes. The call will return `true` when all these nodes
@@ -251,45 +272,46 @@ have either responded or died. In the special case where the leader dies
 during an ongoing sync, the call will fail with a timeout exception.
 (Actually, it should be a `leader_died` exception; more study needed to find
 out why gen_leader times out in this situation, rather than reporting that
-the leader died.)<a name="terminate-2"></a>
-
-###terminate/2##
+the leader died.)
+<a name="terminate-2"></a>
 
+### terminate/2 ###
 
 `terminate(Reason, S) -> any()`
 
-<a name="unreg-1"></a>
 
-###unreg/1##
+<a name="unreg-1"></a>
 
+### unreg/1 ###
 
 `unreg(Key) -> any()`
 
-<a name="unreg_shared-1"></a>
 
-###unreg_shared/1##
+<a name="unreg_shared-1"></a>
 
+### unreg_shared/1 ###
 
 `unreg_shared(Key) -> any()`
 
-<a name="update_counter-2"></a>
 
-###update_counter/2##
+<a name="update_counter-3"></a>
 
+### update_counter/3 ###
 
-`update_counter(Key, Incr) -> any()`
+`update_counter(Key, Pid, Incr) -> any()`
 
-<a name="update_counters-1"></a>
 
-###update_counters/1##
+<a name="update_counters-1"></a>
 
+### update_counters/1 ###
 
 `update_counters(List) -> any()`
 
-<a name="update_shared_counter-2"></a>
 
-###update_shared_counter/2##
+<a name="update_shared_counter-2"></a>
 
+### update_shared_counter/2 ###
 
 `update_shared_counter(Key, Incr) -> any()`
 
+

+ 11 - 7
doc/gproc_info.md

@@ -1,13 +1,13 @@
 
 
-#Module gproc_info#
+# Module gproc_info #
 * [Function Index](#index)
 * [Function Details](#functions)
 
+__Authors:__ Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erlang-solutions.com)).
+<a name="index"></a>
 
-__Authors:__ Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erlang-solutions.com)).<a name="index"></a>
-
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#i-0">i/0</a></td><td></td></tr></table>
@@ -15,14 +15,18 @@ __Authors:__ Ulf Wiger ([`ulf.wiger@erlang-solutions.com`](mailto:ulf.wiger@erla
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="i-0"></a>
 
-###i/0##
+### i/0 ###
+
 
+<pre><code>
+i() -&gt; ok
+</code></pre>
 
-<pre>i() -&gt; ok</pre>
 <br></br>
 
 
+

+ 15 - 7
doc/gproc_init.md

@@ -1,13 +1,13 @@
 
 
-#Module gproc_init#
+# Module gproc_init #
 * [Function Index](#index)
 * [Function Details](#functions)
 
 
 <a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#hard_reset-0">hard_reset/0</a></td><td></td></tr><tr><td valign="top"><a href="#soft_reset-0">soft_reset/0</a></td><td></td></tr></table>
@@ -15,23 +15,31 @@
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="hard_reset-0"></a>
 
-###hard_reset/0##
+### hard_reset/0 ###
 
 
-<pre>hard_reset() -&gt; ok</pre>
+<pre><code>
+hard_reset() -&gt; ok
+</code></pre>
+
 <br></br>
 
 
+
 <a name="soft_reset-0"></a>
 
-###soft_reset/0##
+### soft_reset/0 ###
 
 
-<pre>soft_reset() -&gt; ok</pre>
+<pre><code>
+soft_reset() -&gt; ok
+</code></pre>
+
 <br></br>
 
 
+

+ 62 - 47
doc/gproc_lib.md

@@ -1,167 +1,182 @@
 
 
-#Module gproc_lib#
+# Module gproc_lib #
 * [Description](#description)
 * [Function Index](#index)
 * [Function Details](#functions)
 
 
 Extended process registry.
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
 
-__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).<a name="description"></a>
-
-##Description##
-
+## Description ##
 
 This module implements an extended process registry
 
 
-For a detailed description, see gproc/doc/erlang07-wiger.pdf.<a name="index"></a>
+For a detailed description, see gproc/doc/erlang07-wiger.pdf.
+<a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
-<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#add_monitor-3">add_monitor/3</a></td><td></td></tr><tr><td valign="top"><a href="#await-3">await/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_counter_value-3">do_set_counter_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_value-3">do_set_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#ensure_monitor-2">ensure_monitor/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_many-4">insert_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#insert_reg-4">insert_reg/4</a></td><td></td></tr><tr><td valign="top"><a href="#notify-2">notify/2</a></td><td></td></tr><tr><td valign="top"><a href="#notify-3">notify/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_many-4">remove_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_monitor-3">remove_monitor/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_monitors-3">remove_monitors/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reg-3">remove_reg/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reg-4">remove_reg/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reverse_mapping-3">remove_reverse_mapping/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_wait-4">remove_wait/4</a></td><td></td></tr><tr><td valign="top"><a href="#update_aggr_counter-3">update_aggr_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#update_counter-3">update_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#valid_opts-2">valid_opts/2</a></td><td></td></tr></table>
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#add_monitor-3">add_monitor/3</a></td><td></td></tr><tr><td valign="top"><a href="#await-3">await/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_counter_value-3">do_set_counter_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#do_set_value-3">do_set_value/3</a></td><td></td></tr><tr><td valign="top"><a href="#ensure_monitor-2">ensure_monitor/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_attr-4">insert_attr/4</a></td><td></td></tr><tr><td valign="top"><a href="#insert_many-4">insert_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#insert_reg-4">insert_reg/4</a></td><td></td></tr><tr><td valign="top"><a href="#notify-2">notify/2</a></td><td></td></tr><tr><td valign="top"><a href="#notify-3">notify/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_many-4">remove_many/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_monitor-3">remove_monitor/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_monitors-3">remove_monitors/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reg-3">remove_reg/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reg-4">remove_reg/4</a></td><td></td></tr><tr><td valign="top"><a href="#remove_reverse_mapping-3">remove_reverse_mapping/3</a></td><td></td></tr><tr><td valign="top"><a href="#remove_wait-4">remove_wait/4</a></td><td></td></tr><tr><td valign="top"><a href="#update_aggr_counter-3">update_aggr_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#update_counter-3">update_counter/3</a></td><td></td></tr><tr><td valign="top"><a href="#valid_opts-2">valid_opts/2</a></td><td></td></tr></table>
 
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="add_monitor-3"></a>
 
-###add_monitor/3##
-
+### add_monitor/3 ###
 
 `add_monitor(T, Pid, Ref) -> any()`
 
-<a name="await-3"></a>
 
-###await/3##
+<a name="await-3"></a>
 
+### await/3 ###
 
 `await(Key, WPid, From) -> any()`
 
-<a name="do_set_counter_value-3"></a>
 
-###do_set_counter_value/3##
+<a name="do_set_counter_value-3"></a>
 
+### do_set_counter_value/3 ###
 
 `do_set_counter_value(Key, Value, Pid) -> any()`
 
-<a name="do_set_value-3"></a>
 
-###do_set_value/3##
+<a name="do_set_value-3"></a>
 
+### do_set_value/3 ###
 
 `do_set_value(Key, Value, Pid) -> any()`
 
-<a name="ensure_monitor-2"></a>
 
-###ensure_monitor/2##
+<a name="ensure_monitor-2"></a>
 
+### ensure_monitor/2 ###
 
 `ensure_monitor(Pid, Scope) -> any()`
 
+
+<a name="insert_attr-4"></a>
+
+### insert_attr/4 ###
+
+`insert_attr(Key, Attrs, Pid, Scope) -> any()`
+
+
 <a name="insert_many-4"></a>
 
-###insert_many/4##
+### insert_many/4 ###
+
 
+<pre><code>
+insert_many(T::<a href="#type-type">type()</a>, Scope::<a href="#type-scope">scope()</a>, KVL::[{<a href="#type-key">key()</a>, any()}], Pid::pid()) -&gt; {true, list()} | false
+</code></pre>
 
-<pre>insert_many(T::<a href="#type-type">type()</a>, Scope::<a href="#type-scope">scope()</a>, KVL::[{<a href="#type-key">key()</a>, any()}], Pid::pid()) -> {true, list()} | false</pre>
 <br></br>
 
 
+
 <a name="insert_reg-4"></a>
 
-###insert_reg/4##
+### insert_reg/4 ###
+
 
+<pre><code>
+insert_reg(K::<a href="#type-key">key()</a>, Value::any(), Pid::pid() | shared, Scope::<a href="#type-scope">scope()</a>) -&gt; boolean()
+</code></pre>
 
-<pre>insert_reg(K::<a href="#type-key">key()</a>, Value::any(), Pid::pid() | shared, Scope::<a href="#type-scope">scope()</a>) -> boolean()</pre>
 <br></br>
 
 
-<a name="notify-2"></a>
 
-###notify/2##
+<a name="notify-2"></a>
 
+### notify/2 ###
 
 `notify(Key, Opts) -> any()`
 
-<a name="notify-3"></a>
 
-###notify/3##
+<a name="notify-3"></a>
 
+### notify/3 ###
 
 `notify(Event, Key, Opts) -> any()`
 
-<a name="remove_many-4"></a>
 
-###remove_many/4##
+<a name="remove_many-4"></a>
 
+### remove_many/4 ###
 
 `remove_many(T, Scope, L, Pid) -> any()`
 
-<a name="remove_monitor-3"></a>
 
-###remove_monitor/3##
+<a name="remove_monitor-3"></a>
 
+### remove_monitor/3 ###
 
 `remove_monitor(T, Pid, Ref) -> any()`
 
-<a name="remove_monitors-3"></a>
 
-###remove_monitors/3##
+<a name="remove_monitors-3"></a>
 
+### remove_monitors/3 ###
 
 `remove_monitors(Key, Pid, MPid) -> any()`
 
-<a name="remove_reg-3"></a>
 
-###remove_reg/3##
+<a name="remove_reg-3"></a>
 
+### remove_reg/3 ###
 
 `remove_reg(Key, Pid, Event) -> any()`
 
-<a name="remove_reg-4"></a>
 
-###remove_reg/4##
+<a name="remove_reg-4"></a>
 
+### remove_reg/4 ###
 
 `remove_reg(Key, Pid, Event, Opts) -> any()`
 
-<a name="remove_reverse_mapping-3"></a>
 
-###remove_reverse_mapping/3##
+<a name="remove_reverse_mapping-3"></a>
 
+### remove_reverse_mapping/3 ###
 
 `remove_reverse_mapping(Event, Pid, Key) -> any()`
 
-<a name="remove_wait-4"></a>
 
-###remove_wait/4##
+<a name="remove_wait-4"></a>
 
+### remove_wait/4 ###
 
 `remove_wait(Key, Pid, Ref, Waiters) -> any()`
 
-<a name="update_aggr_counter-3"></a>
 
-###update_aggr_counter/3##
+<a name="update_aggr_counter-3"></a>
 
+### update_aggr_counter/3 ###
 
 `update_aggr_counter(C, N, Val) -> any()`
 
-<a name="update_counter-3"></a>
 
-###update_counter/3##
+<a name="update_counter-3"></a>
 
+### update_counter/3 ###
 
 `update_counter(Key, Incr, Pid) -> any()`
 
-<a name="valid_opts-2"></a>
 
-###valid_opts/2##
+<a name="valid_opts-2"></a>
 
+### valid_opts/2 ###
 
 `valid_opts(Type, Default) -> any()`
 
+

+ 26 - 15
doc/gproc_monitor.md

@@ -1,6 +1,6 @@
 
 
-#Module gproc_monitor#
+# Module gproc_monitor #
 * [Description](#description)
 * [Function Index](#index)
 * [Function Details](#functions)
@@ -10,12 +10,12 @@
 This module implements a notification system for gproc names
 When a process subscribes to notifications for a given name, a message
 will be sent each time that name is registered.
-
 __Behaviours:__ [`gen_server`](gen_server.md).
 
-__Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)).<a name="index"></a>
+__Authors:__ Ulf Wiger ([`ulf.wiger@feuerlabs.com`](mailto:ulf.wiger@feuerlabs.com)).
+<a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#start_link-0">start_link/0</a></td><td>
@@ -26,27 +26,33 @@ Unsubscribe from registration events for a certain name.</td></tr></table>
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="start_link-0"></a>
 
-###start_link/0##
+### start_link/0 ###
+
 
+<pre><code>
+start_link() -&gt; {ok, Pid} | ignore | {error, Error}
+</code></pre>
 
-<pre>start_link() -&gt; {ok, Pid} | ignore | {error, Error}</pre>
 <br></br>
 
 
 
 Starts the server
+
 <a name="subscribe-1"></a>
 
-###subscribe/1##
+### subscribe/1 ###
 
 
-<pre>subscribe(Key::<a href="#type-key">key()</a>) -> ok</pre>
-<br></br>
+<pre><code>
+subscribe(Key::<a href="#type-key">key()</a>) -&gt; ok
+</code></pre>
 
+<br></br>
 
 
 
@@ -60,19 +66,24 @@ whenever a process registers under the given name, and a
 `{gproc_monitor, Name, undefined}` message when the name is unregistered,
 either explicitly, or because the registered process dies.
 
+
 When the subscription is first ordered, one of the above messages will be
-sent immediately, indicating the current status of the name.<a name="unsubscribe-1"></a>
+sent immediately, indicating the current status of the name.
+<a name="unsubscribe-1"></a>
 
-###unsubscribe/1##
+### unsubscribe/1 ###
 
 
-<pre>unsubscribe(Key::<a href="#type-key">key()</a>) -> ok</pre>
-<br></br>
+<pre><code>
+unsubscribe(Key::<a href="#type-key">key()</a>) -&gt; ok
+</code></pre>
 
+<br></br>
 
 
 
 
 Unsubscribe from registration events for a certain name
 
-This function is the reverse of subscribe/1. It removes the subscription.
+
+This function is the reverse of subscribe/1. It removes the subscription.

+ 481 - 0
doc/gproc_pool.md

@@ -0,0 +1,481 @@
+
+
+# Module gproc_pool #
+* [Description](#description)
+* [Function Index](#index)
+* [Function Details](#functions)
+
+
+Load balancing functions based on Gproc.
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
+
+## Description ##
+
+
+
+This module implements support for load-balancing server pools. It was
+originally intended mainly as an example of how to use various Gproc
+resources (e.g. counters and shared properties), but is fully integrated
+into Gproc, and fully functional.
+
+
+
+
+## Concepts ##
+
+
+
+Each pool has a list of 'named' workers (defined using `add_worker/2`) and
+a load-balancing strategy. Processes can then 'connect' to the pool (with
+`connect_worker/2`), using one of the defined names.
+
+
+
+Users then 'pick' one of the currently connected processes in the pool. Which
+process is picked depends on the load-balancing strategy.
+
+
+
+The whole representation of the pool and its connected workers is in gproc.
+The server `gproc_pool` is used to serialize pool management updates, but
+worker selection is performed entirely in the calling process, and can be
+performed by several processes concurrently.
+
+
+
+
+### Load-balancing strategies ###
+
+
+* `round_robin` is the default. A wrapping gproc counter keeps track of the
+latest worker picked, and `gproc:next()` is used to find the next worker.
+* `random` picks a random worker from the pool.
+* `hash` requires a value (`pick/2`), and picks a worker based on the hash of
+that value.
+* `direct` takes an integer as an argument, and picks the next worker (modulo
+the size of the pool). This is mainly for implementations that implement
+a load-balancing strategy on top of `gproc_pool`.
+* `claim` picks the first available worker and 'claims' it while executing
+a user-provided fun. This means that the number of concurrently executing
+jobs will not exceed the size of the pool.<a name="index"></a>
+
+## Function Index ##
+
+
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#active_workers-1">active_workers/1</a></td><td>Return a list of currently connected workers in the pool.</td></tr><tr><td valign="top"><a href="#add_worker-2">add_worker/2</a></td><td>Assign a worker name to the pool, returning the worker's position.</td></tr><tr><td valign="top"><a href="#add_worker-3">add_worker/3</a></td><td>Assign a worker name to a given slot in the pool, returning the slot.</td></tr><tr><td valign="top"><a href="#claim-2">claim/2</a></td><td>Picks the first available worker in the pool and applies <code>Fun</code>.</td></tr><tr><td valign="top"><a href="#connect_worker-2">connect_worker/2</a></td><td>Connect the current process to <code>Name</code> in <code>Pool</code>.</td></tr><tr><td valign="top"><a href="#defined_workers-1">defined_workers/1</a></td><td>Return a list of added workers in the pool.</td></tr><tr><td valign="top"><a href="#delete-1">delete/1</a></td><td>Delete an existing pool.</td></tr><tr><td valign="top"><a href="#disconnect_worker-2">disconnect_worker/2</a></td><td>Disconnect the current process from <code>Name</code> in <code>Pool</code>.</td></tr><tr><td valign="top"><a href="#force_delete-1">force_delete/1</a></td><td>Forcibly remove a pool, terminating all active workers.</td></tr><tr><td valign="top"><a href="#log-1">log/1</a></td><td>Update a counter associated with a worker name.</td></tr><tr><td valign="top"><a href="#new-1">new/1</a></td><td>Equivalent to <a href="#new-3"><tt>new(Pool, round_robin, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#new-3">new/3</a></td><td>Create a new pool.</td></tr><tr><td valign="top"><a href="#pick-1">pick/1</a></td><td>Pick a worker from the pool given the pool's load-balancing algorithm.</td></tr><tr><td valign="top"><a href="#pick-2">pick/2</a></td><td>Pick a worker from the pool based on <code>Value</code>.</td></tr><tr><td valign="top"><a href="#randomize-1">randomize/1</a></td><td>Randomizes the "next" pointer for the pool.</td></tr><tr><td valign="top"><a href="#remove_worker-2">remove_worker/2</a></td><td>Remove a previously added worker.</td></tr><tr><td valign="top"><a href="#whereis_worker-2">whereis_worker/2</a></td><td>Look up the pid of a connected worker.</td></tr><tr><td valign="top"><a href="#worker_id-2">worker_id/2</a></td><td>Return the unique gproc name corresponding to a name in the pool.</td></tr><tr><td valign="top"><a href="#worker_pool-1">worker_pool/1</a></td><td>Return a list of slots and/or named workers in the pool.</td></tr></table>
+
+
+<a name="functions"></a>
+
+## Function Details ##
+
+<a name="active_workers-1"></a>
+
+### active_workers/1 ###
+
+
+<pre><code>
+active_workers(Pool::any()) -&gt; [{Name, Pid}]
+</code></pre>
+
+<br></br>
+
+
+Return a list of currently connected workers in the pool.
+
+<a name="add_worker-2"></a>
+
+### add_worker/2 ###
+
+
+<pre><code>
+add_worker(Pool::any(), Name::any()) -&gt; integer()
+</code></pre>
+
+<br></br>
+
+
+
+Assign a worker name to the pool, returning the worker's position.
+
+
+
+Before a worker can connect to the pool, its name must be added. If no explicit
+position is given (see [`add_worker/3`](#add_worker-3)), the most suitable position,
+depending on load-balancing algorithm, is selected: for round_robin and direct
+pools, names are packed tightly from the beginning; for hash and random pools,
+slots are filled as sparsely as possible, in order to maintain an even
+likelihood of hitting each worker.
+
+
+
+An exception is raised if the pool is full (and `auto_size` is false), or if
+`Name` already exists in the pool.
+
+
+Before a worker can be used, a process must connect to it (see
+[`connect_worker/2`](#connect_worker-2).
+<a name="add_worker-3"></a>
+
+### add_worker/3 ###
+
+
+<pre><code>
+add_worker(Pool::any(), Name::any(), Slot::integer()) -&gt; integer()
+</code></pre>
+
+<br></br>
+
+
+
+Assign a worker name to a given slot in the pool, returning the slot.
+
+
+This function allows the pool maintainer to exactly position each worker
+inside the pool. An exception is raised if the position is already taken,
+or if `Name` already exists in the pool. If `Slot` is larger than the current
+size of the pool, an exception is raised iff `auto_size` is `false`;
+otherwise the pool is expanded to accomodate the new position.
+<a name="claim-2"></a>
+
+### claim/2 ###
+
+
+<pre><code>
+claim(Pool::any(), Fun::function()) -&gt; {true, Res} | false
+</code></pre>
+
+<br></br>
+
+
+
+Picks the first available worker in the pool and applies `Fun`.
+
+
+A `claim` pool allows the caller to "claim" a worker during a short span
+(essentially, a lock is set and released as soon as `Fun` returns).
+Once a worker is selected, `Fun(Name, Pid)` is called, where `Name` is a
+unique gproc name of the worker, and `Pid` is its process identifier.
+The gproc name of the worker serves as a mutex, where its value is 0 (zero)
+if the worker is free, and 1 (one) if it is busy. The mutex operation is
+implemented using `gproc:update_counter/2`.
+<a name="connect_worker-2"></a>
+
+### connect_worker/2 ###
+
+
+<pre><code>
+connect_worker(Pool::any(), Name::any()) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+
+Connect the current process to `Name` in `Pool`.
+
+
+
+Typically, a server will call this function as it starts, similarly to when
+it registers itself. In fact, calling `connect_worker/2` leads to the process
+being registered as `{n,l,[gproc_pool,N,Name]}`, where `N` is the position of
+`Name` in the pool. This means (a) that gproc monitors the worker, and
+removes the connection automatically if it dies, and (b) that the registered
+names can be listed in order of their positions in the pool.
+
+
+This function raises an exception if `Name` does not exist in `Pool` (or
+there is no such pool), or if another worker is already connected to
+`Name`.
+<a name="defined_workers-1"></a>
+
+### defined_workers/1 ###
+
+
+<pre><code>
+defined_workers(Pool::any()) -&gt; [{Name, Pos, Count}]
+</code></pre>
+
+<br></br>
+
+
+
+Return a list of added workers in the pool.
+
+
+
+The added workers are slots in the pool that have been given names, and thus
+can be connected to. This function doesn't detect whether or not there are
+any connected (active) workers.
+
+
+The list contains `{Name, Pos, Count}`, where `Name` is the name of the added
+worker, `Pos` is its position in the pool, and `Count` represents the number
+of times the worker has been picked (assuming callers keep count by explicitly
+calling [`log/1`](#log-1)).
+<a name="delete-1"></a>
+
+### delete/1 ###
+
+
+<pre><code>
+delete(Pool::any()) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+
+Delete an existing pool.
+
+
+This function will delete a pool, only if there are no connected workers.
+Ensure that workers have been disconnected before deleting the pool.
+<a name="disconnect_worker-2"></a>
+
+### disconnect_worker/2 ###
+
+
+<pre><code>
+disconnect_worker(Pool, Name) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+
+Disconnect the current process from `Name` in `Pool`.
+
+
+
+This function is similar to a `gproc:unreg()` call. It removes the
+connection between `Pool`, `Name` and pid, and makes it possible for another
+process to connect to `Name`.
+
+
+An exception is raised if there is no prior connection between `Pool`,
+`Name` and the current process.
+<a name="force_delete-1"></a>
+
+### force_delete/1 ###
+
+
+<pre><code>
+force_delete(Pool::any()) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+
+Forcibly remove a pool, terminating all active workers
+
+
+This function is primarily intended for cleanup of any pools that might have
+become inconsistent (for whatever reason). It will clear out all resources
+belonging to the pool and send `exit(Pid, kill)` signals to all connected
+workers (except the calling process).
+<a name="log-1"></a>
+
+### log/1 ###
+
+
+<pre><code>
+log(X1::GprocKey) -&gt; integer()
+</code></pre>
+
+<br></br>
+
+
+
+Update a counter associated with a worker name.
+
+
+Each added worker has a gproc counter that can be used e.g. to keep track of
+the number of times the worker has been picked. Since it's associated with the
+named 'slot', and not to the connected worker, its value will persist even
+if the currently connected worker dies.
+<a name="new-1"></a>
+
+### new/1 ###
+
+
+<pre><code>
+new(Pool::any()) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+Equivalent to [`new(Pool, round_robin, [])`](#new-3).
+<a name="new-3"></a>
+
+### new/3 ###
+
+
+<pre><code>
+new(Pool::any(), Type, Opts) -&gt; true
+</code></pre>
+
+<ul class="definitions"><li><code>Type = round_robin | random | hash | direct | claim</code></li><li><code>Opts = [{size, integer()} | {auto_size, boolean()}]</code></li></ul>
+
+
+Create a new pool.
+
+
+
+The pool starts out empty. If a size is not given, the pool size is set to
+0 initially. `auto_size` is `true` by default if size is not specified, but
+`false` by default otherwise. If `auto_size == true`, the pool will be
+enlarged to accomodate new workers, when necessary. Otherwise, trying to add
+a worker when the pool is full will raise an exception, as will trying to add
+a worker on a specific position beyond the current size of the pool.
+
+
+If the given pool already exists, this function will raise an exception.
+<a name="pick-1"></a>
+
+### pick/1 ###
+
+
+<pre><code>
+pick(Pool::any()) -&gt; GprocName | false
+</code></pre>
+
+<br></br>
+
+
+
+Pick a worker from the pool given the pool's load-balancing algorithm.
+
+
+The pool types that allows picking without an extra argument are
+round_robin and random. This function returns `false` if there is no available
+worker, or if `Pool` is not a valid pool.
+<a name="pick-2"></a>
+
+### pick/2 ###
+
+
+<pre><code>
+pick(Pool::any(), Value::any()) -&gt; GprocName | false
+</code></pre>
+
+<br></br>
+
+
+
+Pick a worker from the pool based on `Value`.
+
+
+
+The pool types that allows picking based on an extra argument are
+hash and direct. This function returns `false` if there is no available
+worker, or if `Pool` is not a valid pool.
+
+
+If the pool is of type `direct`, `Value` must be an integer corresponding to
+a position in the pool (modulo the size of the pool). If the type is
+`hash`, `Value` may be any term, and its hash value will serve as a guide for
+selecting a worker.
+<a name="randomize-1"></a>
+
+### randomize/1 ###
+
+
+<pre><code>
+randomize(Pool::any()) -&gt; integer()
+</code></pre>
+
+<br></br>
+
+
+
+Randomizes the "next" pointer for the pool.
+
+
+This function only has an effect for `round_robin` pools, which have a
+reference to the next worker to be picked. Without randomizing, the load
+balancing will always start with the first worker in the pool.
+<a name="remove_worker-2"></a>
+
+### remove_worker/2 ###
+
+
+<pre><code>
+remove_worker(Pool::any(), Name::any()) -&gt; true
+</code></pre>
+
+<br></br>
+
+
+
+Remove a previously added worker.
+
+
+This function will assume that any connected worker is disconnected first.
+It will fail if there is no such pool, but will return `true` in the case
+when `Name` did not exist in the pool in the first place.
+<a name="whereis_worker-2"></a>
+
+### whereis_worker/2 ###
+
+
+<pre><code>
+whereis_worker(Pool::any(), Name::any()) -&gt; pid() | undefined
+</code></pre>
+
+<br></br>
+
+
+
+Look up the pid of a connected worker.
+
+
+This function works similarly to `gproc:where/1`: it will return the pid
+of the worker connected as `Pool / Name`, if there is such a worker; otherwise
+it will return `undefined`. It will raise an exception if `Name` has not been
+added to the pool.
+<a name="worker_id-2"></a>
+
+### worker_id/2 ###
+
+
+<pre><code>
+worker_id(Pool, Name) -&gt; GprocName
+</code></pre>
+
+<br></br>
+
+
+
+Return the unique gproc name corresponding to a name in the pool.
+
+
+This function assumes that `Name` has been added to `Pool`. It returns the
+unique name that a connected worker will be registered as. This doesn't mean
+that there is, in fact, such a connected worker.
+<a name="worker_pool-1"></a>
+
+### worker_pool/1 ###
+
+
+<pre><code>
+worker_pool(Pool::any()) -&gt; [integer() | {Name, Pos}]
+</code></pre>
+
+<br></br>
+
+
+
+Return a list of slots and/or named workers in the pool.
+
+
+
+This function is mainly for testing, but can also be useful when implementing
+your own worker placement algorithm on top of gproc_pool.
+
+
+A plain integer represents an unfilled slot, and `{Name, Pos}` represents an
+added worker. The pool is always filled to the current size.

+ 142 - 71
doc/gproc_ps.md

@@ -1,6 +1,6 @@
 
 
-#Module gproc_ps#
+# Module gproc_ps #
 * [Description](#description)
 * [Data Types](#types)
 * [Function Index](#index)
@@ -9,11 +9,10 @@
 
 Gproc Publish/Subscribe patterns
 This module implements a few convenient functions for publish/subscribe.
+__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+<a name="description"></a>
 
-__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).<a name="description"></a>
-
-##Description##
-
+## Description ##
 
 
 
@@ -21,6 +20,7 @@ Publish/subscribe with Gproc relies entirely on gproc properties and counters.
 This makes for a very concise implementation, as the monitoring of subscribers and
 removal of subscriptions comes for free with Gproc.
 
+
 Using this module instead of rolling your own (which is easy enough) brings the
 benefit of consistency, in tracing and debugging.
 The implementation can also serve to illustrate how to use gproc properties and
@@ -28,43 +28,59 @@ counters to good effect.
 
 <a name="types"></a>
 
-##Data Types##
+## Data Types ##
+
+
+
+
+### <a name="type-event">event()</a> ###
 
 
 
+<pre><code>
+event() = any()
+</code></pre>
 
-###<a name="type-event">event()</a>##
 
 
 
-<pre>event() = any()</pre>
 
+### <a name="type-msg">msg()</a> ###
 
 
-###<a name="type-msg">msg()</a>##
 
+<pre><code>
+msg() = any()
+</code></pre>
 
 
-<pre>msg() = any()</pre>
 
 
 
-###<a name="type-scope">scope()</a>##
+### <a name="type-scope">scope()</a> ###
 
 
 
-<pre>scope() = l | g</pre>
+<pre><code>
+scope() = l | g
+</code></pre>
 
 
 
-###<a name="type-status">status()</a>##
 
 
+### <a name="type-status">status()</a> ###
+
+
+
+<pre><code>
+status() = 1 | 0
+</code></pre>
+
 
-<pre>status() = 1 | 0</pre>
 <a name="index"></a>
 
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#change_cond-3">change_cond/3</a></td><td>Change the condition specification of an existing subscription.</td></tr><tr><td valign="top"><a href="#create_single-2">create_single/2</a></td><td>Creates a single-shot subscription entry for Event.</td></tr><tr><td valign="top"><a href="#delete_single-2">delete_single/2</a></td><td>Deletes the single-shot subscription for Event.</td></tr><tr><td valign="top"><a href="#disable_single-2">disable_single/2</a></td><td>Disables the single-shot subscription for Event.</td></tr><tr><td valign="top"><a href="#enable_single-2">enable_single/2</a></td><td>Enables the single-shot subscription for Event.</td></tr><tr><td valign="top"><a href="#list_singles-2">list_singles/2</a></td><td>Lists all single-shot subscribers of Event, together with their status.</td></tr><tr><td valign="top"><a href="#list_subs-2">list_subs/2</a></td><td>List the pids of all processes subscribing to <code>Event</code></td></tr><tr><td valign="top"><a href="#notify_single_if_true-4">notify_single_if_true/4</a></td><td>Create/enable a single subscription for event; notify at once if F() -> true.</td></tr><tr><td valign="top"><a href="#publish-3">publish/3</a></td><td>Publish the message <code>Msg</code> to all subscribers of <code>Event</code></td></tr><tr><td valign="top"><a href="#publish_cond-3">publish_cond/3</a></td><td>Publishes the message <code>Msg</code> to conditional subscribers of <code>Event</code></td></tr><tr><td valign="top"><a href="#subscribe-2">subscribe/2</a></td><td>Subscribe to events of type <code>Event</code></td></tr><tr><td valign="top"><a href="#subscribe_cond-3">subscribe_cond/3</a></td><td>Subscribe conditionally to events of type <code>Event</code></td></tr><tr><td valign="top"><a href="#tell_singles-3">tell_singles/3</a></td><td>Publish <code>Msg</code> to all single-shot subscribers of <code>Event</code></td></tr><tr><td valign="top"><a href="#unsubscribe-2">unsubscribe/2</a></td><td>Remove subscribtion created using <code>subscribe(Scope, Event)</code></td></tr></table>
@@ -72,16 +88,18 @@ counters to good effect.
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="change_cond-3"></a>
 
-###change_cond/3##
+### change_cond/3 ###
 
 
-<pre>change_cond(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Spec::undefined | <a href="ets.md#type-match_spec">ets:match_spec()</a>) -> true</pre>
-<br></br>
+<pre><code>
+change_cond(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Spec::undefined | <a href="ets.md#type-match_spec">ets:match_spec()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
@@ -93,16 +111,20 @@ This function atomically changes the condition spec of an existing
 subscription (see [`subscribe_cond/3`](#subscribe_cond-3)). An exception is raised if
 the subscription doesn't already exist.
 
+
 Note that this function can also be used to change a conditional subscription
 to an unconditional one (by setting `Spec = undefined`), or a 'normal'
-subscription to a conditional one.<a name="create_single-2"></a>
+subscription to a conditional one.
+<a name="create_single-2"></a>
 
-###create_single/2##
+### create_single/2 ###
 
 
-<pre>create_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> true</pre>
-<br></br>
+<pre><code>
+create_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
@@ -121,30 +143,38 @@ The function creates a gproc counter entry, `{c,Scope,{gproc_ps_event,Event}}`,
 will have either of the values `0` (disabled) or `1` (enabled). Initially, the value
 is `1`, meaning the subscription is enabled.
 
+
 Counters are used in this case, since they can be atomically updated by both the
 subscriber (owner) and publisher. The publisher sets the counter value to `0` as soon
-as it has delivered a message.<a name="delete_single-2"></a>
+as it has delivered a message.
+<a name="delete_single-2"></a>
 
-###delete_single/2##
+### delete_single/2 ###
 
 
-<pre>delete_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> true</pre>
-<br></br>
+<pre><code>
+delete_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
 Deletes the single-shot subscription for Event
 
+
 This function deletes the counter entry representing the single-shot description.
-An exception will be raised if there is no such subscription.<a name="disable_single-2"></a>
+An exception will be raised if there is no such subscription.
+<a name="disable_single-2"></a>
 
-###disable_single/2##
+### disable_single/2 ###
 
 
-<pre>disable_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> integer()</pre>
-<br></br>
+<pre><code>
+disable_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; integer()
+</code></pre>
 
+<br></br>
 
 
 
@@ -161,14 +191,18 @@ This function is insensitive to concurrency, using 'wrapping' ets counter update
 This guarantees that the counter will have either the value 1 or 0, depending on which
 update happened last.
 
-The return value indicates the previous status.<a name="enable_single-2"></a>
 
-###enable_single/2##
+The return value indicates the previous status.
+<a name="enable_single-2"></a>
 
+### enable_single/2 ###
 
-<pre>enable_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> integer()</pre>
-<br></br>
 
+<pre><code>
+enable_single(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; integer()
+</code></pre>
+
+<br></br>
 
 
 
@@ -189,51 +223,67 @@ This function is insensitive to concurrency, using 'wrapping' ets counter update
 This guarantees that the counter will have either the value 1 or 0, depending on which
 update happened last.
 
-The return value indicates the previous status.<a name="list_singles-2"></a>
 
-###list_singles/2##
+The return value indicates the previous status.
+<a name="list_singles-2"></a>
+
+### list_singles/2 ###
 
 
-<pre>list_singles(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> [{pid(), <a href="#type-status">status()</a>}]</pre>
+<pre><code>
+list_singles(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; [{pid(), <a href="#type-status">status()</a>}]
+</code></pre>
+
 <br></br>
 
 
-Lists all single-shot subscribers of Event, together with their status<a name="list_subs-2"></a>
+Lists all single-shot subscribers of Event, together with their status
+<a name="list_subs-2"></a>
 
-###list_subs/2##
+### list_subs/2 ###
 
 
-<pre>list_subs(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> [pid()]</pre>
-<br></br>
+<pre><code>
+list_subs(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; [pid()]
+</code></pre>
 
+<br></br>
 
 
 
 List the pids of all processes subscribing to `Event`
 
-This function uses `gproc:select/2` to find all properties indicating a subscription.<a name="notify_single_if_true-4"></a>
 
-###notify_single_if_true/4##
+This function uses `gproc:select/2` to find all properties indicating a subscription.
+<a name="notify_single_if_true-4"></a>
 
+### notify_single_if_true/4 ###
 
-<pre>notify_single_if_true(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, F::fun(() -> boolean()), Msg::<a href="#type-msg">msg()</a>) -> ok</pre>
-<br></br>
 
+<pre><code>
+notify_single_if_true(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, F::fun(() -&gt; boolean()), Msg::<a href="#type-msg">msg()</a>) -&gt; ok
+</code></pre>
+
+<br></br>
 
 
 
 Create/enable a single subscription for event; notify at once if F() -> true
 
+
 This function is a convenience function, wrapping a single-shot pub/sub around a
 user-provided boolean test. `Msg` should be what the publisher will send later, if the
-immediate test returns `false`.<a name="publish-3"></a>
+immediate test returns `false`.
+<a name="publish-3"></a>
 
-###publish/3##
+### publish/3 ###
 
 
-<pre>publish(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Msg::<a href="#type-msg">msg()</a>) -> ok</pre>
-<br></br>
+<pre><code>
+publish(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Msg::<a href="#type-msg">msg()</a>) -&gt; ok
+</code></pre>
 
+<br></br>
 
 
 
@@ -247,32 +297,40 @@ The message delivered to each subscriber will be of the form:
 
 `{gproc_ps_event, Event, Msg}`
 
+
 The function uses `gproc:send/2` to send a message to all processes which have a
-property `{p,Scope,{gproc_ps_event,Event}}`.<a name="publish_cond-3"></a>
+property `{p,Scope,{gproc_ps_event,Event}}`.
+<a name="publish_cond-3"></a>
 
-###publish_cond/3##
+### publish_cond/3 ###
 
 
-<pre>publish_cond(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Msg::<a href="#type-msg">msg()</a>) -> <a href="#type-msg">msg()</a></pre>
-<br></br>
+<pre><code>
+publish_cond(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Msg::<a href="#type-msg">msg()</a>) -&gt; <a href="#type-msg">msg()</a>
+</code></pre>
 
+<br></br>
 
 
 
 Publishes the message `Msg` to conditional subscribers of `Event`
 
+
 The message will be delivered to each subscriber provided their respective
 condition tests succeed.
 
 
-__See also:__ [subscribe_cond/3](#subscribe_cond-3).<a name="subscribe-2"></a>
+__See also:__ [subscribe_cond/3](#subscribe_cond-3).
+<a name="subscribe-2"></a>
 
-###subscribe/2##
+### subscribe/2 ###
 
 
-<pre>subscribe(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> true</pre>
-<br></br>
+<pre><code>
+subscribe(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
@@ -288,16 +346,20 @@ delivered to the current process, along with all other subscribers.
 This function creates a property, `{p,Scope,{gproc_ps_event,Event}}`, which
 can be searched and displayed for debugging purposes.
 
+
 Note that, as with [`gproc:reg/1`](gproc.md#reg-1), this function will raise an
 exception if you try to subscribe to the same event twice from the same
-process.<a name="subscribe_cond-3"></a>
+process.
+<a name="subscribe_cond-3"></a>
 
-###subscribe_cond/3##
+### subscribe_cond/3 ###
 
 
-<pre>subscribe_cond(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Spec::undefined | <a href="ets.md#type-match_spec">ets:match_spec()</a>) -> true</pre>
-<br></br>
+<pre><code>
+subscribe_cond(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Spec::undefined | <a href="ets.md#type-match_spec">ets:match_spec()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
@@ -333,16 +395,20 @@ expects it to be either undefined or a valid match spec).
 This means that `Cond=undefined` and `Cond=[{'_',[],[true]}]` are
 equivalent.
 
+
 Note that, as with [`gproc:reg/1`](gproc.md#reg-1), this function will raise an
 exception if you try to subscribe to the same event twice from the same
-process.<a name="tell_singles-3"></a>
+process.
+<a name="tell_singles-3"></a>
 
-###tell_singles/3##
+### tell_singles/3 ###
 
 
-<pre>tell_singles(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Msg::<a href="#type-msg">msg()</a>) -> [pid()]</pre>
-<br></br>
+<pre><code>
+tell_singles(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>, Msg::<a href="#type-msg">msg()</a>) -&gt; [pid()]
+</code></pre>
 
+<br></br>
 
 
 
@@ -357,18 +423,23 @@ context switch happens just after the select operation (finding the active subsc
 and before the process is able to update the counters. In this case, it is possible
 that more than one can be delivered.
 
+
 The way to prevent this from happening is to ensure that only one process publishes
-for `Event`.<a name="unsubscribe-2"></a>
+for `Event`.
+<a name="unsubscribe-2"></a>
 
-###unsubscribe/2##
+### unsubscribe/2 ###
 
 
-<pre>unsubscribe(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -> true</pre>
-<br></br>
+<pre><code>
+unsubscribe(Scope::<a href="#type-scope">scope()</a>, Event::<a href="#type-event">event()</a>) -&gt; true
+</code></pre>
 
+<br></br>
 
 
 
 Remove subscribtion created using `subscribe(Scope, Event)`
 
-This removes the property created through `subscribe/2`.
+
+This removes the property created through `subscribe/2`.

+ 10 - 10
doc/gproc_sup.md

@@ -1,13 +1,13 @@
 
 
-#Module gproc_sup#
+# Module gproc_sup #
 * [Function Index](#index)
 * [Function Details](#functions)
 
+__Behaviours:__ [`supervisor`](supervisor.md).
+<a name="index"></a>
 
-__Behaviours:__ [`supervisor`](supervisor.md).<a name="index"></a>
-
-##Function Index##
+## Function Index ##
 
 
 <table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#init-1">init/1</a></td><td>The main GPROC supervisor.</td></tr><tr><td valign="top"><a href="#start_link-1">start_link/1</a></td><td></td></tr></table>
@@ -15,19 +15,19 @@ __Behaviours:__ [`supervisor`](supervisor.md).<a name="index"></a>
 
 <a name="functions"></a>
 
-##Function Details##
+## Function Details ##
 
 <a name="init-1"></a>
 
-###init/1##
-
+### init/1 ###
 
 `init(Args) -> any()`
 
-The main GPROC supervisor.<a name="start_link-1"></a>
-
-###start_link/1##
+The main GPROC supervisor.
+<a name="start_link-1"></a>
 
+### start_link/1 ###
 
 `start_link(Args) -> any()`
 
+

+ 1 - 1
doc/overview.edoc

@@ -45,7 +45,7 @@ browse key data in a running system.
 An interesting application of gproc is building publish/subscribe patterns.
 Example:
 
-<pre>
+<pre lang="erlang">
 subscribe(EventType) ->
     %% Gproc notation: {p, l, Name} means {(p)roperty, (l)ocal, Name}
     gproc:reg({p, l, {?MODULE, EventType}}).

+ 231 - 29
src/gproc.erl

@@ -71,13 +71,16 @@
 -behaviour(gen_server).
 
 -export([start_link/0,
-         reg/1, reg/2, unreg/1,
+         reg/1, reg/2, unreg/1, set_attributes/2,
 	 reg_or_locate/1, reg_or_locate/2, reg_or_locate/3,
 	 reg_shared/1, reg_shared/2, unreg_shared/1,
+	 set_attributes_shared/2, set_value_shared/2,
          mreg/3,
          munreg/3,
          set_value/2,
          get_value/1, get_value/2,
+	 get_attribute/2, get_attribute/3,
+	 get_attributes/1, get_attributes/2,
          where/1,
          await/1, await/2, await/3,
 	 wide_await/3,
@@ -90,7 +93,7 @@
          lookup_pids/1,
          lookup_value/1,
          lookup_values/1,
-         update_counter/2,
+         update_counter/2, update_counter/3,
 	 update_counters/2,
 	 reset_counter/1,
 	 update_shared_counter/2,
@@ -938,7 +941,7 @@ reg1(_, _) ->
 %% @doc Try registering a unique name, or return existing registration.
 %%
 %% This function tries to register the name `Key', if available.
-%% If such a registration already exists, the pid and value of
+%% If such a registration object already exists, the pid and value of
 %% the current registration is returned instead.
 %% @end
 reg_or_locate(Key, Value) ->
@@ -977,11 +980,8 @@ reg_or_locate1(_, _, _) ->
 reg_shared(Key) ->
     ?CATCH_GPROC_ERROR(reg_shared1(Key), [Key]).
 
-reg_shared1({c,_,_} = Key) ->
-    reg_shared(Key, 0);
-reg_shared1({a,_,_} = Key) ->
-    reg_shared(Key, undefined).
-
+reg_shared1({T,_,_} = Key) when T==a; T==p; T==c ->
+    reg_shared(Key, default(Key)).
 
 %% @spec reg_shared(Key::key(), Value) -> true
 %%
@@ -991,7 +991,7 @@ reg_shared1({a,_,_} = Key) ->
 %% (using {@link unreg_shared/1}). The types of shared resources currently
 %% supported are `counter' and `aggregated counter'. In listings and query
 %% results, shared resources appear as other similar resources, except that
-%% `Pid == shared'. To wit, update_counter({c,l,myCounter}, 1, shared) would
+%% `Pid == shared'. To wit, update_counter({c,l,myCounter}, shared, 1) would
 %% increment the shared counter `myCounter' with 1, provided it exists.
 %%
 %% A shared aggregated counter will track updates in exactly the same way as
@@ -1009,6 +1009,8 @@ reg_shared1({a,l,_} = Key, undefined) ->
     call({reg_shared, Key, undefined});
 reg_shared1({c,l,_} = Key, Value) when is_integer(Value) ->
     call({reg_shared, Key, Value});
+reg_shared1({p,l,_} = Key, Value) ->
+    call({reg_shared, Key, Value});
 reg_shared1(_, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
@@ -1098,6 +1100,28 @@ unreg1(Key) ->
             end
     end.
 
+%% @spec (Key::key(), Props::[{atom(), any()}]) -> true
+%%
+%% @doc Add/modify `{Key, Value}' attributes associated with a registration.
+%%
+%% Gproc registration objects can have `{Key, Value}' attributes associated with
+%% them. These are stored in a way that doesn't affect the cost of name lookup.
+%%
+%% Attributs can be retrieved using `gproc:get_attribute/3' or
+%% `gproc:get_attributes/2'.
+%% @end
+set_attributes(Key, Props) ->
+    ?CATCH_GPROC_ERROR(set_attributes1(Key, Props), [Key, Props]).
+
+set_attributes1(Key, Props) ->
+    case Key of
+	{_, g, _} ->
+	    ?CHK_DIST,
+	    gproc_dist:set_attributes(Key, Props);
+	{_, l, _} ->
+	    call({set_attributes, Key, Props})
+    end.
+
 %% @spec (Key:: key()) -> true
 %%
 %% @doc Unregister a shared resource.
@@ -1111,11 +1135,34 @@ unreg_shared1(Key) ->
             ?CHK_DIST,
             gproc_dist:unreg_shared(Key);
         {T, l, _} when T == c;
-                       T == a -> call({unreg_shared, Key});
+                       T == a;
+		       T == p -> call({unreg_shared, Key});
         _ ->
 	    ?THROW_GPROC_ERROR(badarg)
     end.
 
+%% @spec (Key::key(), Props::[{K,V}]) -> true
+%% @doc Add/modify `{Key, Value}' attributes associated with a shared registration.
+%%
+%% Gproc registration objects can have `{Key, Value}' attributes associated with
+%% them. These are stored in a way that doesn't affect the cost of name lookup.
+%%
+%% Attributes can be retrieved using `gproc:get_attribute/3' or
+%% `gproc:get_attributes/2'.
+%% @end
+%%
+set_attributes_shared(Key, Attrs) ->
+    ?CATCH_GPROC_ERROR(set_attributes_shared1(Key, Attrs), [Key, Attrs]).
+
+set_attributes_shared1(Key, Attrs) ->
+    case Key of
+	{_, g, _} ->
+	    ?CHK_DIST,
+	    gproc_dist:set_attributes_shared(Key, Attrs);
+	{_, l, _} ->
+	    call({set_attributes_shared, Key, Attrs})
+    end.
+
 %% @spec (key(), pid()) -> yes | no
 %%
 %% @doc Behaviour support callback
@@ -1227,7 +1274,7 @@ local_munreg(T, L) when T==p; T==c ->
     true.
 
 %% @spec (Key :: key(), Value) -> true
-%% @doc Sets the value of the registeration entry given by Key
+%% @doc Sets the value of the registration given by Key
 %%
 %% Key is assumed to exist and belong to the calling process.
 %% If it doesn't, this function will exit.
@@ -1239,6 +1286,21 @@ local_munreg(T, L) when T==p; T==c ->
 set_value(Key, Value) ->
     ?CATCH_GPROC_ERROR(set_value1(Key, Value), [Key, Value]).
 
+%% @spec (Key :: key(), Value) -> true
+%% @doc Sets the value of the shared registration given by Key
+%%
+%% Key is assumed to exist as a shared entity.
+%% If it doesn't, this function will exit.
+%%
+%% Value can be any term, unless the object is a counter, in which case
+%% it must be an integer.
+%% @end
+%%
+set_value_shared({T,_,_} = Key, Value) when T == c;
+					    T == a;
+					    T == p->
+    ?CATCH_GPROC_ERROR(set_value_shared1(Key, Value), [Key, Value]).
+
 set_value1({_,g,_} = Key, Value) ->
     ?CHK_DIST,
     gproc_dist:set_value(Key, Value);
@@ -1261,6 +1323,13 @@ set_value1({c,l,_} = Key, Value) when is_integer(Value) ->
 set_value1(_, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
+set_value_shared1({_,g,_} = Key, Value) ->
+    ?CHK_DIST,
+    gproc_dist:set_value_shared(Key, Value);
+set_value_shared1({_,l,_} = Key, Value) ->
+    call({set_shared, Key, Value}).
+
+
 %% @spec (Key) -> Value
 %% @doc Reads the value stored with a key registered to the current process.
 %%
@@ -1288,9 +1357,10 @@ get_value1({T,_,_} = Key, Pid) when is_pid(Pid) ->
        true ->
             ets:lookup_element(?TAB, {Key, Pid}, 3)
     end;
-get_value1({T,_,_} = K, shared) when T==c; T==a ->
+get_value1({T,_,_} = K, shared) when T==c; T==a; T==p ->
     Key = case T of
 	      c -> {K, shared};
+	      p -> {K, shared};
 	      a -> {K, a}
 	  end,
     case ets:lookup(?TAB, Key) of
@@ -1301,6 +1371,78 @@ get_value1(_, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 
+%% @spec (Key, Attribute::atom()) -> Value
+%% @doc Get attribute value of `Attr' associated with `Key' for most likely Pid.
+%%
+%% The most likely Pid in this case is `self()' for properties and counters,
+%% and the current registration holder in case of names or aggregated counters.
+%% An exception is raised if `Key' is not registered for the given process.
+%% @end
+get_attribute(Key, A) ->
+    Pid = case Key of
+	      {T,_,_} when T==n; T==a ->
+		  where(Key);
+	      {T,_,_} when T==p; T==c ->
+		  self()
+	  end,
+    ?CATCH_GPROC_ERROR(get_attribute1(Key, Pid, A), [Key, A]).
+
+%% @spec (Key, Pid::pid() | shared, Attr::atom()) -> Value
+%% @doc Get the attribute value of `Attr' associated with `Key' for process Pid.
+%%
+%% If `Pid == shared', the attribute of a shared key (see {@link reg_shared/1})
+%% will be read.
+%% @end
+%%
+get_attribute(Key, Pid, A) ->
+    ?CATCH_GPROC_ERROR(get_attribute1(Key, Pid, A), [Key, Pid, A]).
+
+get_attribute1({_,_,_} = Key, Pid, A) when is_pid(Pid); Pid==shared ->
+    case ets:lookup(?TAB, {Pid, Key}) of
+	[{_, Attrs}] ->
+	    case lists:keyfind(attrs, 1, Attrs) of
+		false -> undefined;
+		{_, As} ->
+		    case lists:keyfind(A, 1, As) of
+			false  -> undefined;
+			{_, V} -> V
+		    end
+	    end;
+	_ -> ?THROW_GPROC_ERROR(badarg)
+    end;
+get_attribute1(_, _, _) ->
+    ?THROW_GPROC_ERROR(badarg).
+
+%% @spec get_attributes(Key::key()) -> [{K, V}]
+%% @doc Get attributes associated with registration.
+%% @equiv get_attributes(Key, self())
+%%
+get_attributes(Key) ->
+    ?CATCH_GPROC_ERROR(get_attributes1(Key, self()), [Key]).
+
+%% @spec (Key::key(), Pid::pid() | shared) -> [{K, V}]
+%%
+%% @doc Returns the list of attributes associated with the registration.
+%%
+%% This function raises a `badarg' exception if there is no corresponding
+%% registration.
+%%
+get_attributes(Key, Pid) ->
+    ?CATCH_GPROC_ERROR(get_attributes1(Key, Pid), [Key, Pid]).
+
+get_attributes1({_,_,_} = Key, Pid) when is_pid(Pid); Pid==shared ->
+    case ets:lookup(?TAB, {Pid, Key}) of
+	[{_, Attrs}] ->
+	    case lists:keyfind(attrs, 1, Attrs) of
+		false   -> [];
+		{_, As} -> As
+	    end;
+	_ -> ?THROW_GPROC_ERROR(badarg)
+    end;
+get_attributes1(_, _) ->
+    ?THROW_GPROC_ERROR(badarg).
+
+
 %% @spec (Key) -> Pid
 %% @doc Lookup the Pid stored with a key.
 %%
@@ -1324,7 +1466,7 @@ lookup_value({T,_,_} = Key) ->
 %%
 %% @doc Returns the pid registered as Key
 %%
-%% The type of registration entry must be either name or aggregated counter.
+%% The type of registration must be either name or aggregated counter.
 %% Otherwise this function will exit. Use {@link lookup_pids/1} in these
 %% cases.
 %% @end
@@ -1356,16 +1498,21 @@ whereis_name(Key) ->
 %%
 %% @doc Returns a list of pids with the published key Key
 %%
-%% If the type of registration entry is either name or aggregated counter,
+%% If the type of registration is either name or aggregated counter,
 %% this function will return either an empty list, or a list of one pid.
 %% For non-unique types, the return value can be a list of any length.
+%%
+%% Note: shared resources are not associated with any pid, and will
+%% therefore be excluded.
 %% @end
 %%
 lookup_pids({T,_,_} = Key) ->
     L = if T==n orelse T==a ->
-                ets:select(?TAB, [{{{Key,T}, '$1', '_'},[],['$1']}]);
+                ets:select(?TAB, [{{{Key,T}, '$1', '_'},
+				   [{is_pid, '$1'}], ['$1']}]);
            true ->
-                ets:select(?TAB, [{{{Key,'_'}, '$1', '_'},[],['$1']}])
+                ets:select(?TAB, [{{{Key,'_'}, '$1', '_'},
+				   [{is_pid, '$1'}], ['$1']}])
         end,
     [P || P <- L, my_is_process_alive(P)].
 
@@ -1404,23 +1551,39 @@ lookup_values({T,_,_} = Key) ->
 %%
 %% This function works almost exactly like ets:update_counter/3
 %% (see [http://www.erlang.org/doc/man/ets.html#update_counter-3]), but
-%% will fail if the type of object referred to by Key is not a counter.
+%% will fail if the type of object referred to by Key is not a counter or
+%% a unique name (update_counter/2 can be performed on names as well, but they
+%% do not count as counter objects, and do not affect aggregated counters).
 %%
 %% Aggregated counters with the same name will be updated automatically.
 %% The `UpdateOp' patterns are the same as for `ets:update_counter/3', except
 %% that the position is omitted; in gproc, the value position is always `3'.
+%%
+%% If `Key' refers to a unique name, the operation will depend on the value
+%% part of the registration being an integer(). While non-integer values are
+%% not permitted at all for counter objects, it is the user's responsibility to
+%% ensure that a name, on which `update_counter/2' is to be performed, has the
+%% appropriate value type.
 %% @end
 %%
 -spec update_counter(key(), increment()) -> integer().
 update_counter(Key, Incr) ->
-    ?CATCH_GPROC_ERROR(update_counter1(Key, Incr), [Key, Incr]).
+    Pid = case Key of
+	      {n,_,_} -> n;
+	      {c,_,_} -> self()
+	  end,
+    ?CATCH_GPROC_ERROR(update_counter1(Key, Pid, Incr), [Key, Incr]).
 
-update_counter1({c,l,_} = Key, Incr) ->
-    gproc_lib:update_counter(Key, Incr, self());
-update_counter1({c,g,_} = Key, Incr) ->
+update_counter(Key, Pid, Incr) when is_pid(Pid);
+				    Pid == shared; Pid == n ->
+    ?CATCH_GPROC_ERROR(update_counter1(Key, Pid, Incr), [Key, Pid, Incr]).
+
+update_counter1({T,l,_} = Key, Pid, Incr) when T==c; T==n ->
+    gproc_lib:update_counter(Key, Incr, Pid);
+update_counter1({T,g,_} = Key, Pid, Incr) when T==c; T==n ->
     ?CHK_DIST,
-    gproc_dist:update_counter(Key, Incr);
-update_counter1(_, _) ->
+    gproc_dist:update_counter(Key, Pid, Incr);
+update_counter1(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 %% @doc Update a list of counters
@@ -1611,7 +1774,7 @@ bcast1(Ns, {T,l,_} = Key, Msg) when T==p; T==a; T==c; T==n; T==p ->
 
 %% @spec (Context :: context()) -> key() | '$end_of_table'
 %%
-%% @doc Behaves as ets:first(Tab) for a given type of registration object.
+%% @doc Behaves as ets:first(Tab) for a given type of registration.
 %%
 %% See [http://www.erlang.org/doc/man/ets.html#first-1].
 %%  The registry behaves as an ordered_set table.
@@ -1629,7 +1792,7 @@ first(Context) ->
 
 %% @spec (Context :: context()) -> key() | '$end_of_table'
 %%
-%% @doc Behaves as ets:last(Tab) for a given type of registration object.
+%% @doc Behaves as ets:last(Tab) for a given type of registration.
 %%
 %% See [http://www.erlang.org/doc/man/ets.html#last-1].
 %% The registry behaves as an ordered_set table.
@@ -1646,7 +1809,7 @@ last(Context) ->
 
 %% @spec (Context::context(), Key::key()) -> key() | '$end_of_table'
 %%
-%% @doc Behaves as ets:next(Tab,Key) for a given type of registration object.
+%% @doc Behaves as ets:next(Tab,Key) for a given type of registration.
 %%
 %% See [http://www.erlang.org/doc/man/ets.html#next-2].
 %% The registry behaves as an ordered_set table.
@@ -1654,11 +1817,24 @@ last(Context) ->
 %%
 next(Context, K) ->
     {S,T} = get_s_t(Context),
-    step(ets:next(?TAB,K), S, T).
+    {Prev,Unwrap} =
+	case K of
+	    {{_,_,_},_} ->
+		{K, false};
+	    {_,_,_} ->
+		{{K,[]}, true}  % [] is higher than pid(), shared, p, c...
+	end,
+    unwrap(Unwrap, step(ets:next(?TAB,Prev), S, T)).
+
+unwrap(true, {{_,_,_} = R,_}) ->
+    R;
+unwrap(_, R) ->
+    R.
+
 
 %% @spec (Context::context(), Key::key()) -> key() | '$end_of_table'
 %%
-%% @doc Behaves as ets:prev(Tab,Key) for a given type of registration object.
+%% @doc Behaves as ets:prev(Tab,Key) for a given type of registration.
 %%
 %% See [http://www.erlang.org/doc/man/ets.html#prev-2].
 %% The registry behaves as an ordered_set table.
@@ -1666,7 +1842,13 @@ next(Context, K) ->
 %%
 prev(Context, K) ->
     {S, T} = get_s_t(Context),
-    step(ets:prev(?TAB, K), S, T).
+    {Prev, Unwrap} =
+	case K of
+	    {{_,_,_},_} -> {K, false};
+	    {_,_,_} ->
+		{{K,1}, true}
+	end,
+    unwrap(Unwrap, step(ets:prev(?TAB, Prev), S, T)).
 
 step(Key, '_', '_') ->
     case Key of
@@ -1813,6 +1995,19 @@ handle_call({reg, {_T,l,_} = Key, Val}, {Pid,_}, S) ->
         false ->
             {reply, badarg, S}
     end;
+handle_call({set_attributes, {_,l,_} = Key, Attrs}, {Pid,_}, S) ->
+    case gproc_lib:insert_attr(Key, Attrs, Pid, l) of
+	false -> {reply, badarg, S};
+	L when is_list(L)  ->
+	    {reply, true, S}
+    end;
+handle_call({set_attributes_shared, {_,l,_} = Key, Attrs}, _, S) ->
+    case gproc_lib:insert_attr(Key, Attrs, shared, l) of
+	false ->
+	    {reply, badarg, S};
+	L when is_list(L) ->
+	    {reply, true, S}
+    end;
 handle_call({reg_or_locate, {T,l,_} = Key, Val, P}, _, S) ->
     case ets:lookup(?TAB, {Key, T}) of
 	[] ->
@@ -1914,6 +2109,13 @@ handle_call({set, {_,l,_} = Key, Value}, {Pid,_}, S) ->
         false ->
             {reply, badarg, S}
     end;
+handle_call({set_shared, {_,l,_} = Key, Value}, {_,_}, S) ->
+    case gproc_lib:do_set_value(Key, Value, shared) of
+        true ->
+            {reply, true, S};
+        false ->
+            {reply, badarg, S}
+    end;
 handle_call({audit_process, Pid}, _, S) ->
     _ = case is_process_alive(Pid) of
 	    false ->

+ 41 - 9
src/gproc_dist.erl

@@ -26,11 +26,13 @@
          reg/1, reg/2, unreg/1,
 	 reg_or_locate/3,
 	 reg_shared/2, unreg_shared/1,
+	 set_attributes/2,
+	 set_attributes_shared/2,
          mreg/2,
          munreg/2,
          set_value/2,
          give_away/2,
-         update_counter/2,
+         update_counter/3,
 	 update_counters/1,
 	 update_shared_counter/2,
 	 reset_counter/1]).
@@ -115,6 +117,16 @@ reg_shared({_,g,_} = Key, Value) ->
 reg_shared(_, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
+set_attributes({_,g,_} = Key, Attrs) ->
+    leader_call({set_attributes, Key, Attrs, self()});
+set_attributes(_, _) ->
+    ?THROW_GPROC_ERROR(badarg).
+
+set_attributes_shared({_,g,_} = Key, Attrs) ->
+    leader_call({set_attributes, Key, Attrs, shared});
+set_attributes_shared(_, _) ->
+    ?THROW_GPROC_ERROR(badarg).
+
 
 mreg(T, KVL) ->
     if is_list(KVL) -> leader_call({mreg, T, g, KVL, self()});
@@ -152,9 +164,10 @@ give_away({_,g,_} = Key, To) ->
     leader_call({give_away, Key, To, self()}).
 
 
-update_counter({c,g,_} = Key, Incr) when is_integer(Incr) ->
-    leader_call({update_counter, Key, Incr, self()});
-update_counter(_, _) ->
+update_counter({T,g,_} = Key, Pid, Incr) when is_integer(Incr), T==c;
+					      is_integer(Incr), T==n ->
+    leader_call({update_counter, Key, Incr, Pid});
+update_counter(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 update_counters(List) when is_list(List) ->
@@ -239,8 +252,9 @@ elected(S, _E, _Node) ->
 
 globs() ->
     Gs = ets:select(?TAB, [{{{{'_',g,'_'},'_'},'_','_'},[],['$_']}]),
+    As = ets:select(?TAB, [{{{'$1',{'_',g,'_'}}, '$2'},[],['$_']}]),
     _ = [gproc_lib:ensure_monitor(Pid, g) || {_, Pid, _} <- Gs],
-    Gs.
+    Gs ++ As.
 
 surrendered(#state{is_leader = true} = S, {globals, Globs}, _E) ->
     %% Leader conflict!
@@ -307,6 +321,13 @@ handle_leader_call({reg, {_C,g,_Name} = K, Value, Pid}, _From, S, _E) ->
             %%     end,
             {reply, true, [{insert, Vals}], S}
     end;
+handle_leader_call({set_attributes, {_,g,_} = K, Attrs, Pid}, _From, S, _E) ->
+    case gproc_lib:insert_attr(K, Attrs, Pid, g) of
+	false ->
+	    {reply, badarg, S};
+	NewAttrs when is_list(NewAttrs) ->
+	    {reply, true, [{insert, [{{Pid,K}, NewAttrs}]}], S}
+    end;
 handle_leader_call({reg_or_locate, {n,g,_} = K, Value, Pid}, _From, S, _E) ->
     case gproc_lib:insert_reg(K, Value, Pid, g) of
 	false ->
@@ -354,10 +375,16 @@ handle_leader_call({demonitor, {T,g,_} = Key, Ref, Pid}, _From, S, _E)
 		    {reply, Ref, [{insert, [NewRev]}], S}
 	    end
     end;
-handle_leader_call({update_counter, {c,g,_Ctr} = Key, Incr, Pid}, _From, S, _E)
-  when is_integer(Incr) ->
+handle_leader_call({update_counter, {T,g,_Ctr} = Key, Incr, Pid}, _From, S, _E)
+  when is_integer(Incr), T==c;
+       is_integer(Incr), T==n ->
     try New = ets:update_counter(?TAB, {Key, Pid}, {3,Incr}),
-        Vals = [{{Key,Pid},Pid,New} | update_aggr_counter(Key, Incr)],
+	 RealPid = case Pid of
+		       n -> ets:lookup_element(?TAB, {Key,Pid}, 2);
+		       shared -> shared;
+		       P when is_pid(P) -> P
+		   end,
+	 Vals = [{{Key,Pid},RealPid,New} | update_aggr_counter(Key, Incr)],
         {reply, New, [{insert, Vals}], S}
     catch
         error:_ ->
@@ -674,7 +701,10 @@ surrendered_1(Globs) ->
     %% remove all remote globals.
     ets:select_delete(?TAB, [{{{{'_',g,'_'},'_'}, '$1', '_'},
                               [{'=/=', {node,'$1'}, node()}],
-                              [true]}]),
+                              [true]},
+			     {{{'$1',{'_',g,'_'}}, '_'},
+			      [{'=/=', {node,'$1'}, node()}],
+			      [true]}]),
     %% insert new non-local globals, collect the leader's version of
     %% what my globals are
     Ldr_local_globs =
@@ -764,6 +794,8 @@ expand_ops([]) ->
 expand_ops(_) ->
     ?THROW_GPROC_ERROR(badarg).
 
+update_aggr_counter({n,_,_}, _) ->
+    [];
 update_aggr_counter(Key, Incr) ->
     update_aggr_counter(Key, Incr, []).
 

+ 30 - 6
src/gproc_lib.erl

@@ -27,6 +27,7 @@
          ensure_monitor/2,
          insert_many/4,
          insert_reg/4,
+	 insert_attr/4,
          remove_many/4,
          remove_reg/3, remove_reg/4,
 	 add_monitor/3,
@@ -71,6 +72,11 @@ insert_reg({T,_,Name} = K, Value, Pid, Scope) when T==a; T==n ->
                     false
             end
     end;
+insert_reg({p,Scope,_} = K, Value, shared, Scope)
+  when Scope == g; Scope == l ->
+    %% shared properties are unique
+    Info = [{{K, shared}, shared, Value}, {{shared,K}, []}],
+    ets:insert_new(?TAB, Info);
 insert_reg({c,Scope,Ctr} = Key, Value, Pid, Scope) when Scope==l; Scope==g ->
     %% Non-unique keys; store Pid in the key part
     K = {Key, Pid},
@@ -90,6 +96,20 @@ insert_reg({_,_,_} = Key, Value, Pid, _Scope) when is_pid(Pid) ->
     ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, []}]).
 
 
+insert_attr({_,Scope,_} = Key, Attrs, Pid, Scope) when Scope==l;
+						       Scope==g ->
+    case ets:lookup(?TAB,  K = {Pid, Key}) of
+	[{_, Attrs0}] when is_list(Attrs) ->
+	    As = proplists:get_value(attrs, Attrs0, []),
+	    As1 = lists:foldl(fun({K1,_} = Attr, Acc) ->
+				     lists:keystore(K1, 1, Acc, Attr)
+			     end, As, Attrs),
+	    Attrs1 = lists:keystore(attrs, 1, Attrs0, {attrs, As1}),
+	    ets:insert(?TAB, {K, Attrs1}),
+	    Attrs1;
+	_ ->
+	    false
+    end.
 
 -spec insert_many(type(), scope(), [{key(),any()}], pid()) ->
           {true,list()} | false.
@@ -347,8 +367,9 @@ remove_counter_1({c,C,N} = Key, Val, Pid) ->
     Res.
 
 do_set_value({T,_,_} = Key, Value, Pid) ->
-    K2 = if T==n orelse T==a -> T;
-            true -> Pid
+    K2 = if Pid == shared -> shared;
+	    T==n orelse T==a -> T;
+	    true -> Pid
          end,
     case (catch ets:lookup_element(?TAB, {Key,K2}, 2)) of
         {'EXIT', {badarg, _}} ->
@@ -365,17 +386,20 @@ do_set_counter_value({_,C,N} = Key, Value, Pid) ->
     update_aggr_counter(C, N, Value - OldVal),
     Res.
 
-update_counter({c,l,Ctr} = Key, Incr, Pid) when is_integer(Incr) ->
+update_counter({T,l,Ctr} = Key, Incr, Pid) when is_integer(Incr), T==c;
+						is_integer(Incr), T==n ->
     Res = ets:update_counter(?TAB, {Key, Pid}, {3,Incr}),
     update_aggr_counter(l, Ctr, Incr),
     Res;
-update_counter({c,l,Ctr} = Key, {Incr, Threshold, SetValue}, Pid)
-  when is_integer(Incr), is_integer(Threshold), is_integer(SetValue) ->
+update_counter({T,l,Ctr} = Key, {Incr, Threshold, SetValue}, Pid)
+  when is_integer(Incr), is_integer(Threshold), is_integer(SetValue), T==c;
+       is_integer(Incr), is_integer(Threshold), is_integer(SetValue), T==n ->
     [Prev, New] = ets:update_counter(?TAB, {Key, Pid},
 				     [{3, 0}, {3, Incr, Threshold, SetValue}]),
     update_aggr_counter(l, Ctr, New - Prev),
     New;
-update_counter({c,l,Ctr} = Key, Ops, Pid) when is_list(Ops) ->
+update_counter({T,l,Ctr} = Key, Ops, Pid) when is_list(Ops), T==c;
+					       is_list(Ops), T==n ->
     case ets:update_counter(?TAB, {Key, Pid},
 			    [{3, 0} | expand_ops(Ops)]) of
 	[_] ->

+ 897 - 0
src/gproc_pool.erl

@@ -0,0 +1,897 @@
+%% ``The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% @author Ulf Wiger <ulf@wiger.net>
+%%
+%% @doc Load balancing functions based on Gproc.
+%%
+%% This module implements support for load-balancing server pools. It was
+%% originally intended mainly as an example of how to use various Gproc
+%% resources (e.g. counters and shared properties), but is fully integrated
+%% into Gproc, and fully functional.
+%%
+%% <h2>Concepts</h2>
+%%
+%% Each pool has a list of 'named' workers (defined using `add_worker/2') and
+%% a load-balancing strategy. Processes can then 'connect' to the pool (with
+%% `connect_worker/2'), using one of the defined names.
+%%
+%% Users then 'pick' one of the currently connected processes in the pool. Which
+%% process is picked depends on the load-balancing strategy.
+%%
+%% The whole representation of the pool and its connected workers is in gproc.
+%% The server `gproc_pool' is used to serialize pool management updates, but
+%% worker selection is performed entirely in the calling process, and can be
+%% performed by several processes concurrently.
+%%
+%% <h3>Load-balancing strategies</h3>
+%%
+%% * `round_robin' is the default. A wrapping gproc counter keeps track of the
+%%   latest worker picked, and `gproc:next()' is used to find the next worker.
+%% * `random' picks a random worker from the pool.
+%% * `hash' requires a value (`pick/2'), and picks a worker based on the hash of
+%%   that value.
+%% * `direct' takes an integer as an argument, and picks the next worker (modulo
+%%   the size of the pool). This is mainly for implementations that implement
+%%   a load-balancing strategy on top of `gproc_pool'.
+%% * `claim' picks the first available worker and 'claims' it while executing
+%%   a user-provided fun. This means that the number of concurrently executing
+%%   jobs will not exceed the size of the pool.
+%% @end
+-module(gproc_pool).
+-behavior(gen_server).
+
+%% gproc round-robin name lookup
+-export([new/1,                % (Pool) -> (Pool, round_robin, [])
+	 new/3,                % (Pool, Type, Opts)
+	 delete/1,             % (Pool)
+	 force_delete/1,       % (Pool)
+	 add_worker/2,         % (Pool, Name)      -> Pos
+	 add_worker/3,         % (Pool, Name, Pos) -> Pos
+	 remove_worker/2,      % (Pool, Name)
+	 connect_worker/2,     % (Pool, Name)
+	 disconnect_worker/2,  % (Pool, Name)
+	 whereis_worker/2,     % (Pool, Name)
+	 worker_id/2,          % (Pool, Name)
+	 active_workers/1,     % (Pool)
+	 defined_workers/1,    % (Pool)
+	 worker_pool/1,        % (Pool)
+	 pick/1,               % (Pool)
+	 pick/2,               % (Pool, Value)
+	 claim/2,              % (Pool, Fun)
+	 log/1,                % (WorkerId)
+	 randomize/1]).        % (Pool)
+
+-export([start_link/0]).
+-export([init/1,
+	 handle_call/3,
+	 handle_cast/2,
+	 handle_info/2,
+	 terminate/2,
+	 code_change/3]).
+
+-export([test/1, test/3, test_run/2, test_run1/2,
+	 setup_test_pool/3, remove_test_pool/1]).
+
+-define(POOL(Pool), {p,l,{?MODULE,Pool}}).
+-define(POOL_CUR(Pool), {c,l,{?MODULE,Pool,cur}}).
+-define(POOL_WRK(Pool,Name), {c,l,{?MODULE,Pool,w,Name}}).
+
+-record(st, {}).
+
+%% @spec new(Pool::any()) -> true
+%%
+%% @equiv new(Pool, round_robin, [])
+new(Pool) ->
+    new(Pool, round_robin, []).
+
+%% @spec new(Pool::any(), Type, Opts) -> true
+%%     Type = round_robin | random | hash | direct | claim
+%%     Opts = [{size, integer()} | {auto_size, boolean()}]
+%%
+%% @doc Create a new pool.
+%%
+%% The pool starts out empty. If a size is not given, the pool size is set to
+%% 0 initially. `auto_size' is `true' by default if size is not specified, but
+%% `false' by default otherwise. If `auto_size == true', the pool will be
+%% enlarged to accomodate new workers, when necessary. Otherwise, trying to add
+%% a worker when the pool is full will raise an exception, as will trying to add
+%% a worker on a specific position beyond the current size of the pool.
+%%
+%% If the given pool already exists, this function will raise an exception.
+%% @end
+new(Pool, Type, Opts) when Type == round_robin;
+			   Type == random;
+			   Type == hash;
+			   Type == direct;
+			   Type == claim ->
+    call({new, Pool, Type, Opts}).
+
+%% @spec delete(Pool::any()) -> true
+%% @doc Delete an existing pool.
+%%
+%% This function will delete a pool, only if there are no connected workers.
+%% Ensure that workers have been disconnected before deleting the pool.
+%% @end
+%%
+delete(Pool) ->
+    call({delete, Pool}).
+
+%% @spec force_delete(Pool::any()) -> true
+%% @doc Forcibly remove a pool, terminating all active workers
+%%
+%% This function is primarily intended for cleanup of any pools that might have
+%% become inconsistent (for whatever reason). It will clear out all resources
+%% belonging to the pool and send `exit(Pid, kill)' signals to all connected
+%% workers (except the calling process).
+%% @end
+%%
+force_delete(Pool) ->
+    %% This is not pretty, but this function is mainly intended to clean up
+    %% a pool that's not used, with no regard to connected workers, except self(),
+    %% (that is, we kill each connected worker). We don't worry about races,
+    %% so don't go to the server (which doesn't have own state on the pool
+    %% anyway).
+    force_delete_(Pool).
+
+%% @spec add_worker(Pool::any(), Name::any()) -> integer()
+%%
+%% @doc Assign a worker name to the pool, returning the worker's position.
+%%
+%% Before a worker can connect to the pool, its name must be added. If no explicit
+%% position is given (see {@link add_worker/3}), the most suitable position,
+%% depending on load-balancing algorithm, is selected: for round_robin and direct
+%% pools, names are packed tightly from the beginning; for hash and random pools,
+%% slots are filled as sparsely as possible, in order to maintain an even
+%% likelihood of hitting each worker.
+%%
+%% An exception is raised if the pool is full (and `auto_size' is false), or if
+%% `Name' already exists in the pool.
+%%
+%% Before a worker can be used, a process must connect to it (see
+%% {@link connect_worker/2}.
+%% @end
+add_worker(Pool, Name) ->
+    call({add_worker, Pool, Name}).
+
+%% @spec add_worker(Pool::any(), Name::any(), Slot::integer()) -> integer()
+%%
+%% @doc Assign a worker name to a given slot in the pool, returning the slot.
+%%
+%% This function allows the pool maintainer to exactly position each worker
+%% inside the pool. An exception is raised if the position is already taken,
+%% or if `Name' already exists in the pool. If `Slot' is larger than the current
+%% size of the pool, an exception is raised iff `auto_size' is `false';
+%% otherwise the pool is expanded to accomodate the new position.
+%% @end
+add_worker(Pool, Name, Slot) ->
+    call({add_worker, Pool, Name, Slot}).
+
+%% @spec connect_worker(Pool::any(), Name::any()) -> true
+%% @doc Connect the current process to `Name' in `Pool'.
+%%
+%% Typically, a server will call this function as it starts, similarly to when
+%% it registers itself. In fact, calling `connect_worker/2' leads to the process
+%% being registered as `{n,l,[gproc_pool,N,Name]}', where `N' is the position of
+%% `Name' in the pool. This means (a) that gproc monitors the worker, and
+%% removes the connection automatically if it dies, and (b) that the registered
+%% names can be listed in order of their positions in the pool.
+%%
+%% This function raises an exception if `Name' does not exist in `Pool' (or
+%% there is no such pool), or if another worker is already connected to
+%% `Name'.
+%% @end
+%%
+connect_worker(Pool, Name) ->
+    gproc:reg(worker_id(Pool, Name), 0).
+
+%% @spec disconnect_worker(Pool, Name) -> true
+%%
+%% @doc Disconnect the current process from `Name' in `Pool'.
+%%
+%% This function is similar to a `gproc:unreg()' call. It removes the
+%% connection between `Pool', `Name' and pid, and makes it possible for another
+%% process to connect to `Name'.
+%%
+%% An exception is raised if there is no prior connection between `Pool',
+%% `Name' and the current process.
+%% @end
+%%
+disconnect_worker(Pool, Name) ->
+    gproc:unreg(worker_id(Pool, Name)).
+
+%% @spec remove_worker(Pool::any(), Name::any()) -> true
+%% @doc Remove a previously added worker.
+%%
+%% This function will assume that any connected worker is disconnected first.
+%% It will fail if there is no such pool, but will return `true' in the case
+%% when `Name' did not exist in the pool in the first place.
+%% @end
+remove_worker(Pool, Name) ->
+    call({remove_worker, Pool, Name}).
+
+%% @spec whereis_worker(Pool::any(), Name::any()) -> pid() | undefined
+%% @doc Look up the pid of a connected worker.
+%%
+%% This function works similarly to `gproc:where/1': it will return the pid
+%% of the worker connected as `Pool / Name', if there is such a worker; otherwise
+%% it will return `undefined'. It will raise an exception if `Name' has not been
+%% added to the pool.
+%% @end
+whereis_worker(Pool, Name) ->
+    ID = worker_id(Pool, Name),
+    gproc:where(ID).
+
+%% @spec worker_id(Pool, Name) -> GprocName
+%% @doc Return the unique gproc name corresponding to a name in the pool.
+%%
+%% This function assumes that `Name' has been added to `Pool'. It returns the
+%% unique name that a connected worker will be registered as. This doesn't mean
+%% that there is, in fact, such a connected worker.
+%% @end
+worker_id(Pool, Name) ->
+    N = gproc:get_attribute(?POOL_WRK(Pool, Name), shared, n),
+    {n, l, [?MODULE, Pool, N, Name]}.
+
+%% @spec active_workers(Pool::any()) -> [{Name, Pid}]
+%% @doc Return a list of currently connected workers in the pool.
+%%
+active_workers(Pool) ->
+    gproc:select(
+      {l,n},
+      [{ {{n,l,[?MODULE,Pool,'$1','$2']},'$3','_'}, [{is_integer, '$1'}],
+	 [{{'$2', '$3'}}] }]).
+
+%% @spec defined_workers(Pool::any()) -> [{Name, Pos, Count}]
+%% @doc Return a list of added workers in the pool.
+%%
+%% The added workers are slots in the pool that have been given names, and thus
+%% can be connected to. This function doesn't detect whether or not there are
+%% any connected (active) workers.
+%%
+%% The list contains `{Name, Pos, Count}', where `Name' is the name of the added
+%% worker, `Pos' is its position in the pool, and `Count' represents the number
+%% of times the worker has been picked (assuming callers keep count by explicitly
+%% calling {@link log/1}).
+%% @end
+defined_workers(Pool) ->
+    K = ?POOL(Pool),
+    [{N, Pos, gproc:get_value(?POOL_WRK(Pool, N))}
+     || {N, Pos} <- get_workers_(K)].
+
+%% @spec worker_pool(Pool::any()) -> [integer() | {Name, Pos}]
+%% @doc Return a list of slots and/or named workers in the pool.
+%%
+%% This function is mainly for testing, but can also be useful when implementing
+%% your own worker placement algorithm on top of gproc_pool.
+%%
+%% A plain integer represents an unfilled slot, and `{Name, Pos}' represents an
+%% added worker. The pool is always filled to the current size.
+%% @end
+worker_pool(Pool) ->
+    get_workers_(?POOL(Pool)).
+
+%% @spec pick(Pool::any()) -> GprocName | false
+%% @doc Pick a worker from the pool given the pool's load-balancing algorithm.
+%%
+%% The pool types that allows picking without an extra argument are
+%% round_robin and random. This function returns `false' if there is no available
+%% worker, or if `Pool' is not a valid pool.
+%% @end
+pick(Pool) ->
+    case gproc:get_value(?POOL(Pool), shared) of
+	{0, _} -> false;
+	{Sz, Type} when Type == round_robin; Type == random ->
+	    pick(Pool, Sz, Type);
+	_ ->
+	    error(badarg)
+    end.
+
+%% @spec pick(Pool::any(), Value::any()) -> GprocName | false
+%% @doc Pick a worker from the pool based on `Value'.
+%%
+%% The pool types that allows picking based on an extra argument are
+%% hash and direct. This function returns `false' if there is no available
+%% worker, or if `Pool' is not a valid pool.
+%%
+%% If the pool is of type `direct', `Value' must be an integer corresponding to
+%% a position in the pool (modulo the size of the pool). If the type is
+%% `hash', `Value' may be any term, and its hash value will serve as a guide for
+%% selecting a worker.
+%% @end
+pick(Pool, N) ->
+    case gproc:get_value(?POOL(Pool), shared) of
+	{0, _} -> false;
+	{Sz, Type} when Type == hash; Type == direct ->
+	    pick(Pool, Sz, Type, N);
+	_ ->
+	    error(badarg)
+    end.
+
+
+pick(Pool, Sz, round_robin) ->
+    Next = incr(Pool, 1, Sz),
+    case gproc:next({l,n}, {n,l,[?MODULE,Pool,Next]}) of
+	{n,l,[?MODULE,Pool,Actual,_Name]} = Pick ->
+	    case Actual - Next of
+		Diff when Diff > 1 ->
+		    gproc:update_counter(
+		      ?POOL_CUR(Pool), shared, {Diff, Sz, 1}),
+		    Pick;
+		_ ->
+		    Pick
+	    end;
+	_ ->
+	    case gproc:next({l,n}, {n,l,[?MODULE,Pool,0]}) of
+		{n,l,[?MODULE,Pool,Actual1,_Name1]} = Pick ->
+		    incr(Pool, Sz-Next+Actual1, Sz),
+		    %% gproc:update_counter(
+		    %%   ?POOL_CUR(Pool), shared, {Sz-Next+Actual1, Sz, 1}),
+		    Pick;
+		_ ->
+		    false
+	    end
+    end;
+pick(Pool, Sz, random) ->
+    pick_near(Pool, crypto:rand_uniform(1, Sz + 1)).
+
+pick(Pool, Sz, hash, Val) ->
+    pick_near(Pool, erlang:phash2(Val, Sz) + 1);
+pick(Pool, Sz, direct, N) when is_integer(N), N > 0 ->
+    pick_near(Pool, case (N rem Sz-1) + 1  of 0 -> Sz; N1 -> N1 end).
+
+pick_near(Pool, N) ->
+    case gproc:next({l,n}, {n,l,[?MODULE,Pool,N]}) of
+	{n,l,[?MODULE,Pool,_,_]} = Pick ->
+	    Pick;
+	_ ->
+	    %% wrap
+	    case gproc:next({l,n}, {n,l,[?MODULE,Pool,1]}) of
+		{n,l,[?MODULE,Pool,_,_]} = Pick ->
+		    Pick;
+		_ ->
+		    false
+	    end
+    end.
+
+%% @spec claim(Pool::any(), Fun::function()) -> {true, Res} | false
+%% @doc Picks the first available worker in the pool and applies `Fun'.
+%%
+%% A `claim' pool allows the caller to "claim" a worker during a short span
+%% (essentially, a lock is set and released as soon as `Fun' returns).
+%% Once a worker is selected, `Fun(Name, Pid)' is called, where `Name' is a
+%% unique gproc name of the worker, and `Pid' is its process identifier.
+%% The gproc name of the worker serves as a mutex, where its value is 0 (zero)
+%% if the worker is free, and 1 (one) if it is busy. The mutex operation is
+%% implemented using `gproc:update_counter/2'.
+%% @end
+claim(Pool, F) when is_function(F, 2) ->
+    case gproc:get_value(?POOL(Pool), shared) of
+	{0, _} -> false;
+	{_, claim} ->
+	    claim_(Pool, F);
+	_ ->
+	    error(badarg)
+    end.
+
+claim_(Pool, F) ->
+    case gproc:select({l,n}, [{ {{n,l,[?MODULE,Pool,'$1','_']}, '_', 0}, [],
+				[{{ {element,1,'$_'}, '$1' }}]}], 1) of
+	{[{K, Pid}], Cont} ->
+	    case try_claim(K, Pid, F) of
+		{true, _} = True ->
+		    True;
+		false ->
+		    claim_cont(Cont, F)
+	    end;
+	_ ->
+	    false
+    end.
+
+claim_cont(Cont, F) ->
+    case gproc:select(Cont) of
+	{[{K, Pid}], Cont1} ->
+	    case try_claim(K, Pid, F) of
+		{true, _} = True ->
+		    True;
+		false ->
+		    claim_cont(Cont1, F)
+	    end;
+	_ ->
+	    false
+    end.
+
+try_claim(K, Pid, F) ->
+    case gproc:update_counter(K, [0, {1, 1, 1}]) of
+	[0, 1] ->
+	    %% have lock
+	    try  Res = F(K, Pid),
+		 {true, Res}
+	    after
+		gproc:set_value(K, 0)
+	    end;
+	[1, 1] ->
+	    %% no
+	    false
+    end.
+
+
+%% @spec log(GprocKey) -> integer()
+%% @doc Update a counter associated with a worker name.
+%%
+%% Each added worker has a gproc counter that can be used e.g. to keep track of
+%% the number of times the worker has been picked. Since it's associated with the
+%% named 'slot', and not to the connected worker, its value will persist even
+%% if the currently connected worker dies.
+%% @end
+log({n,l,[?MODULE,Pool,_,Name]}) ->
+    gproc:update_shared_counter(?POOL_WRK(Pool,Name), 1).
+
+%% @spec randomize(Pool::any()) -> integer()
+%% @doc Randomizes the "next" pointer for the pool.
+%%
+%% This function only has an effect for `round_robin' pools, which have a
+%% reference to the next worker to be picked. Without randomizing, the load
+%% balancing will always start with the first worker in the pool.
+%% @end
+randomize(Pool) ->
+    case pool_size(Pool) of
+	0 -> 0;
+	1 -> 1;
+	Sz ->
+	    incr(Pool, crypto:rand_uniform(0, Sz), Sz)
+    end.
+
+%% @spec pool_size(Pool::any()) -> integer()
+%% @doc Return the size of the pool.
+%%
+pool_size(Pool) ->
+    {Sz, _} = gproc:get_value(?POOL(Pool), shared),
+    Sz.
+
+
+
+%% ===================================================================
+%% Start, stop, call gen_server
+
+%% @private
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+%% @private
+init([]) ->
+    {ok, #st{}}.
+
+%% @private
+call(Req) ->
+    case gen_server:call(?MODULE, Req) of
+	badarg ->
+	    error(badarg);
+	{badarg, Reason} ->
+	    error(Reason);
+	Reply ->
+	    Reply
+    end.
+
+%% ===================================================================
+%% Gen_server callbacks
+
+%% @private
+handle_call(Req, From, S) ->
+    try handle_call_(Req, From, S)
+    catch
+	error:Reason ->
+	    io:fwrite("server backtrace: ~p~n", [erlang:get_stacktrace()]),
+	    {reply, {badarg, Reason}, S}
+    end.
+
+handle_call_({new, Pool, Type, Opts}, _, S) ->
+    new_(Pool, Type, Opts),
+    {reply, ok, S};
+handle_call_({delete, Pool}, _, S) ->
+    delete_(Pool),
+    {reply, ok, S};
+handle_call_({force_delete, Pool}, _, S) ->
+    force_delete_(Pool),
+    {reply, ok, S};
+handle_call_({add_worker, Pool, Name}, _, S) ->
+    N = add_worker_(Pool, Name),
+    {reply, N, S};
+handle_call_({add_worker, Pool, Name, Pos}, _, S) ->
+    N = add_worker_(Pool, Name, Pos),
+    {reply, N, S};
+handle_call_({set_pool_size, Pool, Sz}, _, S) ->
+    Workers = get_workers_(Pool),
+    case get_last_worker_n(Workers) of
+	N when N > Sz ->
+	    {reply, badarg, S};
+	_ ->
+	    set_pool_size_(?POOL(Pool), Sz, Workers),
+	    {reply, true, S}
+    end;
+handle_call_({remove_worker, Pool, Name}, _, S) ->
+    ok = remove_worker_(Pool, Name),
+    {reply, true, S}.
+
+%% @private
+handle_cast(_, S) ->
+    {noreply, S}.
+
+%% @private
+handle_info(_, S) ->
+    {noreply, S}.
+
+%% @private
+terminate(_, _) ->
+    ok.
+
+%% @private
+code_change(_, S, _) ->
+    {ok, S}.
+
+%% ===================================================================
+%% Internal functions
+
+
+new_(Pool, Type, Opts) ->
+    valid_type(Type),
+    Size = proplists:get_value(size, Opts, 0),
+    Workers = lists:seq(1, Size),
+    gproc:reg_shared(K = ?POOL(Pool), {Size, Type}),
+    Opts1 =
+	case lists:keyfind(auto_size, 1, Opts) of
+	    false ->
+		Opts ++ [{auto_size, not lists:keymember(size, 1, Opts)}];
+	    {_, Bool} when is_boolean(Bool) ->
+		Opts
+	end,
+    gproc:set_attributes_shared(K, Opts1),
+    set_workers(K, Workers),
+    gproc:reg_shared(?POOL_CUR(Pool), Size).
+
+valid_type(T) when T==round_robin; T==hash; T==random; T==direct; T==claim ->
+    true;
+valid_type(_) ->
+    error(invalid_type).
+
+set_pool_size_(K, Sz, Workers) ->
+    {_, Type} = gproc:get_value(K, shared),
+    case length(Workers) of
+	Sz ->
+	    set_workers(K, Workers);
+	Len when Len > Sz ->
+	    Workers1 = lists:sublist(Workers, 1, Sz),
+	    set_workers(K, Workers1);
+	Len when Len < Sz ->
+	    Workers1 = Workers ++ lists:seq(Len+1, Sz),
+	    set_workers(K, Workers1)
+    end,
+    gproc:set_value_shared(K, {Sz, Type}).
+
+delete_(Pool) ->
+    K = ?POOL(Pool),
+    Ws = get_workers_(K),
+    case [1 || {_,_} <- Ws] of
+	[] ->
+	    gproc:unreg_shared(K),
+	    gproc:unreg_shared(?POOL_CUR(Pool));
+	[_|_] ->
+	    error(not_empty)
+    end.
+
+force_delete_(Pool) ->
+    Props = gproc:select({l,p}, [{ {?POOL(Pool), '_', '_'}, [], ['$_']}]),
+    Cur = gproc:select({l,c}, [{ {?POOL_CUR(Pool), '_', '_'}, [], ['$_']}]),
+    Workers = gproc:select(
+		{l,c}, [{ {?POOL_WRK(Pool,'_'), '_', '_'}, [], ['$_']}]),
+    Names = find_names(Pool, '_'),
+    io:fwrite("Props = ~p~n"
+	      "Cur = ~p~n"
+	      "Workers = ~p~n"
+	      "Names = ~p~n", [Props, Cur, Workers, Names]),
+    lists:foreach(
+      fun({Key, Pid, _}) when Pid == self() -> gproc:unreg(Key);
+	 ({_, Pid, _}) when is_pid(Pid) -> exit(Pid, kill)
+      end, Names),
+    [gproc:unreg_shared(W) || {W,shared,_} <- Cur ++ Props ++ Workers],
+    true.
+
+find_names(Pool, Pid) ->
+    gproc:select(
+      {l,n}, [{ {{n,l,[?MODULE,Pool,Pid,'_']}, '_', '_'}, [], ['$_']}]).
+
+add_worker_(Pool, Name) ->
+    K = ?POOL(Pool),
+    {Sz, Type} = gproc:get_value(K, shared),
+    AutoSz = gproc:get_attribute(K, shared, auto_size),
+    Ws0 = get_workers_(K),
+    {N,Ws1} =
+	case lists:keymember(Name, 1, Ws0) of
+	    false ->
+		case find_slot(Name, K, Ws0, Sz, Type, AutoSz) of
+		    {_, _} = Res ->
+			Res;
+		    false ->
+			error(pool_full)
+		end;
+	    true ->
+		error(exists)
+	end,
+    if N > Sz ->
+	    set_pool_size_(K, N, Ws1); % also calls set_workers/2
+       true ->
+	    %% size not changed
+	    set_workers(K, Ws1)
+    end,
+    reg_worker(Pool, Name, N),
+    N.
+
+add_worker_(Pool, Name, Pos) ->
+    K = ?POOL(Pool),
+    {Sz, _} = gproc:get_value(K, shared),
+    Ws0 = get_workers_(K),
+    if Pos > Sz ->
+	    case gproc:get_attribute(K, shared, auto_size) of
+		true ->
+		    Ws1 = Ws0 ++ lists:seq(Sz+1,Pos-1) ++ [{Name, Pos}],
+		    set_pool_size_(K, Pos, Ws1);
+		false ->
+		    error(out_of_range)
+	    end;
+       true ->
+	    case lists:nth(Pos, Ws0) of
+		{_,_} -> error(exists);
+		P when is_integer(P) ->
+		    Ws1 = set_pos(Pos, Ws0, {Name, Pos}),
+		    set_workers(K, Ws1)
+	    end
+    end,
+    reg_worker(Pool, Name, Pos),
+    Pos.
+
+reg_worker(Pool, Name, Pos) ->
+    gproc:reg_shared(Wrk = ?POOL_WRK(Pool, Name), 0),
+    gproc:set_attributes_shared(Wrk, [{n, Pos}]).
+
+remove_worker_(Pool, Name) ->
+    case whereis_worker(Pool, Name) of
+	Pid when is_pid(Pid) ->
+	    error({worker_connected, Pid});
+	undefined ->
+	    do_remove_worker_(Pool, Name)
+    end.
+
+do_remove_worker_(Pool, Name) ->
+    K = ?POOL(Pool),
+    Ws0 = get_workers_(K),
+    Ws1 = del_slot(Name, Ws0),
+    gproc:unreg_shared(?POOL_WRK(Pool, Name)),
+    case (NewLen = length(Ws1)) - length(Ws0) of
+	0 -> ok;
+	Diff when Diff < 0 ->
+	    gproc:set_value_shared(K, NewLen)
+    end,
+    gproc:set_attributes_shared(K, [{workers, Ws1}]),
+    ok.
+
+del_slot(Name, [{Name,_}]) ->
+    [];
+del_slot(Name, [{Name, Pos}|T]) ->
+    [Pos|T];
+del_slot(Name, [H|T]) ->
+    [H|del_slot(Name, T)].
+
+find_slot(Name, _, [], Sz, _, Auto) ->
+    case {Sz, Auto} of
+	{0, false} -> false;
+	{_, _} ->
+	    {1, [{Name, 1}]}
+    end;
+find_slot(Name, Key, Workers, Sz, Type, AutoSz) ->
+    case get_strategy(Key, Type) of
+	packed ->
+	    find_slot_packed(Name, Workers, AutoSz);
+	sparse ->
+	    find_slot_sparse(Name, Workers, Sz, AutoSz)
+    end.
+%%     find_slot(Name, Key, Workers, Sz, Type, AutoSz, Strategy).
+%% find_slot(Name, []) ->
+%%     {1, [{Name, 1}]};
+%% find_slot(Name, Slots) ->
+%%     find_slot(Name, Slots, []).
+
+get_last_worker_n(Ws) ->
+    get_last_worker_n(Ws, 0, 1).
+
+get_last_worker_n([{_,_}|T], _, P) ->
+    get_last_worker_n(T, P, P+1);
+get_last_worker_n([H|T], Last, P) when is_integer(H) ->
+    get_last_worker_n(T, Last, P+1);
+get_last_worker_n([], Last, _) ->
+    Last.
+
+find_slot_packed(Name, Workers, AutoSz) ->
+    find_slot_packed(Name, Workers, AutoSz, []).
+
+find_slot_packed(Name, [N|T], _, Acc) when is_integer(N) -> % empty slot
+    {N, lists:reverse(Acc) ++ [{Name, N}|T]};
+find_slot_packed(Name, [{_,Prev} = Last], true, Acc) ->  % last elem; expand pool
+    New = Prev+1,
+    {New, lists:reverse([{Name, New}, Last|Acc])};
+find_slot_packed(_, [_], false, _) ->
+    false;
+find_slot_packed(Name, [{_,_} = H|T], Auto, Acc) ->
+    find_slot_packed(Name, T, Auto, [H|Acc]).
+
+find_slot_sparse(Name, Ws, Sz, Auto) ->
+    %% Collect the position of the first and last filled slots, as well as
+    %% the largest gap between filled slots
+    case lists:foldl(
+	   fun(N, {Prev, StartP, First, Last, Max, MaxP}) when is_integer(N) ->
+		      case Prev+1 of
+			  Gap when Gap > Max ->
+			      {Gap, StartP, First, Last, Gap, StartP};
+			  Gap ->
+			      {Gap, StartP, First, Last, Max, MaxP}
+		      end;
+	      (N, []) when is_integer(N) ->
+		   %% skip
+		   [];
+	      ({_, Pos}, []) ->
+		   {0, Pos, _First = Pos, _Last = Pos, 0, 0};
+	      ({_, Pos}, {Prev, StartP, First, _PrevLast, Max, MaxP}) ->
+		      if Prev > Max ->
+			      {0, Pos, First, Pos, Prev, StartP};
+			 true ->
+			      {0, Pos, First, Pos, Max, MaxP}
+		      end
+	   end, [], Ws) of
+	[] ->
+	    %% all empty slots
+	    case {Sz, Auto} of
+		{0, false} ->
+		    false;
+		{0, true} ->
+		    {1, [{Name, 1}]};
+		{_, _} when is_integer(Sz), Sz > 0 ->
+		    {1, [{Name, 1}|tl(Ws)]}
+	    end;
+	{_, _, 1, Last, 0, _} ->
+	    %% Pool full
+	    if Auto ->
+		    NewPos = Last + 1,
+		    {NewPos, Ws ++ [{Name, NewPos}]};
+	       true ->
+		    false
+	    end;
+	{_, _, First, Last, MaxGap, StartPos} ->
+	    WrapGap = (Sz - Last) + First - 1,
+	    NewPos = if WrapGap >= MaxGap ->
+			     (Last + (WrapGap div 2) + 1) rem (Sz+1);
+			true ->
+			     (StartPos + (MaxGap div 2) + 1) rem (Sz+1)
+		     end,
+	    {NewPos, set_pos(NewPos, Ws, {Name, NewPos})}
+    end.
+
+set_pos(P, L, X) when P > 0, is_list(L) ->
+    set_pos(P, 1, L, X).
+
+set_pos(P, P, [_|T], X) ->
+    [X|T];
+set_pos(P, C, [H|T], X) when C < P ->
+    [H|set_pos(P, C+1, T, X)].
+
+get_workers_(K) ->
+    case gproc:get_attribute(K, shared, workers) of
+	undefined ->
+	    [];
+	L when is_list(L) ->
+	    L
+    end.
+
+set_workers(K, L) when is_list(L) ->
+    gproc:set_attributes_shared(K, [{workers, L}]).
+
+get_strategy(Key, Type) ->
+    Default = case Type of
+		  round_robin -> packed;
+		  random      -> sparse;
+		  hash        -> sparse;
+		  direct      -> packed;
+		  claim       -> packed
+	      end,
+    attribute(Key, fill_strategy, Default).
+
+attribute(Key, A, Default) ->
+    case gproc:get_attribute(Key, shared, A) of
+	undefined -> Default;
+	Value     -> Value
+    end.
+
+incr(Pool, Incr, Sz) ->
+    gproc:update_counter(?POOL_CUR(Pool), shared, {Incr, Sz, 1}).
+
+%% find_worker(Pool, Name) ->
+%%     case gproc:select(n, [{ {{n, l, {?MODULE, Pool, '_'}}, '_', Name},
+%% 			  [], ['$_'] }]) of
+%% 	[] ->
+%% 	    undefined;
+%% 	[{{n,l,{?MODULE,_,N}}, Pid, _}] ->
+%% 	    {N, Pid}
+%%     end.
+
+%% ============================= Test code ===========================
+
+%% @private
+test(N) when N > 0 ->
+    test(N, round_robin, []).
+
+%% @private
+test(N, Type, Opts) when Type==round_robin;
+			 Type==random;
+			 Type==hash;
+			 Type==direct;
+			 Type==claim ->
+    P = ?LINE,
+    setup_test_pool(P, Type, Opts),
+    F = if Type==hash; Type==direct -> test_run1;
+	   true -> test_run
+	end,
+    try timer:tc(?MODULE, F, [N, P])
+    after
+        remove_test_pool(P)
+    end.
+
+%% @private
+setup_test_pool(P, Type, Opts) ->
+    new(P, Type, Opts),
+    [begin R = add_worker(P, W),
+	   io:fwrite("add_worker(~p, ~p) -> ~p; Ws = ~p~n",
+		     [P, W, R, get_workers_(?POOL(P))]),
+           connect_worker(P, W)
+     end || W <- test_workers()].
+
+%% @private
+remove_test_pool(P) ->
+    io:fwrite("worker stats (~p):~n"
+              "~p~n", [P, gproc:select(
+                            {l,c},
+                            [{ {{c,l,{?MODULE,P,w,'$1'}},'_','$2'}, [],
+                               [{{'$1','$2'}}] }])]),
+    [begin disconnect_worker(P, W),
+           remove_worker(P, W)
+     end || W <- test_workers()],
+    delete(P).
+
+test_workers() -> [a,b,c,d,e,f].
+
+%% @private
+test_run(N, P) when N > 0 ->
+    Worker = pick(P),
+    true = (Worker =/= false),
+    log(Worker),
+    test_run(N-1, P);
+test_run(_, _) ->
+    ok.
+
+%% @private
+test_run1(N, P) when N > 0 ->
+    Worker = pick(P, N),
+    true = (Worker =/= false),
+    log(Worker),
+    test_run1(N-1, P);
+test_run1(_, _) ->
+    ok.

+ 3 - 1
src/gproc_sup.erl

@@ -52,7 +52,9 @@ init(_Args) ->
 	   permanent, 2000, worker, [gproc_monitor]},
     BCast = {gproc_bcast, {gproc_bcast, start_link, []},
 	     permanent, 2000, worker, [gproc_bcast]},
-    {ok,{{one_for_one, 15, 60}, [GProc| Dist] ++ [Mon, BCast]}}.
+    Pool = {gproc_pool, {gproc_pool, start_link, []},
+	    permanent, 2000, worker, [gproc_pool]},
+    {ok,{{one_for_one, 15, 60}, [GProc| Dist] ++ [Mon, BCast, Pool]}}.
 
 
 %%%----------------------------------------------------------------------