Browse Source

Merge pull request #95 from uwiger/uw-resource-counts

Uw resource counts
Ulf Wiger 9 years ago
parent
commit
981fab2e99
11 changed files with 619 additions and 230 deletions
  1. 15 7
      doc/gproc.md
  2. 29 3
      doc/gproc_pool.md
  3. 0 23
      include/gproc.hrl
  4. 212 129
      src/gproc.erl
  5. 3 1
      src/gproc_bcast.erl
  6. 48 28
      src/gproc_dist.erl
  7. 4 0
      src/gproc_int.hrl
  8. 125 35
      src/gproc_lib.erl
  9. 102 3
      test/gproc_dist_tests.erl
  10. 14 1
      test/gproc_test_lib.erl
  11. 67 0
      test/gproc_tests.erl

+ 15 - 7
doc/gproc.md

@@ -296,12 +296,12 @@ unique_id() = {n | a, <a href="#type-scope">scope()</a>, any()}
 ## 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
+<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 name or aggregated counter to be registered.</td></tr><tr><td valign="top"><a href="#await-3">await/3</a></td><td>Wait for a name or aggregated counter 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.</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_attribute_shared-2">get_attribute_shared/2</a></td><td>Get the attribute value of <code>Attr</code> associated with the shared <code>Key</code>.</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="#get_value_shared-1">get_value_shared/1</a></td><td>Reads the value stored with a shared key.</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.</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>Equivalent to <a href="#monitor-2"><tt>monitor(Key, info)</tt></a>.</td></tr><tr><td valign="top"><a href="#monitor-2">monitor/2</a></td><td>monitor a registered name
 <code>monitor(Key, info)</code> 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.</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 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 name or aggregated counter to be registered.</td></tr><tr><td valign="top"><a href="#nb_wait-2">nb_wait/2</a></td><td>Wait for a name or aggregated counter 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>
@@ -410,7 +410,7 @@ await(Key::<a href="#type-key">key()</a>, Timeout) -&gt; {pid(), Value}
 
 <ul class="definitions"><li><code>Timeout = integer() | infinity</code></li></ul>
 
-Wait for a local name to be registered.
+Wait for a name or aggregated counter to be registered.
 The function raises an exception if the timeout expires. Timeout must be
 either an interger > 0 or 'infinity'.
 A small optimization: we first perform a lookup, to see if the name
@@ -428,7 +428,7 @@ await(Node::node(), Key::<a href="#type-key">key()</a>, Timeout) -&gt; {pid(), V
 
 <ul class="definitions"><li><code>Timeout = integer() | infinity</code></li></ul>
 
-Wait for a local name to be registered on `Node`.
+Wait for a name or aggregated counter 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
@@ -1149,7 +1149,7 @@ nb_wait(Key::<a href="#type-key">key()</a>) -&gt; Ref
 </code></pre>
 <br />
 
-Wait for a local name to be registered.
+Wait for a name or aggregated counter 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>
@@ -1162,7 +1162,7 @@ nb_wait(Node::node(), Key::<a href="#type-key">key()</a>) -&gt; Ref
 </code></pre>
 <br />
 
-Wait for a local name to be registered on `Node`.
+Wait for a name or aggregated counter 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>
@@ -1335,16 +1335,24 @@ Behaviour support callback
 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>
+<ul class="definitions"><li><code>Key = {c, Scope, Name} | {n, 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.
+
+
+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 `reset_counter/1` is to be performed, has the
+appropriate value type.
 <a name="select-1"></a>
 
 ### select/1 ###

+ 29 - 3
doc/gproc_pool.md

@@ -63,7 +63,7 @@ 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="#pick_worker-1">pick_worker/1</a></td><td>Pick a worker pid from the pool given the pool's load-balancing algorithm.</td></tr><tr><td valign="top"><a href="#pick_worker-2">pick_worker/2</a></td><td>Pick a worker pid from the pool given the pool's load-balancing algorithm.</td></tr><tr><td valign="top"><a href="#ptest-4">ptest/4</a></td><td></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="#test_run0-2">test_run0/2</a></td><td></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>
+<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>Equivalent to <a href="#claim-3"><tt>claim(Pool, F, nowait)</tt></a>.</td></tr><tr><td valign="top"><a href="#claim-3">claim/3</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="#pick_worker-1">pick_worker/1</a></td><td>Pick a worker pid from the pool given the pool's load-balancing algorithm.</td></tr><tr><td valign="top"><a href="#pick_worker-2">pick_worker/2</a></td><td>Pick a worker pid from the pool given the pool's load-balancing algorithm.</td></tr><tr><td valign="top"><a href="#ptest-4">ptest/4</a></td><td></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="#setup_test_pool-4">setup_test_pool/4</a></td><td></td></tr><tr><td valign="top"><a href="#test_run0-2">test_run0/2</a></td><td></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>
@@ -135,16 +135,25 @@ otherwise the pool is expanded to accomodate the new position.
 
 ### claim/2 ###
 
+`claim(Pool, F) -> any()`
+
+Equivalent to [`claim(Pool, F, nowait)`](#claim-3).
+<a name="claim-3"></a>
+
+### claim/3 ###
+
 
 <pre><code>
-claim(Pool::any(), Fun::function()) -&gt; {true, Res} | false
+claim(Pool, F::Fun, Wait) -&gt; {true, Res} | false
 </code></pre>
-<br />
+
+<ul class="definitions"><li><code>Pool = any()</code></li><li><code>Fun = function()</code></li><li><code>Wait = nowait | {busy_wait, integer()}</code></li></ul>
 
 
 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
@@ -152,6 +161,16 @@ 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`.
+
+
+
+`Wait == nowait` means that the call will return `false` immediately if
+there is no available worker.
+
+
+`Wait == {busy_wait, Timeout}` will keep repeating the claim attempt
+for `Timeout` milliseconds. If still no worker is available, it will
+return `false`.
 <a name="connect_worker-2"></a>
 
 ### connect_worker/2 ###
@@ -424,6 +443,13 @@ 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="setup_test_pool-4"></a>
+
+### setup_test_pool/4 ###
+
+`setup_test_pool(P, Type0, Opts, Workers) -> any()`
+
+
 <a name="test_run0-2"></a>
 
 ### test_run0/2 ###

+ 0 - 23
include/gproc.hrl

@@ -18,26 +18,3 @@
 %% gproc.hrl: Common definitions
 
 -define(TAB, gproc).
-
-
--type type()     :: n | p | c | a.
--type scope()    :: l | g.
--type context()  :: {scope(),type()} | type().
--type sel_type() :: n | p | c | a |
-                    names | props | counters | aggr_counters.
-
--type sel_var() :: '_' | atom().
--type keypat()  :: {sel_type() | sel_var(), l | g | sel_var(), any()}.
--type pidpat()  :: pid() | sel_var().
--type headpat() :: {keypat(),pidpat(),any()}.
--type key()     :: {type(), scope(), any()}.
-
--type sel_pattern() :: [{headpat(), list(), list()}].
-
-%% update_counter increment
--type ctr_incr()   :: integer().
--type ctr_thr()    :: integer().
--type ctr_setval() :: integer().
--type ctr_update()  :: ctr_incr()
-		     | {ctr_incr(), ctr_thr(), ctr_setval()}.
--type increment() :: ctr_incr() | ctr_update() | [ctr_update()].

+ 212 - 129
src/gproc.erl

@@ -42,43 +42,13 @@
 %%
 %% @end
 
-%% @type type()  = n | p | c | a. n = name; p = property; c = counter;
-%%                                a = aggregate_counter
-%% @type scope() = l | g. l = local registration; g = global registration
-%%
-%% @type reg_id() = {type(), scope(), any()}.
-%% @type unique_id() = {n | a, scope(), any()}.
-%%
-%% @type monitor_type() = info | standby | follow.
-%%
-%% @type sel_scope() = scope | all | global | local.
-%% @type sel_type() = type() | names | props | counters | aggr_counters.
-%% @type context() = {scope(), type()} | type(). {'all','all'} is the default
-%%
-%% @type headpat() = {keypat(),pidpat(),ValPat}.
-%% @type keypat() = {sel_type() | sel_var(),
-%%                   l | g | sel_var(),
-%%                   any()}.
-%% @type pidpat() = pid() | sel_var().
-%% @type sel_var() = DollarVar | '_'.
-%% @type sel_pattern() = [{headpat(), Guards, Prod}].
-%% @type key()   = {type(), scope(), any()}.
-%%
-%% update_counter increment
-%% @type ctr_incr()   = integer().
-%% @type ctr_thr()    = integer().
-%% @type ctr_setval() = integer().
-%% @type ctr_update()  = ctr_incr()
-%% 		     | {ctr_incr(), ctr_thr(), ctr_setval()}.
-%% @type increment() = ctr_incr() | ctr_update() | [ctr_update()].
-
 -module(gproc).
 -behaviour(gen_server).
 
 -export([start_link/0,
-         reg/1, reg/2, unreg/1, set_attributes/2,
+         reg/1, reg/2, reg/3, 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,
+	 reg_shared/1, reg_shared/2, reg_shared/3, unreg_shared/1,
 	 set_attributes_shared/2, set_value_shared/2,
          mreg/3,
          munreg/3,
@@ -161,6 +131,40 @@
 -include("gproc_int.hrl").
 -include("gproc.hrl").
 
+-export_type([scope/0, type/0, key/0,
+              context/0, sel_pattern/0, sel_scope/0, sel_context/0,
+              reg_id/0, unique_id/0, monitor_type/0]).
+
+-type type()     :: n | p | c | a | r | rc.
+-type scope()    :: l | g.
+-type context()  :: {scope(),type()} | type().
+-type sel_type() :: type()
+                    | names | props | counters | aggr_counters
+                    | resources | resource_counters.
+
+-type sel_var() :: '_' | atom().
+-type keypat()  :: {sel_type() | sel_var(), l | g | sel_var(), any()}.
+-type pidpat()  :: pid() | sel_var().
+-type headpat() :: {keypat(), pidpat(), any()}.
+-type key()     :: {type(), scope(), any()}.
+
+-type sel_pattern() :: [{headpat(), list(), list()}].
+
+-type reg_id() :: {type(), scope(), any()}.
+-type unique_id() :: {n | a, scope(), any()}.
+-type monitor_type() :: info | standby | follow.
+-type sel_scope() :: scope | all | global | local.
+-type sel_context() :: {scope(), type()} | type().
+
+%% update_counter increment
+-type ctr_incr()   :: integer().
+-type ctr_thr()    :: integer().
+-type ctr_setval() :: integer().
+-type ctr_update()  :: ctr_incr()
+		     | {ctr_incr(), ctr_thr(), ctr_setval()}.
+-type increment() :: ctr_incr() | ctr_update() | [ctr_update()].
+
+
 -define(SERVER, ?MODULE).
 %%-define(l, l(?LINE)). % when activated, calls a traceable empty function
 -define(l, ignore).
@@ -196,7 +200,8 @@ start_link() ->
 %% @doc Registers a local (unique) name. @equiv reg({n,l,Name})
 %% @end
 %%
-add_local_name(Name)  -> ?CATCH_GPROC_ERROR(reg1({n,l,Name}, undefined), [Name]).
+add_local_name(Name)  ->
+    ?CATCH_GPROC_ERROR(reg1({n,l,Name}, undefined, []), [Name]).
 
 
 %% spec(Name::any()) -> true
@@ -204,7 +209,8 @@ add_local_name(Name)  -> ?CATCH_GPROC_ERROR(reg1({n,l,Name}, undefined), [Name])
 %% @doc Registers a global (unique) name. @equiv reg({n,g,Name})
 %% @end
 %%
-add_global_name(Name) -> ?CATCH_GPROC_ERROR(reg1({n,g,Name}, undefined), [Name]).
+add_global_name(Name) ->
+    ?CATCH_GPROC_ERROR(reg1({n,g,Name}, undefined, []), [Name]).
 
 
 %% spec(Name::any(), Value::any()) -> true
@@ -213,7 +219,7 @@ add_global_name(Name) -> ?CATCH_GPROC_ERROR(reg1({n,g,Name}, undefined), [Name])
 %% @end
 %%
 add_local_property(Name , Value) ->
-    ?CATCH_GPROC_ERROR(reg1({p,l,Name}, Value), [Name, Value]).
+    ?CATCH_GPROC_ERROR(reg1({p,l,Name}, Value, []), [Name, Value]).
 
 %% spec(Name::any(), Value::any()) -> true
 %%
@@ -221,7 +227,7 @@ add_local_property(Name , Value) ->
 %% @end
 %%
 add_global_property(Name, Value) ->
-    ?CATCH_GPROC_ERROR(reg1({p,g,Name}, Value), [Name, Value]).
+    ?CATCH_GPROC_ERROR(reg1({p,g,Name}, Value, []), [Name, Value]).
 
 %% spec(Name::any(), Initial::integer()) -> true
 %%
@@ -229,7 +235,7 @@ add_global_property(Name, Value) ->
 %% @end
 %%
 add_local_counter(Name, Initial) when is_integer(Initial) ->
-    ?CATCH_GPROC_ERROR(reg1({c,l,Name}, Initial), [Name, Initial]).
+    ?CATCH_GPROC_ERROR(reg1({c,l,Name}, Initial, []), [Name, Initial]).
 
 
 %% spec(Name::any(), Initial::integer()) -> true
@@ -248,7 +254,7 @@ add_shared_local_counter(Name, Initial) when is_integer(Initial) ->
 %% @end
 %%
 add_global_counter(Name, Initial) when is_integer(Initial) ->
-    ?CATCH_GPROC_ERROR(reg1({c,g,Name}, Initial), [Name, Initial]).
+    ?CATCH_GPROC_ERROR(reg1({c,g,Name}, Initial, []), [Name, Initial]).
 
 %% spec(Name::any()) -> true
 %%
@@ -538,7 +544,7 @@ lookup_env(Scope, App, Key, P) ->
 
 cache_env(Scope, App, Key, Value) ->
     ?CATCH_GPROC_ERROR(
-       reg1({p, Scope, {gproc_env, App, Key}}, Value),
+       reg1({p, Scope, {gproc_env, App, Key}}, Value, []),
        [Scope,App,Key,Value]).
 
 update_cached_env(Scope, App, Key, Value) ->
@@ -604,13 +610,13 @@ is_string(S) ->
 %% @spec reg(Key::key()) -> true
 %%
 %% @doc
-%% @equiv reg(Key, default(Key))
+%% @equiv reg(Key, default(Key), [])
 %% @end
 reg(Key) ->
     ?CATCH_GPROC_ERROR(reg1(Key), [Key]).
 
 reg1(Key) ->
-    reg1(Key, default(Key)).
+    reg1(Key, default(Key), []).
 
 %% @spec reg_or_locate(Key::key()) -> {pid(), NewValue}
 %%
@@ -635,7 +641,7 @@ await(Key) ->
 %% @spec await(Key::key(), Timeout) -> {pid(),Value}
 %%   Timeout = integer() | infinity
 %%
-%% @doc Wait for a local name to be registered.
+%% @doc Wait for a name or aggregated counter to be registered.
 %% The function raises an exception if the timeout expires. Timeout must be
 %% either an interger &gt; 0 or 'infinity'.
 %% A small optimization: we first perform a lookup, to see if the name
@@ -650,7 +656,7 @@ await(Key, Timeout) ->
 %% @spec await(Node::node(), Key::key(), Timeout) -> {pid(),Value}
 %%   Timeout = integer() | infinity
 %%
-%% @doc Wait for a local name to be registered on `Node'.
+%% @doc Wait for a name or aggregated counter to be registered on `Node'.
 %% This function works exactly like {@link 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
@@ -662,11 +668,11 @@ await(Node, Key, Timeout) when Node == node() ->
 await(Node, Key, Timeout) when is_atom(Node) ->
     ?CATCH_GPROC_ERROR(await1(Node, Key, Timeout), [Node, Key, Timeout]).
 
-await1({n,g,_} = Key, Timeout) ->
+await1({T,g,_} = Key, Timeout) when T=:=n; T=:=a; T=:=rc ->
     ?CHK_DIST,
     request_wait(Key, Timeout);
-await1({n,l,_} = Key, Timeout) ->
-    case ets:lookup(?TAB, {Key, n}) of
+await1({T,l,_} = Key, Timeout) when T=:=n; T=:=a; T=:=rc ->
+    case ets:lookup(?TAB, {Key, T}) of
         [{_, Pid, Value}] ->
 	    case is_process_alive(Pid) of
 		true ->
@@ -685,12 +691,12 @@ await1({n,l,_} = Key, Timeout) ->
             request_wait(Key, Timeout)
     end;
 await1(_, _) ->
-    throw(badarg).
+    ?THROW_GPROC_ERROR(badarg).
 
 await1(N, {n,l,_} = Key, Timeout) when is_atom(N) ->
     request_wait(N, Key, Timeout);
 await1(_, _, _) ->
-    throw(badarg).
+    ?THROW_GPROC_ERROR(badarg).
 
 
 request_wait({_,g,_} = Key, Timeout) ->
@@ -698,7 +704,7 @@ request_wait({_,g,_} = Key, Timeout) ->
 request_wait(Key, Timeout) ->
     request_wait(node(), Key, Timeout).
 
-request_wait(N, {n,C,_} = Key, Timeout) when C==l; C==g ->
+request_wait(N, {_,C,_} = Key, Timeout) when C==l; C==g ->
     TRef = case Timeout of
                infinity -> no_timer;
                T when is_integer(T), T > 0 ->
@@ -741,7 +747,7 @@ request_wait(N, {n,C,_} = Key, Timeout) when C==l; C==g ->
 wide_await(Nodes, Key, Timeout) ->
     ?CATCH_GPROC_ERROR(wide_await1(Nodes, Key, Timeout), [Nodes, Key, Timeout]).
 
-wide_await1(Nodes, {n,l,_} = Key, Timeout) ->
+wide_await1(Nodes, {T,l,_} = Key, Timeout) when T=:=n; T=:=a ->
     {_, Ref} = spawn_monitor(fun() ->
 				     wide_request_wait(Nodes, Key, Timeout)
 			     end),
@@ -758,7 +764,7 @@ wide_await1(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 
-wide_request_wait(Nodes, {n,l,_} = Key, Timeout) ->
+wide_request_wait(Nodes, {Tk,l,_} = Key, Timeout) when Tk=:=n; Tk=:=a ->
         TRef = case Timeout of
                infinity -> no_timer;
                T when is_integer(T), T > 0 ->
@@ -771,8 +777,8 @@ wide_request_wait(Nodes, {n,l,_} = Key, Timeout) ->
 	     fun(Node) ->
 		     S = {?MODULE, Node},
 		     Ref = erlang:monitor(process, S),
-		     catch erlang:send(S, {'$gen_call', {self(), Ref}, Req},
-				       [noconnect]),
+		     ?MAY_FAIL(erlang:send(S, {'$gen_call', {self(), Ref}, Req},
+                                           [noconnect])),
 		     {Node, Ref}
 	     end, Nodes),
     collect_replies(Refs, Key, TRef).
@@ -798,7 +804,7 @@ collect_replies(Refs, Key, TRef) ->
 
 %% @spec nb_wait(Key::key()) -> Ref
 %%
-%% @doc Wait for a local name to be registered.
+%% @doc Wait for a name or aggregated counter to be registered.
 %% The caller can expect to receive a message,
 %% {gproc, Ref, registered, {Key, Pid, Value}}, once the name is registered.
 %% @end
@@ -808,23 +814,25 @@ nb_wait(Key) ->
 
 %% @spec nb_wait(Node::node(), Key::key()) -> Ref
 %%
-%% @doc Wait for a local name to be registered on `Node'.
+%% @doc Wait for a name or aggregated counter to be registered on `Node'.
 %% The caller can expect to receive a message,
 %% {gproc, Ref, registered, {Key, Pid, Value}}, once the name is registered.
 %% @end
 %%
-nb_wait(Node, {n,l,_} = Key) when is_atom(Node) ->
+nb_wait(Node, Key) ->
     ?CATCH_GPROC_ERROR(nb_wait1(Node, Key), [Node, Key]).
 
-nb_wait1({n,g,_} = Key) ->
+nb_wait1({T,g,_} = Key) when T=:=n; T=:=a; T=:=rc ->
     ?CHK_DIST,
     call({await, Key, self()}, g);
-nb_wait1({n,l,_} = Key) ->
+nb_wait1({T,l,_} = Key) when T=:=n; T=:=a; T=:=rc ->
     call({await, Key, self()}, l);
 nb_wait1(_) ->
     ?THROW_GPROC_ERROR(badarg).
 
-nb_wait1(Node, {n,l,_} = Key) when is_atom(Node) ->
+nb_wait1(Node, {T,l,_} = Key) when is_atom(Node), T=:=n;
+                                   is_atom(Node), T=:=a;
+                                   is_atom(Node), T=:=rc ->
     call(Node, {await, Key, self()}, l).
 
 
@@ -945,21 +953,65 @@ demonitor1(_, _) ->
 %%
 %%
 reg(Key, Value) ->
-    ?CATCH_GPROC_ERROR(reg1(Key, Value), [Key, Value]).
+    ?CATCH_GPROC_ERROR(reg1(Key, Value, []), [Key, Value]).
+
+%% @spec reg(Key::key(), Value, Attrs::[{atom(),any()}]) -> true
+%%
+%% @doc Register a name or property for the current process
+%% `Attrs' (default: `[]') can be inspected using {@link get_attribute/2}.
+%%
+%% The structure of `Key' is `{Type, Context, Name}', where:
+%%
+%% * `Context :: l | g' - `l' means 'local' context; `g' means 'global'
+%% * `Type :: p | n | c | a | r | rc' specifies the type of entry
+%%
+%% The semantics of the different types:
+%%
+%% * `p' - 'property', is non-unique, i.e. different processes can each
+%%    register a property with the same name.
+%% * `n' - 'name, is unique within the given context (local or global).
+%% * `c' - 'counter', is similar to a property, but has a numeric value
+%%    and behaves roughly as an ets counter (see {@link update_counter/2}.)
+%% * `a' - 'aggregated counter', is automatically updated by gproc, and
+%%    reflects the sum of all counter objects with the same name in the given
+%%    scope. The initial value for an aggregated counter must be `undefined'.
+%% * `r' - 'resource property', behaves like a property, but can be tracked
+%%    with a 'resource counter'.
+%% * `rc' - 'resource counter', tracks the number of resource properties
+%%    with the same name. When the resource count reaches `0', any triggers
+%%    specified using an `on_zero' attribute may be executed (see below).
+%%
+%% On-zero triggers:
+%%
+%% `Msg = {gproc, resource_on_zero, Context, Name, Pid}'
+%%
+%% * `{send, Key}' - run `gproc:send(Key, Msg)'
+%% * `{bcast, Key}' - run `gproc:bcast(Key, Msg)'
+%% * `publish' - run
+%%  `gproc_ps:publish(Context, gproc_resource_on_zero, {Context, Name, Pid})'
+%% * `{unreg_shared, Type, Name}' - unregister the shared key
+%%  `{Type, Context, Name}'
+%% @end
+reg(Key, Value, Attrs) ->
+    ?CATCH_GPROC_ERROR(reg1(Key, Value, Attrs), [Key, Value, Attrs]).
 
-reg1({_,g,_} = Key, Value) ->
+reg1({T,g,_} = Key, Value, As) when T==p; T==a; T==c; T==n; T==r; T==rc ->
     %% anything global
     ?CHK_DIST,
-    gproc_dist:reg(Key, Value);
-reg1({p,l,_} = Key, Value) ->
-    local_reg(Key, Value);
-reg1({a,l,_} = Key, undefined) ->
-    call({reg, Key, undefined});
-reg1({c,l,_} = Key, Value) when is_integer(Value) ->
-    call({reg, Key, Value});
-reg1({n,l,_} = Key, Value) ->
-    call({reg, Key, Value});
-reg1(_, _) ->
+    gproc_dist:reg(Key, Value, As);
+reg1({p,l,_} = Key, Value, As) ->
+    local_reg(Key, Value, As);
+reg1({a,l,_} = Key, undefined, As) ->
+    call({reg, Key, undefined, As});
+reg1({c,l,_} = Key, Value, As) when is_integer(Value) ->
+    call({reg, Key, Value, As});
+reg1({n,l,_} = Key, Value, As) ->
+    call({reg, Key, Value, As});
+reg1({r,l,_} = Key, Value, As) ->
+    call({reg, Key, Value, As});
+reg1({rc,l,_} = Key, Value, As) ->
+    call({reg, Key, Value, As});
+reg1(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 %% @spec reg_or_locate(Key::key(), Value) -> {pid(), NewValue}
@@ -991,7 +1043,7 @@ reg_or_locate({n,_,_} = Key, Value, F) when is_function(F, 0) ->
 reg_or_locate1({_,g,_} = Key, Value, P) ->
     ?CHK_DIST,
     gproc_dist:reg_or_locate(Key, Value, P);
-reg_or_locate1({n,l,_} = Key, Value, P) ->
+reg_or_locate1({T,l,_} = Key, Value, P) when T==n; T==a; T==rc ->
     call({reg_or_locate, Key, Value, P});
 reg_or_locate1(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
@@ -1026,20 +1078,25 @@ reg_shared1({T,_,_} = Key) when T==a; T==p; T==c ->
 %% @end
 %%
 reg_shared(Key, Value) ->
-    ?CATCH_GPROC_ERROR(reg_shared1(Key, Value), [Key, Value]).
+    ?CATCH_GPROC_ERROR(reg_shared1(Key, Value, []), [Key, Value]).
+
+reg_shared(Key, Value, Attrs) when is_list(Attrs) ->
+    ?CATCH_GPROC_ERROR(reg_shared1(Key, Value, Attrs), [Key, Value, Attrs]).
 
 %% @private
-reg_shared1({_,g,_} = Key, Value) ->
+reg_shared1({_,g,_} = Key, Value, As) ->
     %% anything global
     ?CHK_DIST,
-    gproc_dist:reg_shared(Key, Value);
-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(_, _) ->
+    gproc_dist:reg_shared(Key, Value, As);
+reg_shared1({a,l,_} = Key, undefined, As) ->
+    call({reg_shared, Key, undefined, As});
+reg_shared1({c,l,_} = Key, Value, As) when is_integer(Value) ->
+    call({reg_shared, Key, Value, As});
+reg_shared1({p,l,_} = Key, Value, As) ->
+    call({reg_shared, Key, Value, As});
+reg_shared1({rc,l,_} = Key, undefined, As) ->
+    call({reg_shared, Key, undefined, As});
+reg_shared1(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 %% @spec mreg(type(), scope(), [{Key::any(), Value::any()}]) -> true
@@ -1116,8 +1173,8 @@ unreg1(Key) ->
         {_, g, _} ->
             ?CHK_DIST,
             gproc_dist:unreg(Key);
-        {T, l, _} when T == n;
-                       T == a -> call({unreg, Key});
+        {T, l, _} when T == n; T == a; T == r; T == rc ->
+            call({unreg, Key});
         {_, l, _} ->
             case ets:member(?TAB, {Key,self()}) of
                 true ->
@@ -1165,7 +1222,8 @@ unreg_shared1(Key) ->
             gproc_dist:unreg_shared(Key);
         {T, l, _} when T == c;
                        T == a;
-		       T == p -> call({unreg_shared, Key});
+		       T == p;
+                       T == rc -> call({unreg_shared, Key});
         _ ->
 	    ?THROW_GPROC_ERROR(badarg)
     end.
@@ -1235,7 +1293,7 @@ select({?TAB, _, _, _, _, _, _, _} = Continuation) ->
 select(Pat) ->
     select(all, Pat).
 
-%% @spec (Context::context(), Pat::sel_pattern()) -> [{Key, Pid, Value}]
+%% @spec (Context::sel_context(), Pat::sel_pattern()) -> [{Key, Pid, Value}]
 %%
 %% @doc Perform a select operation with limited context on the process registry
 %%
@@ -1285,10 +1343,15 @@ select_count(Context, Pat) ->
 %%% Local properties can be registered in the local process, since
 %%% no other process can interfere.
 %%%
-local_reg(Key, Value) ->
+local_reg({_,Scope,_} = Key, Value, As) ->
     case gproc_lib:insert_reg(Key, Value, self(), l) of
         false -> ?THROW_GPROC_ERROR(badarg);
-        true  -> monitor_me()
+        true  ->
+            monitor_me(),
+            if As =/= [] ->
+                    gproc_lib:insert_attr(Key, As, self(), Scope);
+               true -> true
+            end
     end.
 
 local_mreg(_, []) -> true;
@@ -1327,7 +1390,8 @@ set_value(Key, Value) ->
 %%
 set_value_shared({T,_,_} = Key, Value) when T == c;
 					    T == a;
-					    T == p->
+					    T == p;
+                                            T == r ->
     ?CATCH_GPROC_ERROR(set_value_shared1(Key, Value), [Key, Value]).
 
 set_value1({_,g,_} = Key, Value) ->
@@ -1386,7 +1450,7 @@ get_value(Key, Pid) ->
     ?CATCH_GPROC_ERROR(get_value1(Key, Pid), [Key, Pid]).
 
 get_value1({T,_,_} = Key, Pid) when is_pid(Pid) ->
-    if T==n orelse T==a ->
+    if T==n; T==a; T==rc ->
             case ets:lookup(?TAB, {Key, T}) of
                 [{_, P, Value}] when P == Pid -> Value;
                 _ -> ?THROW_GPROC_ERROR(badarg)
@@ -1394,11 +1458,13 @@ 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; T==p ->
+get_value1({T,_,_} = K, shared) when T==c; T==a; T==p; T==r ->
     Key = case T of
-	      c -> {K, shared};
-	      p -> {K, shared};
-	      a -> {K, a}
+	      c  -> {K, shared};
+	      p  -> {K, shared};
+              r  -> {K, shared};
+	      a  -> {K, a};
+              rc -> {K, rc}
 	  end,
     case ets:lookup(?TAB, Key) of
 	[{_, shared, Value}] -> Value;
@@ -1417,9 +1483,9 @@ get_value1(_, _) ->
 %% @end
 get_attribute(Key, A) ->
     Pid = case Key of
-	      {T,_,_} when T==n; T==a ->
+	      {T,_,_} when T==n; T==a; T==rc ->
 		  where(Key);
-	      {T,_,_} when T==p; T==c ->
+	      {T,_,_} when T==p; T==c; T==r ->
 		  self()
 	  end,
     ?CATCH_GPROC_ERROR(get_attribute1(Key, Pid, A), [Key, A]).
@@ -1507,7 +1573,7 @@ lookup_pid({_T,_,_} = Key) ->
 %% This function raises a `badarg' exception if `Key' is not registered.
 %% @end
 lookup_value({T,_,_} = Key) ->
-    if T==n orelse T==a ->
+    if T==n orelse T==a orelse T==rc ->
             ets:lookup_element(?TAB, {Key,T}, 3);
        true ->
             erlang:error(badarg)
@@ -1526,7 +1592,7 @@ where(Key) ->
     ?CATCH_GPROC_ERROR(where1(Key), [Key]).
 
 where1({T,_,_}=Key) ->
-    if T==n orelse T==a ->
+    if T==n orelse T==a orelse T==rc ->
             case ets:lookup(?TAB, {Key,T}) of
                 [{_, P, _Value}] ->
                     case my_is_process_alive(P) of
@@ -1558,7 +1624,7 @@ whereis_name(Key) ->
 %% @end
 %%
 lookup_pids({T,_,_} = Key) ->
-    L = if T==n orelse T==a ->
+    L = if T==n orelse T==a orelse T==rc ->
                 ets:select(?TAB, [{{{Key,T}, '$1', '_'},
 				   [{is_pid, '$1'}], ['$1']}]);
            true ->
@@ -1586,7 +1652,7 @@ my_is_process_alive(_) ->
 %% @end
 %%
 lookup_values({T,_,_} = Key) ->
-    L = if T==n orelse T==a ->
+    L = if T==n orelse T==a orelse T==rc ->
                 ets:select(?TAB, [{{{Key,T}, '$1', '$2'},[],[{{'$1','$2'}}]}]);
            true ->
                 ets:select(?TAB, [{{{Key,'_'}, '$1', '$2'},[],[{{'$1','$2'}}]}])
@@ -1793,14 +1859,14 @@ send(Key, Msg) ->
     ?CATCH_GPROC_ERROR(send1(Key, Msg), [Key, Msg]).
 
 send1({T,C,_} = Key, Msg) when C==l; C==g ->
-    if T == n orelse T == a ->
+    if T == n orelse T == a orelse T == rc ->
             case ets:lookup(?TAB, {Key, T}) of
                 [{_, Pid, _}] ->
                     Pid ! Msg;
                 _ ->
                     ?THROW_GPROC_ERROR(badarg)
             end;
-       T==p orelse T==c ->
+       T==p orelse T==c orelse T==r ->
             %% BUG - if the key part contains select wildcards, we may end up
             %% sending multiple messages to the same pid
             lists:foreach(fun(Pid) ->
@@ -1836,7 +1902,7 @@ bcast(Key, Msg) ->
 bcast(Ns, Key, Msg) ->
     ?CATCH_GPROC_ERROR(bcast1(Ns, Key, Msg), [Key, Msg]).
 
-bcast1(Ns, {T,l,_} = Key, Msg) when T==p; T==a; T==c; T==n ->
+bcast1(Ns, {T,l,_} = Key, Msg) when T==p; T==a; T==c; T==n; T==r; T==rc ->
     send1(Key, Msg),
     gen_server:abcast(Ns -- [node()], gproc_bcast, {send, Key, Msg}),
     Msg.
@@ -2056,10 +2122,14 @@ handle_cast({cancel_wait_or_monitor, Pid, {T,_,_} = Key}, S) ->
     {noreply, S}.
 
 %% @hidden
-handle_call({reg, {_T,l,_} = Key, Val}, {Pid,_}, S) ->
+handle_call({reg, {_T,l,_} = Key, Val, Attrs}, {Pid,_}, S) ->
     case try_insert_reg(Key, Val, Pid) of
         true ->
             _ = gproc_lib:ensure_monitor(Pid,l),
+            _ = if Attrs =/= [] ->
+                        gproc_lib:insert_attr(Key, Attrs, Pid, l);
+                   true -> true
+                end,
             {reply, true, S};
         false ->
             {reply, badarg, S}
@@ -2136,7 +2206,7 @@ handle_call({monitor, {T,l,_} = Key, Pid, Type}, _From, S)
 	end,
     {reply, Ref, S};
 handle_call({demonitor, {T,l,_} = Key, Ref, Pid}, _From, S)
-  when T==n; T==a ->
+  when T==n; T==a; T==rc ->
     _ = case where(Key) of
 	    undefined ->
 		ok;  % be nice
@@ -2150,10 +2220,13 @@ handle_call({demonitor, {T,l,_} = Key, Ref, Pid}, _From, S)
 		end
 	end,
     {reply, ok, S};
-handle_call({reg_shared, {_T,l,_} = Key, Val}, _From, S) ->
+handle_call({reg_shared, {_T,l,_} = Key, Val, Attrs}, _From, S) ->
     case try_insert_reg(Key, Val, shared) of
-    %% case try_insert_shared(Key, Val) of
 	true ->
+            _ = if Attrs =/= [] ->
+                        gproc_lib:insert_attr(Key, Attrs, shared, l);
+                   true -> true
+                end,
 	    {reply, true, S};
 	false ->
 	    {reply, badarg, S}
@@ -2309,7 +2382,6 @@ try_insert_reg({T,l,_} = Key, Val, Pid) ->
             true
     end.
 
-
 %% try_insert_shared({c,l,_} = Key, Val) ->
 %%     ets:insert_new(?TAB, [{{Key,shared}, shared, Val}, {{shared, Key}, []}]);
 %% try_insert_shared({a,l,_} = Key, Val) ->
@@ -2368,23 +2440,14 @@ process_is_down(Pid) when is_pid(Pid) ->
                       [{_, _, Value}] = ets:lookup(?TAB, Key),
                       ets:delete(?TAB, Key),
                       gproc_lib:update_aggr_counter(l, C, -Value);
+                 ({{r,l,Rsrc} = K, _}) ->
+                      Key = {K, Pid},
+                      ets:delete(?TAB, Key),
+                      gproc_lib:decrement_resource_count(l, Rsrc);
+                 ({{rc,l,_} = K, R}) ->
+                      remove_aggregate(rc, K, R, Pid);
                  ({{a,l,_} = K, R}) ->
-                      case ets:lookup(?TAB, {K,a}) of
-                          [{_, Pid, V}] ->
-                              ets:delete(?TAB, {K,a}),
-                              opt_notify(R, K, Pid, V);
-                          [{_, OtherPid, _}] when Pid =/= OtherPid ->
-                              case ets:lookup(?TAB, {OtherPid, K}) of
-                                  [{RK, Opts}] when is_list(Opts) ->
-                                      Opts1 = gproc_lib:remove_monitor_pid(
-                                                Opts, Pid),
-                                      ets:insert(?TAB, {RK, Opts1});
-                                  _ ->
-                                      true
-                              end;
-                          [] ->
-                              opt_notify(R, K, Pid, undefined)
-                      end;
+                      remove_aggregate(a, K, R, Pid);
                  ({{p,_,_} = K, _}) ->
                       ets:delete(?TAB, {K, Pid})
               end, Revs),
@@ -2393,6 +2456,24 @@ process_is_down(Pid) when is_pid(Pid) ->
             ok
     end.
 
+remove_aggregate(T, K, R, Pid) ->
+    case ets:lookup(?TAB, {K,T}) of
+        [{_, Pid, V}] ->
+            ets:delete(?TAB, {K,T}),
+            opt_notify(R, K, Pid, V);
+        [{_, OtherPid, _}] when Pid =/= OtherPid ->
+            case ets:lookup(?TAB, {OtherPid, K}) of
+                [{RK, Opts}] when is_list(Opts) ->
+                    Opts1 = gproc_lib:remove_monitor_pid(
+                              Opts, Pid),
+                    ets:insert(?TAB, {RK, Opts1});
+                _ ->
+                    true
+            end;
+        [] ->
+            opt_notify(R, K, Pid, undefined)
+    end.
+
 opt_notify(r, _, _, _) ->
     ok;
 opt_notify(Opts, {T,_,_} = Key, Pid, Value) ->
@@ -2441,7 +2522,7 @@ pick_standby([]) ->
 
 
 
-do_give_away({T,l,_} = K, To, Pid) when T==n; T==a ->
+do_give_away({T,l,_} = K, To, Pid) when T==n; T==a; T==rc ->
     Key = {K, T},
     case ets:lookup(?TAB, Key) of
         [{_, Pid, Value}] ->
@@ -2464,7 +2545,7 @@ do_give_away({T,l,_} = K, To, Pid) when T==n; T==a ->
         _ ->
             badarg
     end;
-do_give_away({T,l,_} = K, To, Pid) when T==c; T==p ->
+do_give_away({T,l,_} = K, To, Pid) when T==c; T==p; T==r ->
     Key = {K, Pid},
     case ets:lookup(?TAB, Key) of
         [{_, Pid, Value}] ->
@@ -2493,7 +2574,7 @@ do_give_away({T,l,_} = K, To, Pid) when T==c; T==p ->
 
 pid_to_give_away_to(P) when is_pid(P), node(P) == node() ->
     P;
-pid_to_give_away_to({T,l,_} = Key) when T==n; T==a ->
+pid_to_give_away_to({T,l,_} = Key) when T==n; T==a; T==rc ->
     case ets:lookup(?TAB, {Key, T}) of
         [{_, Pid, _}] ->
             Pid;
@@ -2583,7 +2664,7 @@ headpat(T, C, V1,V2,V3) ->
     Rf = fun(Pos) ->
                  {element,Pos,{element,1,{element,1,'$_'}}}
          end,
-    K2 = if T==n orelse T==a -> T;
+    K2 = if T==n orelse T==a orelse T==rc -> T;
             true -> '_'
          end,
     {Kp,Vars} = case V1 of
@@ -2632,8 +2713,10 @@ type(all)   -> '_';
 type(T) when T==n; T==p; T==c; T==a -> T;
 type(names) -> n;
 type(props) -> p;
+type(resources) -> r;
 type(counters) -> c;
-type(aggr_counters) -> a.
+type(aggr_counters) -> a;
+type(resource_counters) -> rc.
 
 rev_keypat(Context) ->
     {S,T} = get_s_t(Context),

+ 3 - 1
src/gproc_bcast.erl

@@ -35,6 +35,8 @@
 	 terminate/2,
 	 code_change/3]).
 
+-include("gproc_int.hrl").
+
 start_link() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
 
@@ -45,7 +47,7 @@ handle_call(_, _, S) ->
     {reply, {error, unknown_call}, S}.
 
 handle_cast({send, Key, Msg}, S) ->
-    catch gproc:send(Key, Msg),
+    ?MAY_FAIL(gproc:send(Key, Msg)),
     {noreply, S};
 handle_cast(_, S) ->
     {noreply, S}.

+ 48 - 28
src/gproc_dist.erl

@@ -23,9 +23,9 @@
 -behaviour(gen_leader).
 
 -export([start_link/0, start_link/1,
-         reg/1, reg/2, unreg/1,
+         reg/1, reg/3, unreg/1,
 	 reg_or_locate/3,
-	 reg_shared/2, unreg_shared/1,
+	 reg_shared/3, unreg_shared/1,
          monitor/2,
          demonitor/2,
 	 set_attributes/2,
@@ -91,7 +91,7 @@ start_link({Nodes, Opts}) ->
 %% {@see gproc:reg/1}
 %%
 reg(Key) ->
-    reg(Key, gproc:default(Key)).
+    reg(Key, gproc:default(Key), []).
 
 %% {@see gproc:reg_or_locate/2}
 %%
@@ -117,15 +117,15 @@ reg_or_locate(_, _, _) ->
 %%%          | a  - aggregated counter
 %%%    Scope = l | g (global or local)
 %%% @end
-reg({_,g,_} = Key, Value) ->
+reg({_,g,_} = Key, Value, Attrs) ->
     %% anything global
-    leader_call({reg, Key, Value, self()});
-reg(_, _) ->
+    leader_call({reg, Key, Value, self(), Attrs});
+reg(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
-reg_shared({_,g,_} = Key, Value) ->
-    leader_call({reg, Key, Value, shared});
-reg_shared(_, _) ->
+reg_shared({_,g,_} = Key, Value, Attrs) ->
+    leader_call({reg, Key, Value, shared, Attrs});
+reg_shared(_, _, _) ->
     ?THROW_GPROC_ERROR(badarg).
 
 monitor({_,g,_} = Key, Type) when Type==info;
@@ -255,6 +255,8 @@ handle_info({'DOWN', _MRef, process, Pid, _}, S) ->
     ets:delete(?TAB, {Pid, g}),
     leader_cast({pid_is_DOWN, Pid}),
     {ok, S};
+handle_info({gproc_unreg, Objs}, S) ->
+    {ok, [{delete, Objs}], S};
 handle_info(_, S) ->
     {ok, S}.
 
@@ -332,23 +334,17 @@ handle_leader_call(sync, From, #state{sync_requests = SReqs} = S, E) ->
             GenLeader:broadcast({from_leader, {sync, From}}, Alive, E),
             {noreply, S#state{sync_requests = [{From, Alive}|SReqs]}}
     end;
-handle_leader_call({reg, {_C,g,_Name} = K, Value, Pid}, _From, S, _E) ->
+handle_leader_call({reg, {_C,g,_Name} = K, Value, Pid, As}, _From, S, _E) ->
     case gproc_lib:insert_reg(K, Value, Pid, g) of
         false ->
             {reply, badarg, S};
         true ->
             _ = gproc_lib:ensure_monitor(Pid,g),
+            _ = if As =/= [] ->
+                        gproc_lib:insert_attr(K, As, Pid, g);
+                   true -> []
+                end,
 	    Vals = mk_broadcast_insert_vals([{K, Pid, Value}]),
-            %% Vals =
-            %%     if C == a ->
-            %%             ets:lookup(?TAB, {K,a});
-            %%        C == c ->
-            %%             [{{K,Pid},Pid,Value} | ets:lookup(?TAB,{{a,g,Name},a})];
-            %%        C == n ->
-            %%             [{{K,n},Pid,Value}];
-            %%        true ->
-            %%             [{{K,Pid},Pid,Value}]
-            %%     end,
             {reply, true, [{insert, Vals}], S}
     end;
 handle_leader_call({monitor, {T,g,_} = K, MPid, Type}, _From, S, _E) when T==n;
@@ -484,7 +480,7 @@ handle_leader_call({reset_counter, {c,g,_Ctr} = Key, Pid}, _From, S, _E) ->
 	    {reply, badarg, S}
     end;
 handle_leader_call({unreg, {T,g,Name} = K, Pid}, _From, S, _E) ->
-    Key = if T == n; T == a -> {K,T};
+    Key = if T == n; T == a; T == rc -> {K,T};
              true -> {K, Pid}
           end,
     case ets:member(?TAB, Key) of
@@ -499,6 +495,14 @@ handle_leader_call({unreg, {T,g,Name} = K, Pid}, _From, S, _E) ->
                         [] ->
                             {reply, true, [{delete, [Key, {Pid,K}]}], S}
                     end;
+               T == r ->
+                    case ets:lookup(?TAB, {{rc,g,Name},rc}) of
+                        [RC] ->
+                            {reply, true, [{delete,[Key, {Pid,K}]},
+                                           {insert, [RC]}], S};
+                        [] ->
+                            {reply, true, [{delete, [Key, {Pid, K}]}], S}
+                    end;
                true ->
                     {reply, true, [{notify, [{K, Pid, unreg}]},
                                    {delete, [Key, {Pid,K}]}], S}
@@ -507,7 +511,7 @@ handle_leader_call({unreg, {T,g,Name} = K, Pid}, _From, S, _E) ->
             {reply, badarg, S}
     end;
 handle_leader_call({give_away, {T,g,_} = K, To, Pid}, _From, S, _E)
-  when T == a; T == n ->
+  when T == a; T == n; T == rc ->
     Key = {K, T},
     case ets:lookup(?TAB, Key) of
         [{_, Pid, Value}] ->
@@ -537,7 +541,7 @@ handle_leader_call({give_away, {T,g,_} = K, To, Pid}, _From, S, _E)
             {reply, badarg, S}
     end;
 handle_leader_call({mreg, T, g, L, Pid}, _From, S, _E) ->
-    if T==p; T==n ->
+    if T==p; T==n; T==r ->
             try gproc_lib:insert_many(T, g, L, Pid) of
                 {true,Objs} -> {reply, true, [{insert,Objs}], S};
                 false       -> {reply, badarg, S}
@@ -655,11 +659,14 @@ handle_leader_cast({pid_is_DOWN, Pid}, S, _E) ->
 mk_broadcast_insert_vals(Objs) ->
     lists:flatmap(
       fun({{C, g, Name} = K, Pid, Value}) ->
-	      if C == a ->
-		      ets:lookup(?TAB, {K,a}) ++ ets:lookup(?TAB, {Pid,K});
+	      if C == a; C == rc ->
+		      ets:lookup(?TAB, {K,C}) ++ ets:lookup(?TAB, {Pid,K});
 		 C == c ->
 		      [{{K,Pid},Pid,Value} | ets:lookup(?TAB,{{a,g,Name},a})]
 			  ++ ets:lookup(?TAB, {Pid,K});
+                 C == r ->
+                      [{{K,Pid},Pid,Value} | ets:lookup(?TAB,{{rc,g,Name},rc})]
+                          ++ ets:lookup(?TAB, {Pid, K});
 		 C == n ->
 		      [{{K,n},Pid,Value}| ets:lookup(?TAB, {Pid,K})];
 		 true ->
@@ -671,7 +678,7 @@ mk_broadcast_insert_vals(Objs) ->
 process_globals(Globals) ->
     {Modified, Notifications} =
         lists:foldl(
-          fun({{T,_,_} = Key, Pid}, A) when T==n; T==a ->
+          fun({{T,_,_} = Key, Pid}, A) when T==n; T==a; T==rc ->
                   case ets:lookup(?TAB, {Pid,Key}) of
                       [{_, Opts}] when is_list(Opts) ->
                           maybe_failover(Key, Pid, Opts, A);
@@ -683,6 +690,8 @@ process_globals(Globals) ->
                             c ->
                                 Incr = ets:lookup_element(?TAB, {Key,Pid}, 3),
                                 update_aggr_counter(Key, -Incr) ++ MA;
+                            r ->
+                                decrement_resource_count(Key, []) ++ MA;
                             _ ->
                                MA
                         end,
@@ -813,7 +822,7 @@ do_notify([]) ->
     ok.
 
 
-ets_key({T,_,_} = K, _) when T==n; T==a ->
+ets_key({T,_,_} = K, _) when T==n; T==a; T==rc ->
     {K, T};
 ets_key(K, Pid) ->
     {K, Pid}.
@@ -951,6 +960,17 @@ update_aggr_counter({c,g,Ctr}, Incr, Acc) ->
             [New|Acc]
     end.
 
+decrement_resource_count({r,g,Rsrc}, Acc) ->
+    Key = {{rc,g,Rsrc},rc},
+    case ets:member(?TAB, Key) of
+        false ->
+            Acc;
+        true ->
+            %% Call the lib function, which might trigger events
+            gproc_lib:decrement_resource_count(g, Rsrc),
+            ets:lookup(?TAB, Key) ++ Acc
+    end.
+
 pid_to_give_away_to(P) when is_pid(P) ->
     P;
 pid_to_give_away_to({T,g,_} = Key) when T==n; T==a ->
@@ -962,7 +982,7 @@ pid_to_give_away_to({T,g,_} = Key) when T==n; T==a ->
     end.
 
 insert_reg([{_, Waiters}], K, Val, Pid, Event) ->
-    gproc_lib:insert_reg(K, Val, Pid, g, []),
+    gproc_lib:insert_reg(K, Val, Pid, g),
     tell_waiters(Waiters, K, Pid, Val, Event).
 
 tell_waiters([{P,R}|T], K, Pid, V, Event) ->

+ 4 - 0
src/gproc_int.hrl

@@ -25,3 +25,7 @@
 	end).
 
 -define(THROW_GPROC_ERROR(E), throw({gproc_error, E})).
+
+%% Used to wrap operations that may fail, but we ignore the exception.
+%% Use instead of catch, to avoid building a stacktrace unnecessarily.
+-define(MAY_FAIL(Expr), try (Expr) catch _:_ -> '$caught_exception' end).

+ 125 - 35
src/gproc_lib.erl

@@ -42,6 +42,7 @@
 	 remove_wait/4,
          update_aggr_counter/3,
          update_counter/3,
+         decrement_resource_count/2,
 	 valid_opts/2]).
 
 -export([dbg/1]).
@@ -62,30 +63,21 @@ dbg(Mods) ->
 %% Pid around as payload as well. This is a bit redundant, but
 %% symmetric.
 %%
--spec insert_reg(key(), any(), pid() | shared, scope()) -> boolean().
+-spec insert_reg(gproc:key(), any(), pid() | shared, gproc:scope()) -> boolean().
 insert_reg(K, Value, Pid, Scope) ->
     insert_reg(K, Value, Pid, Scope, registered).
 
-insert_reg({T,_,Name} = K, Value, Pid, Scope, Event) when T==a; T==n ->
-    MaybeScan = fun() ->
-                        if T==a ->
-                                Initial = scan_existing_counters(Scope, Name),
-                                ets:insert(?TAB, {{K,a}, Pid, Initial});
-                           true ->
-                                true
-                        end
-                end,
-    case ets:insert_new(?TAB, {{K,T}, Pid, Value}) of
-        true ->
-            _ = ets:insert_new(?TAB, {{Pid,K}, []}),
-            MaybeScan();
-        false ->
-            if T==n; T==a ->
-                    maybe_waiters(K, Pid, Value, T, Event);
-               true ->
-                    false
-            end
-    end;
+insert_reg({T,_,Name} = K, Value, Pid, Scope, Event) when T==a; T==n; T==rc ->
+    Res = case ets:insert_new(?TAB, {{K,T}, Pid, Value}) of
+              true ->
+                  %% Use insert_new to avoid overwriting existing entry
+                  _ = ets:insert_new(?TAB, {{Pid,K}, []}),
+                  true;
+              false ->
+                  maybe_waiters(K, Pid, Value, T, Event)
+          end,
+    maybe_scan(T, Pid, Scope, Name, K),
+    Res;
 insert_reg({p,Scope,_} = K, Value, shared, Scope, _E)
   when Scope == g; Scope == l ->
     %% shared properties are unique
@@ -103,12 +95,31 @@ insert_reg({c,Scope,Ctr} = Key, Value, Pid, Scope, _E) when Scope==l; Scope==g -
             ignore
     end,
     Res;
+insert_reg({r,Scope,R} = Key, Value, Pid, Scope, _E) when Scope==l; Scope==g ->
+    K = {Key, Pid},
+    Kr = {Pid, Key},
+    Res = ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, [{initial, Value}]}]),
+    case Res of
+        true ->
+            update_resource_count(Scope, R, 1);
+        false ->
+            ignore
+    end,
+    Res;
 insert_reg({_,_,_} = Key, Value, Pid, _Scope, _E) when is_pid(Pid) ->
     %% Non-unique keys; store Pid in the key part
     K = {Key, Pid},
     Kr = {Pid, Key},
     ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, []}]).
 
+maybe_scan(a, Pid, Scope, Name, K) ->
+    Initial = scan_existing_counters(Scope, Name),
+    ets:insert(?TAB, {{K,a}, Pid, Initial});
+maybe_scan(rc, Pid, Scope, Name, K) ->
+    Initial = scan_existing_resources(Scope, Name),
+    ets:insert(?TAB, {{K,rc}, Pid, Initial});
+maybe_scan(_, _, _, _, _) ->
+    true.
 
 insert_attr({_,Scope,_} = Key, Attrs, Pid, Scope) when Scope==l;
 						       Scope==g ->
@@ -125,7 +136,25 @@ insert_attr({_,Scope,_} = Key, Attrs, Pid, Scope) when Scope==l;
 	    false
     end.
 
--spec insert_many(type(), scope(), [{key(),any()}], pid()) ->
+get_attr(Attr, Pid, {_,_,_} = Key, Default) ->
+    case ets:lookup(?TAB, {Pid, Key}) of
+        [{_, Opts}] when is_list(Opts) ->
+            case lists:keyfind(attrs, 1, Opts) of
+                {_, Attrs} ->
+                    case lists:keyfind(Attr, 1, Attrs) of
+                        {_, Val} ->
+                            Val;
+                        _ ->
+                            Default
+                    end;
+                _ ->
+                    Default
+            end;
+        _ ->
+            Default
+    end.
+
+-spec insert_many(gproc:type(), gproc:scope(), [{gproc:key(),any()}], pid()) ->
           {true,list()} | false.
 
 insert_many(T, Scope, KVL, Pid) ->
@@ -155,7 +184,7 @@ insert_many(T, Scope, KVL, Pid) ->
             end
     end.
 
--spec insert_objects([{key(), pid(), any()}]) -> ok.
+-spec insert_objects([{gproc:key(), pid(), any()}]) -> ok.
 
 insert_objects(Objs) ->
     lists:foreach(
@@ -211,7 +240,7 @@ maybe_waiters(K, Pid, Value, T, Event) ->
             false
     end.
 
--spec notify_waiters([{pid(), reference()}], key(), pid(), any(), any()) -> ok.
+-spec notify_waiters([{pid(), reference()}], gproc:key(), pid(), any(), any()) -> ok.
 notify_waiters([{P, Ref}|T], K, Pid, V, E) ->
     P ! {gproc, Ref, registered, {K, Pid, V}},
     notify_waiters(T, K, Pid, V, E);
@@ -274,7 +303,7 @@ remove_monitors(Key, Pid, MPid) ->
     end.
 
 
-mk_reg_objs(T, Scope, Pid, L) when T==n; T==a ->
+mk_reg_objs(T, Scope, Pid, L) when T==n; T==a; T==rc ->
     lists:map(fun({K,V}) ->
                       {{{T,Scope,K},T}, Pid, V};
                  (_) ->
@@ -422,11 +451,11 @@ unreg_opts(Key, Pid) ->
 remove_reg_1({c,_,_} = Key, Pid) ->
     remove_counter_1(Key, ets:lookup_element(?TAB, Reg = {Key,Pid}, 3), Pid),
     Reg;
-remove_reg_1({a,_,_} = Key, _Pid) ->
-    ets:delete(?TAB, Reg = {Key,a}),
+remove_reg_1({r,_,_} = Key, Pid) ->
+    remove_resource_1(Key, ets:lookup_element(?TAB, Reg = {Key,Pid}, 3), Pid),
     Reg;
-remove_reg_1({n,_,_} = Key, _Pid) ->
-    ets:delete(?TAB, Reg = {Key,n}),
+remove_reg_1({T,_,_} = Key, _Pid) when T==a; T==n; T==rc ->
+    ets:delete(?TAB, Reg = {Key,T}),
     Reg;
 remove_reg_1({_,_,_} = Key, Pid) ->
     ets:delete(?TAB, Reg = {Key, Pid}),
@@ -437,18 +466,23 @@ remove_counter_1({c,C,N} = Key, Val, Pid) ->
     update_aggr_counter(C, N, -Val),
     Res.
 
+remove_resource_1({r,C,N} = Key, _, Pid) ->
+    Res = ets:delete(?TAB, {Key, Pid}),
+    update_resource_count(C, N, -1),
+    Res.
+
 do_set_value({T,_,_} = Key, Value, Pid) ->
     K2 = if Pid == shared -> shared;
-	    T==n orelse T==a -> T;
+	    T==n orelse T==a orelse T==rc -> T;
 	    true -> Pid
          end,
-    case (catch ets:lookup_element(?TAB, {Key,K2}, 2)) of
-        {'EXIT', {badarg, _}} ->
-            false;
+    try ets:lookup_element(?TAB, {Key,K2}, 2) of
         Pid ->
             ets:insert(?TAB, {{Key, K2}, Pid, Value});
         _ ->
             false
+    catch
+        error:_ -> false
     end.
 
 do_set_counter_value({_,C,N} = Key, Value, Pid) ->
@@ -478,6 +512,7 @@ update_counter({T,l,Ctr} = Key, {Incr, Threshold, SetValue}, Pid)
     end,
     New;
 update_counter({T,l,Ctr} = Key, Ops, Pid) when is_list(Ops), T==c;
+                                               is_list(Ops), T==r;
 					       is_list(Ops), T==n ->
     case ets:update_counter(?TAB, {Key, Pid},
 			    [{3, 0} | expand_ops(Ops)]) of
@@ -505,18 +540,73 @@ expand_ops([]) ->
 expand_ops(_) ->
     ?THROW_GPROC_ERROR(badarg).
 
+update_aggr_counter(C, N, Val) ->
+    ?MAY_FAIL(ets:update_counter(?TAB, {{a,C,N},a}, {3, Val})).
 
+decrement_resource_count(C, N) ->
+    update_resource_count(C, N, -1).
 
+update_resource_count(C, N, Val) ->
+    try ets:update_counter(?TAB, {{rc,C,N},rc}, {3, Val}) of
+        0 ->
+            resource_count_zero(C, N);
+        _ ->
+            ok
+    catch
+        _:_ -> ok
+    end.
 
+resource_count_zero(C, N) ->
+    case ets:lookup(?TAB, {K = {rc,C,N},rc}) of
+        [{_, Pid, _}] ->
+            case get_attr(on_zero, Pid, K, undefined) of
+                undefined -> ok;
+                Actions ->
+                    perform_on_zero(Actions, C, N, Pid)
+            end;
+        _ -> ok
+    end.
 
-update_aggr_counter(C, N, Val) ->
-    catch ets:update_counter(?TAB, {{a,C,N},a}, {3, Val}).
+perform_on_zero(Actions, C, N, Pid) ->
+    lists:foreach(
+      fun(A) ->
+              try perform_on_zero_(A, C, N, Pid)
+              catch error:_ -> ignore
+              end
+      end, Actions).
+
+perform_on_zero_({send, ToProc}, C, N, Pid) ->
+    gproc:send(ToProc, {gproc, resource_on_zero, C, N, Pid}),
+    ok;
+perform_on_zero_({bcast, ToProc}, C, N, Pid) ->
+    gproc:bcast(ToProc, {gproc, resource_on_zero, C, N, Pid}),
+    ok;
+perform_on_zero_(publish, C, N, Pid) ->
+    gproc_ps:publish(C, gproc_resource_on_zero, {C, N, Pid}),
+    ok;
+perform_on_zero_({unreg_shared, T,N}, C, _, _) ->
+    K = {T, C, N},
+    case ets:member(?TAB, {K, shared}) of
+        true ->
+            Objs = remove_reg(K, shared, unreg),
+            _ = if C == g -> self() ! {gproc_unreg, Objs};
+                   true   -> ok
+                end,
+            ok;
+        false ->
+            ok
+    end;
+perform_on_zero_(_, _, _, _) ->
+    ok.
 
 scan_existing_counters(Ctxt, Name) ->
     Head = {{{c,Ctxt,Name},'_'},'_','$1'},
     Cs = ets:select(?TAB, [{Head, [], ['$1']}]),
     lists:sum(Cs).
 
+scan_existing_resources(Ctxt, Name) ->
+    Head = {{{r,Ctxt,Name},'_'},'_','_'},
+    ets:select_count(?TAB, [{Head, [], [true]}]).
 
 valid_opts(Type, Default) ->
     Opts = get_app_env(Type, Default),

+ 102 - 3
test/gproc_dist_tests.erl

@@ -61,6 +61,18 @@ dist_test_() ->
                                        ?debugVal(t_aggr_counter(Ns))
                                end,
                                fun() ->
+                                       ?debugVal(t_awaited_aggr_counter(Ns))
+                               end,
+                               fun() ->
+                                       ?debugVal(t_simple_resource_count(Ns))
+                               end,
+                               fun() ->
+                                       ?debugVal(t_awaited_resource_count(Ns))
+                               end,
+                               fun() ->
+                                       ?debugVal(t_resource_count_on_zero(Ns))
+                               end,
+                               fun() ->
                                        ?debugVal(t_update_counters(Ns))
                                end,
                                fun() ->
@@ -127,6 +139,7 @@ run_dist_tests() ->
 -define(T_NAME, {n, g, {?MODULE, ?LINE, erlang:now()}}).
 -define(T_KVL, [{foo, "foo"}, {bar, "bar"}]).
 -define(T_COUNTER, {c, g, {?MODULE, ?LINE}}).
+-define(T_RESOURCE, {r, g, {?MODULE, ?LINE}}).
 -define(T_PROP, {p, g, ?MODULE}).
 
 t_simple_reg([H|_] = Ns) ->
@@ -201,6 +214,92 @@ t_aggr_counter([H1,H2|_] = Ns) ->
     ?assertMatch(ok, t_call(Pc2, die)),
     ?assertMatch(ok, t_call(Pa, die)).
 
+t_awaited_aggr_counter([H1,H2|_] = Ns) ->
+    {c,g,Nm} = Ctr = ?T_COUNTER,
+    Aggr = {a,g,Nm},
+    Pc1 = t_spawn_reg(H1, Ctr, 3),
+    P = t_spawn(H2),
+    Ref = erlang:monitor(process, P),
+    P ! {self(), Ref, {apply, gproc, await, [Aggr]}},
+    t_sleep(),
+    P1 = t_spawn_reg(H2, Aggr),
+    ?assert(P1 == receive
+                      {P, Ref, Res} ->
+                          element(1, Res);
+                      {'DOWN', Ref, _, _, Reason} ->
+                          erlang:error(Reason);
+                      Other ->
+                          erlang:error({received, Other})
+                  end),
+    ?assertMatch(ok, t_read_everywhere(Aggr, P1, Ns, 3)),
+    ?assertMatch(ok, t_call(Pc1, die)),
+    ?assertMatch(ok, t_call(P, die)),
+    flush_down(Ref),
+    ?assertMatch(ok, t_call(P1, die)).
+
+t_simple_resource_count([H1,H2|_] = Ns) ->
+    {r,g,Nm} = R = ?T_RESOURCE,
+    RC = {rc,g,Nm},
+    Pr1 = t_spawn_reg(H1, R, 3),
+    Prc = t_spawn_reg(H2, RC),
+    ?assertMatch(ok, t_read_everywhere(R, Pr1, Ns, 3)),
+    ?assertMatch(ok, t_read_everywhere(RC, Prc, Ns, 1)),
+    Pr2 = t_spawn_reg(H2, R, 4),
+    ?assertMatch(ok, t_read_everywhere(R, Pr2, Ns, 4)),
+    ?assertMatch(ok, t_read_everywhere(RC, Prc, Ns, 2)),
+    ?assertMatch(ok, t_call(Pr1, die)),
+    ?assertMatch(ok, t_read_everywhere(RC, Prc, Ns, 1)),
+    ?assertMatch(ok, t_call(Pr2, die)),
+    ?assertMatch(ok, t_call(Prc, die)).
+
+t_awaited_resource_count([H1,H2|_] = Ns) ->
+    {r,g,Nm} = R = ?T_RESOURCE,
+    RC = {rc,g,Nm},
+    Pr1 = t_spawn_reg(H1, R, 3),
+    P = t_spawn(H2),
+    Ref = erlang:monitor(process, P),
+    P ! {self(), Ref, {apply, gproc, await, [RC]}},
+    t_sleep(),
+    P1 = t_spawn_reg(H2, RC),
+    ?assert(P1 == receive
+                      {P, Ref, Res} ->
+                          element(1, Res);
+                      {'DOWN', Ref, _, _, Reason} ->
+                          erlang:error(Reason);
+                      Other ->
+                          erlang:error({received, Other})
+                  end),
+    ?assertMatch(ok, t_read_everywhere(RC, P1, Ns, 1)),
+    ?assertMatch(ok, t_call(Pr1, die)),
+    ?assertMatch(ok, t_call(P, die)),
+    flush_down(Ref),
+    ?assertMatch(ok, t_call(P1, die)).
+
+t_resource_count_on_zero([H1,H2|_] = Ns) ->
+    {r,g,Nm} = R = ?T_RESOURCE,
+    Prop = ?T_PROP,
+    RC = {rc,g,Nm},
+    Pr1 = t_spawn_reg(H1, R, 3),
+    Pp = t_spawn_reg(H2, Prop),
+    ?assertMatch(ok, t_call(Pp, {selective, true})),
+    Prc = t_spawn_reg(H2, RC, undefined, [{on_zero, [{send, Prop}]}]),
+    ?assertMatch(ok, t_read_everywhere(R, Pr1, Ns, 3)),
+    ?assertMatch(ok, t_read_everywhere(RC, Prc, Ns, 1)),
+    ?assertMatch(ok, t_call(Pr1, die)),
+    ?assertMatch(ok, t_read_everywhere(RC, Prc, Ns, 0)),
+    ?assertMatch({gproc, resource_on_zero, g, Nm, Prc},
+                 t_call(Pp, {apply_fun, fun() ->
+                                                receive
+                                                    {gproc, _, _, _, _} = M ->
+                                                        M
+                                                after 10000 ->
+                                                        timeout
+                                                end
+                                        end})),
+    ?assertMatch(ok, t_call(Pp, {selective, false})),
+    ?assertMatch(ok, t_call(Pp, die)),
+    ?assertMatch(ok, t_call(Prc, die)).
+
 t_update_counters([H1,H2|_] = Ns) ->
     {c,g,N1} = C1 = ?T_COUNTER,
     A1 = {a,g,N1},
@@ -213,7 +312,6 @@ t_update_counters([H1,H2|_] = Ns) ->
     ?assertMatch(ok, t_read_everywhere(C1, P12, Ns, 2)),
     ?assertMatch(ok, t_read_everywhere(C2, P2, Ns, 1)),
     ?assertMatch(ok, t_read_everywhere(A1, Pa1, Ns, 4)),
-    ?debugFmt("code:which(gproc_dist) = ~p~n", [code:which(gproc_dist)]),
     ?assertMatch([{C1,P1, 3},
 		  {C1,P12,4},
 		  {C2,P2, 0}], t_call(P1, {apply, gproc, update_counters,
@@ -347,12 +445,12 @@ t_standby_monitor([A,B|_] = Ns) ->
     ?assertMatch({gproc,unreg,Ref1,Na}, got_msg(Pc, gproc)),
     ?assertMatch(ok, t_lookup_everywhere(Na, Ns, undefined)).
 
-t_follow_monitor([A,B|_] = Ns) ->
+t_follow_monitor([A,B|_]) ->
     Na = ?T_NAME,
     Pa = t_spawn(A, _Selective = true),
     Ref = t_call(Pa, {apply, gproc, monitor, [Na, follow]}),
     {gproc,unreg,Ref,Na} = got_msg(Pa),
-    Pb = t_spawn_reg(A, Na),
+    Pb = t_spawn_reg(B, Na),
     {gproc,registered,Ref,Na} = got_msg(Pa),
     ok = t_call(Pb, die),
     ok = t_call(Pa, die).
@@ -468,6 +566,7 @@ t_spawn(Node, Selective) -> gproc_test_lib:t_spawn(Node, Selective).
 t_spawn_mreg(Node, KVL) -> gproc_test_lib:t_spawn_mreg(Node, KVL).
 t_spawn_reg(Node, N) -> gproc_test_lib:t_spawn_reg(Node, N).
 t_spawn_reg(Node, N, V) -> gproc_test_lib:t_spawn_reg(Node, N, V).
+t_spawn_reg(Node, N, V, As) -> gproc_test_lib:t_spawn_reg(Node, N, V, As).
 t_spawn_reg_shared(Node, N, V) -> gproc_test_lib:t_spawn_reg_shared(Node, N, V).
 got_msg(P) -> gproc_test_lib:got_msg(P).
 got_msg(P, Tag) -> gproc_test_lib:got_msg(P, Tag).

+ 14 - 1
test/gproc_test_lib.erl

@@ -1,7 +1,7 @@
 -module(gproc_test_lib).
 
 -export([t_spawn/1, t_spawn/2,
-         t_spawn_reg/2, t_spawn_reg/3,
+         t_spawn_reg/2, t_spawn_reg/3, t_spawn_reg/4,
          t_spawn_reg_shared/3,
          t_spawn_mreg/2,
          t_call/2,
@@ -42,6 +42,19 @@ t_spawn_reg(Node, Name, Value) ->
             erlang:error({timeout, t_spawn_reg, [Node, Name, Value]})
     end.
 
+t_spawn_reg(Node, Name, Value, Attrs) ->
+    Me = self(),
+    P = spawn(Node, fun() ->
+                            ?assertMatch(true, gproc:reg(Name, Value, Attrs)),
+                            Me ! {self(), ok},
+                            t_loop()
+                    end),
+    receive
+	{P, ok} -> P
+    after 1000 ->
+            erlang:error({timeout, t_spawn_reg, [Node, Name, Value]})
+    end.
+
 t_spawn_mreg(Node, KVL) ->
     Me = self(),
     P = spawn(Node, fun() ->

+ 67 - 0
test/gproc_tests.erl

@@ -84,6 +84,14 @@ reg_test_() ->
       , ?_test(t_is_clean())
       , {spawn, ?_test(?debugVal(t_simple_aggr_counter()))}
       , ?_test(t_is_clean())
+      , {spawn, ?_test(?debugVal(t_awaited_aggr_counter()))}
+      , ?_test(t_is_clean())
+      , {spawn, ?_test(?debugVal(t_simple_resource_count()))}
+      , ?_test(t_is_clean())
+      , {spawn, ?_test(?debugVal(t_awaited_resource_count()))}
+      , ?_test(t_is_clean())
+      , {spawn, ?_test(?debugVal(t_resource_count_on_zero_send()))}
+      , ?_test(t_is_clean())
       , {spawn, ?_test(?debugVal(t_update_counters()))}
       , ?_test(t_is_clean())
       , {spawn, ?_test(?debugVal(t_simple_prop()))}
@@ -229,6 +237,65 @@ t_simple_aggr_counter() ->
     end,
     ?assert(gproc:get_value({a,l,c1}) =:= 7).
 
+t_awaited_aggr_counter() ->
+    ?assert(gproc:reg({c,l,c1}, 3) =:= true),
+    gproc:nb_wait({a,l,c1}),
+    ?assert(gproc:reg({a,l,c1}) =:= true),
+    receive {gproc,_,registered,{{a,l,c1},_,_}} -> ok
+    after 1000 ->
+            error(timeout)
+    end,
+    ?assertMatch(3, gproc:get_value({a,l,c1})).
+
+t_simple_resource_count() ->
+    ?assert(gproc:reg({r,l,r1}, 1) =:= true),
+    ?assert(gproc:reg({rc,l,r1}) =:= true),
+    ?assert(gproc:get_value({rc,l,r1}) =:= 1),
+    P = self(),
+    P1 = spawn_link(fun() ->
+                            gproc:reg({r,l,r1}, 1),
+                            P ! {self(), ok},
+                            receive
+                                {P, goodbye} -> ok
+                            end
+                    end),
+    receive {P1, ok} -> ok end,
+    ?assert(gproc:get_value({rc,l,r1}) =:= 2),
+    P1 ! {self(), goodbye},
+    R = erlang:monitor(process, P1),
+    receive {'DOWN', R, _, _, _} ->
+            gproc:audit_process(P1)
+    end,
+    ?assert(gproc:get_value({rc,l,r1}) =:= 1).
+
+t_awaited_resource_count() ->
+    ?assert(gproc:reg({r,l,r1}, 3) =:= true),
+    ?assert(gproc:reg({r,l,r2}, 3) =:= true),
+    ?assert(gproc:reg({r,l,r3}, 3) =:= true),
+    gproc:nb_wait({rc,l,r1}),
+    ?assert(gproc:reg({rc,l,r1}) =:= true),
+    receive {gproc,_,registered,{{rc,l,r1},_,_}} -> ok
+    after 1000 ->
+            error(timeout)
+    end,
+    ?assertMatch(1, gproc:get_value({rc,l,r1})).
+
+t_resource_count_on_zero_send() ->
+    Me = self(),
+    ?assertMatch(true, gproc:reg({p,l,myp})),
+    ?assertMatch(true, gproc:reg({r,l,r1})),
+    ?assertMatch(true, gproc:reg({rc,l,r1}, 1, [{on_zero,
+                                                 [{send, {p,l,myp}}]}])),
+    ?assertMatch(1, gproc:get_value({rc,l,r1})),
+    ?assertMatch(true, gproc:unreg({r,l,r1})),
+    ?assertMatch(0, gproc:get_value({rc,l,r1})),
+    receive
+        {gproc, resource_on_zero, l, r1, Me} ->
+            ok
+    after 1000 ->
+            error(timeout)
+    end.
+
 t_update_counters() ->
     ?assert(gproc:reg({c,l,c1}, 3) =:= true),
     ?assert(gproc:reg({a,l,c1}) =:= true),