Browse Source

shared counters; reset_counter

Ulf Wiger 14 years ago
parent
commit
8c1e685420
8 changed files with 510 additions and 42 deletions
  1. 132 8
      doc/gproc.md
  2. 42 2
      doc/gproc_dist.md
  3. 1 1
      doc/gproc_lib.md
  4. 144 13
      src/gproc.erl
  5. 50 7
      src/gproc_dist.erl
  6. 10 8
      src/gproc_lib.erl
  7. 96 2
      test/gproc_dist_tests.erl
  8. 35 1
      test/gproc_tests.erl

+ 132 - 8
doc/gproc.md

@@ -180,9 +180,9 @@ a = aggregate_counter
 
 
 
-<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="#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="#cancel_wait-2">cancel_wait/2</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="#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>Read the value stored with a key registered to the current process.</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  
+<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="#cancel_wait-2">cancel_wait/2</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="#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  
 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="#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="#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="#select-1">select/1</a></td><td>Equivalent to <a href="#select-2"><tt>select(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td>Perform a select operation 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="#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="#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></table>
+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="#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="#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_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="#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>Equivalent to <a href="#select-2"><tt>select(all, Pat)</tt></a>.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td>Perform a select operation 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_shared_counter-2">update_shared_counter/2</a></td><td></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></table>
 
 
 
@@ -290,7 +290,21 @@ Registers a local (unique) name. @equiv reg({n,l,Name})<a name="add_local_proper
 
 
 
-Registers a local (non-unique) property. @equiv reg({p,l,Name},Value)<a name="audit_process-1"></a>
+Registers a local (non-unique) property. @equiv reg({p,l,Name},Value)<a name="add_shared_local_counter-2"></a>
+
+<h3>add_shared_local_counter/2</h3>
+
+
+
+
+
+`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>
 
 <h3>audit_process/1</h3>
 
@@ -502,9 +516,28 @@ __See also:__ [get_env/4](#get_env-4).<a name="get_value-1"></a>
 
 
 
-Read the value stored with a key registered to the current process.
+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>
+
+<h3>get_value/2</h3>
+
+
+
+
+
+<pre>get_value(Key, Pid) -> Value</pre>
+<br></br>
+
+
+
+
 
-If no such key is registered to the current process, this function exits.<a name="give_away-2"></a>
+
+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>
 
 <h3>give_away/2</h3>
 
@@ -962,7 +995,74 @@ Equivalent to [`reg(Key, default(Key))`](#reg-2).<a name="reg-2"></a>
 
 Register a name or property for the current process
 
-<a name="select-1"></a>
+<a name="reg_shared-1"></a>
+
+<h3>reg_shared/1</h3>
+
+
+
+
+
+<pre>reg_shared(Key::<a href="#type-key">key()</a>) -> true</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>
+
+<h3>reg_shared/2</h3>
+
+
+
+
+
+<pre>reg_shared(Key::<a href="#type-key">key()</a>, Value) -> true</pre>
+<br></br>
+
+
+
+
+
+
+Register a resource, but don't tie it to a particular process.
+
+
+
+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
+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="reset_counter-1"></a>
+
+<h3>reset_counter/1</h3>
+
+
+
+
+
+<pre>reset_counter(Key) -> {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>
+
+
+
+
+
+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>
 
 <h3>select/1</h3>
 
@@ -1195,7 +1295,21 @@ See [`http://www.erlang.org/doc/man/qlc.html`](http://www.erlang.org/doc/man/qlc
 
 
 
-Unregister a name or property.<a name="unregister_name-1"></a>
+Unregister a name or property.<a name="unreg_shared-1"></a>
+
+<h3>unreg_shared/1</h3>
+
+
+
+
+
+<pre>unreg_shared(Key::<a href="#type-key">key()</a>) -> true</pre>
+<br></br>
+
+
+
+
+Unregister a shared resource.<a name="unregister_name-1"></a>
 
 <h3>unregister_name/1</h3>
 
@@ -1227,7 +1341,17 @@ Updates the counter registered as Key for the current process.
 
 This function works 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.<a name="where-1"></a>
+will fail if the type of object referred to by Key is not a counter.<a name="update_shared_counter-2"></a>
+
+<h3>update_shared_counter/2</h3>
+
+
+
+
+
+`update_shared_counter(Key, Incr) -> any()`
+
+<a name="where-1"></a>
 
 <h3>where/1</h3>
 

+ 42 - 2
doc/gproc_dist.md

@@ -35,7 +35,7 @@ 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="#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="#update_counter-2">update_counter/2</a></td><td></td></tr></table>
+Scope = l | g (global or local).</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_shared_counter-2">update_shared_counter/2</a></td><td></td></tr></table>
 
 
 
@@ -244,7 +244,27 @@ Class = n  - unique name
 | p  - non-unique property
 | c  - counter
 | a  - aggregated counter
-Scope = l | g (global or local)<a name="set_value-2"></a>
+Scope = l | g (global or local)<a name="reg_shared-2"></a>
+
+<h3>reg_shared/2</h3>
+
+
+
+
+
+`reg_shared(Key, Value) -> any()`
+
+<a name="reset_counter-1"></a>
+
+<h3>reset_counter/1</h3>
+
+
+
+
+
+`reset_counter(Key) -> any()`
+
+<a name="set_value-2"></a>
 
 <h3>set_value/2</h3>
 
@@ -329,6 +349,16 @@ the leader died.)<a name="terminate-2"></a>
 
 `unreg(Key) -> any()`
 
+<a name="unreg_shared-1"></a>
+
+<h3>unreg_shared/1</h3>
+
+
+
+
+
+`unreg_shared(Key) -> any()`
+
 <a name="update_counter-2"></a>
 
 <h3>update_counter/2</h3>
@@ -339,3 +369,13 @@ the leader died.)<a name="terminate-2"></a>
 
 `update_counter(Key, Incr) -> any()`
 
+<a name="update_shared_counter-2"></a>
+
+<h3>update_shared_counter/2</h3>
+
+
+
+
+
+`update_shared_counter(Key, Incr) -> any()`
+

+ 1 - 1
doc/gproc_lib.md

@@ -96,7 +96,7 @@ For a detailed description, see gproc/doc/erlang07-wiger.pdf.
 
 
 
-<pre>insert_reg(K::<a href="#type-key">key()</a>, Value::any(), Pid::pid(), Scope::<a href="#type-scope">scope()</a>) -> boolean()</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>
 
 

+ 144 - 13
src/gproc.erl

@@ -64,10 +64,11 @@
 
 -export([start_link/0,
          reg/1, reg/2, unreg/1,
+	 reg_shared/1, reg_shared/2, unreg_shared/1,
          mreg/3,
          munreg/3,
          set_value/2,
-         get_value/1,
+         get_value/1, get_value/2,
          where/1,
          await/1, await/2,
          nb_wait/1,
@@ -77,6 +78,8 @@
          lookup_value/1,
          lookup_values/1,
          update_counter/2,
+	 reset_counter/1,
+	 update_shared_counter/2,
          give_away/2,
          goodbye/0,
          send/2,
@@ -104,6 +107,7 @@
          add_global_counter/2,
          add_local_aggr_counter/1,
          add_global_aggr_counter/1,
+	 add_shared_local_counter/2,
          lookup_local_name/1,
          lookup_global_name/1,
          lookup_local_properties/1,
@@ -202,6 +206,16 @@ add_local_counter(Name, Initial) when is_integer(Initial) ->
 
 %% spec(Name::any(), Initial::integer()) -> true
 %%
+%% @doc Registers a local shared (unique) counter. 
+%% @equiv reg_shared({c,l,Name},Value)
+%% @end
+%%
+add_shared_local_counter(Name, Initial) when is_integer(Initial) ->
+    reg_shared({c,l,Name}, Initial).
+
+
+%% spec(Name::any(), Initial::integer()) -> true
+%%
 %% @doc Registers a global (non-unique) counter. @equiv reg({c,g,Name},Value)
 %% @end
 %%
@@ -671,6 +685,46 @@ reg({n,l,_} = Key, Value) ->
 reg(_, _) ->
     erlang:error(badarg).
 
+
+%% @spec reg_shared(Key::key()) -> true
+%%
+%% @doc 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).'
+%% @end
+reg_shared({c,_,_} = Key) ->
+    reg_shared(Key, 0);
+reg_shared({a,_,_} = Key) ->
+    reg_shared(Key, undefined).
+
+
+%% @spec reg_shared(Key::key(), Value) -> true
+%%
+%% @doc Register a resource, but don't tie it to a particular process.
+%%
+%% Shared resources are all unique. They remain until explicitly unregistered
+%% (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
+%% 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.
+%% @end
+%%
+reg_shared({_,g,_} = Key, Value) ->
+    %% anything global
+    ?CHK_DIST,
+    gproc_dist:reg_shared(Key, Value);
+reg_shared({a,l,_} = Key, undefined) ->
+    call({reg_shared, Key, undefined});
+reg_shared({c,l,_} = Key, Value) when is_integer(Value) ->
+    call({reg_shared, Key, Value});
+reg_shared(_, _) ->
+    erlang:error(badarg).
+
 %% @spec mreg(type(), scope(), [{Key::any(), Value::any()}]) -> true
 %%
 %% @doc Register multiple {Key,Value} pairs of a given type and scope.
@@ -748,6 +802,21 @@ unreg(Key) ->
             end
     end.
 
+%% @spec (Key:: key()) -> true
+%%
+%% @doc Unregister a shared resource.
+%% @end
+unreg_shared(Key) ->
+    case Key of
+        {_, g, _} ->
+            ?CHK_DIST,
+            gproc_dist:unreg_shared(Key);
+        {T, l, _} when T == c;
+                       T == a -> call({unreg_shared, Key});
+        _ ->
+	    erlang:error(badarg)
+    end.
+
 %% @equiv unreg/1
 unregister_name(Key) ->
     unreg(Key).
@@ -850,13 +919,20 @@ set_value(_, _) ->
     erlang:error(badarg).
 
 %% @spec (Key) -> Value
-%% @doc Read the value stored with a key registered to the current process.
+%% @doc 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.
 %% @end
 get_value(Key) ->
     get_value(Key, self()).
 
+%% @spec (Key, Pid) -> Value
+%% @doc Reads the value stored with a key registered to the process Pid.
+%%
+%% If `Pid == shared', the value of a shared key (see {@link reg_shared/1})
+%% will be read.
+%% @end
+%%
 get_value({T,_,_} = Key, Pid) when is_pid(Pid) ->
     if T==n orelse T==a ->
             case ets:lookup(?TAB, {Key, T}) of
@@ -866,6 +942,15 @@ get_value({T,_,_} = Key, Pid) when is_pid(Pid) ->
        true ->
             ets:lookup_element(?TAB, {Key, Pid}, 3)
     end;
+get_value({T,_,_} = K, shared) when T==c; T==a ->
+    Key = case T of
+	      c -> {K, shared};
+	      a -> {K, a}
+	  end,
+    case ets:lookup(?TAB, Key) of
+	[{_, shared, Value}] -> Value;
+	_ -> erlang:error(badarg)
+    end;
 get_value(_, _) ->
     erlang:error(badarg).
 
@@ -978,6 +1063,40 @@ update_counter({c,g,_} = Key, Incr) when is_integer(Incr) ->
 update_counter(_, _) ->
     erlang:error(badarg).
 
+
+%% @spec (Key) -> {ValueBefore, ValueAfter}
+%%   Key   = {c, Scope, Name}
+%%   Scope = l | g
+%%   ValueBefore = integer()
+%%   ValueAfter  = integer()
+%%
+%% @doc 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 {@link update_counter/2},
+%% which allows for concurrent calls to {@link update_counter/2} without losing
+%% updates. Aggregated counters are updated accordingly.
+%% @end
+%%
+reset_counter({c,g,_} = Key) ->
+    ?CHK_DIST,
+    gproc_dist:reset_counter(Key);
+reset_counter({c,l,_} = Key) ->
+    Current = ets:lookup_element(?TAB, {Key, self()}, 3),
+    Initial = case ets:lookup(?TAB, {self(), Key}) of
+		  [{_, r}] -> 0;
+		  [{_, Opts}] ->
+		      proplists:get_value(initial, Opts, 0)
+	      end,
+    {Current, update_counter(Key, Initial - Current)}.
+
+
+update_shared_counter({c,g,_} = Key, Incr) ->
+    ?CHK_DIST,
+    gproc_dist:update_shared_counter(Key, Incr);
+update_shared_counter({c,l,_} = Key, Incr) ->
+    gproc_lib:update_counter(Key, Incr, shared).
+
 %% @spec (From::key(), To::pid() | key()) -> undefined | pid()
 %%
 %% @doc Atomically transfers the key `From' to the process identified by `To'.
@@ -1150,7 +1269,7 @@ info(Pid) when is_pid(Pid) ->
 %% same as [http://www.erlang.org/doc/man/erlang.html#process_info-2].
 %% @end
 info(Pid, ?MODULE) ->
-    Keys = ets:select(?TAB, [{ {{Pid,'$1'}, r}, [], ['$1'] }]),
+    Keys = ets:select(?TAB, [{ {{Pid,'$1'}, '_'}, [], ['$1'] }]),
     {?MODULE, lists:zf(
                 fun(K) ->
                         try V = get_value(K, Pid),
@@ -1210,6 +1329,14 @@ handle_call({reg, {_T,l,_} = Key, Val}, {Pid,_}, S) ->
         false ->
             {reply, badarg, S}
     end;
+handle_call({reg_shared, {_T,l,_} = Key, Val}, _From, S) ->
+    case try_insert_reg(Key, Val, shared) of
+    %% case try_insert_shared(Key, Val) of
+	true ->
+	    {reply, true, S};
+	false ->
+	    {reply, badarg, S}
+    end;
 handle_call({unreg, {_,l,_} = Key}, {Pid,_}, S) ->
     case ets:member(?TAB, {Pid,Key}) of
         true ->
@@ -1218,6 +1345,9 @@ handle_call({unreg, {_,l,_} = Key}, {Pid,_}, S) ->
         false ->
             {reply, badarg, S}
     end;
+handle_call({unreg_shared, {_,l,_} = Key}, _, S) ->
+    _ = gproc_lib:remove_reg(Key, shared),
+    {reply, true, S};
 handle_call({await, {_,l,_} = Key, Pid}, From, S) ->
     %% Passing the pid explicitly is needed when leader_call is used,
     %% since the Pid given as From in the leader is the local gen_leader
@@ -1307,9 +1437,6 @@ cast(Msg, l) ->
 cast(Msg, g) ->
     gproc_dist:leader_cast(Msg).
 
-
-
-
 try_insert_reg({T,l,_} = Key, Val, Pid) ->
     case gproc_lib:insert_reg(Key, Val, Pid, l) of
         false ->
@@ -1332,6 +1459,10 @@ 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) ->
+%%     ets:insert_new(?TAB, [{{Key, a}, shared, Val}, {{shared, Key}, []}]).
 
 -spec audit_process(pid()) -> ok.
 
@@ -1349,7 +1480,7 @@ process_is_down(Pid) when is_pid(Pid) ->
         false ->
             ok;
         true ->
-            Revs = ets:select(?TAB, [{{{Pid,'$1'},r},
+            Revs = ets:select(?TAB, [{{{Pid,'$1'}, '_'},
                                       [{'==',{element,2,'$1'},l}], ['$1']}]),
             lists:foreach(
               fun({n,l,_}=K) ->
@@ -1395,7 +1526,7 @@ do_give_away({T,l,_} = K, To, Pid) when T==n; T==a ->
                     Pid;
                 ToPid when is_pid(ToPid) ->
                     ets:insert(?TAB, [{Key, ToPid, Value},
-                                      {{ToPid, K}, r}]),
+                                      {{ToPid, K}, []}]),
                     ets:delete(?TAB, {Pid, K}),
                     _ = gproc_lib:ensure_monitor(ToPid, l),
                     ToPid;
@@ -1418,7 +1549,7 @@ do_give_away({T,l,_} = K, To, Pid) when T==c; T==p ->
                             badarg;
                         false ->
                             ets:insert(?TAB, [{ToKey, ToPid, Value},
-                                              {{ToPid, K}, r}]),
+                                              {{ToPid, K}, []}]),
                             ets:delete(?TAB, {Pid, K}),
                             ets:delete(?TAB, Key),
                             _ = gproc_lib:ensure_monitor(ToPid, l),
@@ -1598,7 +1729,7 @@ is_var('$8') -> {true,8};
 is_var('$9') -> {true,9};
 is_var(X) when is_atom(X) ->
     case atom_to_list(X) of
-        "$" ++ Tl ->
+        "\$" ++ Tl ->
             try N = list_to_integer(Tl),
                 {true,N}
             catch
@@ -1704,10 +1835,10 @@ qlc_lookup(_Scope, 1, Keys) ->
 qlc_lookup(Scope, 2, Pids) ->
     lists:flatmap(fun(Pid) ->
                           Found =
-                              ets:select(?TAB, [{ {{Pid, rev_keypat(Scope)}, r},
-                                                  [], ['$_']}]),
+                              ets:select(?TAB, [{{{Pid, rev_keypat(Scope)}, '_'},
+						 [], ['$_']}]),
                           lists:flatmap(
-                            fun({{_,{T,_,_}=K}, r}) ->
+                            fun({{_,{T,_,_}=K}, _}) ->
                                     K2 = if T==n orelse T==a -> T;
                                             true -> Pid
                                          end,

+ 50 - 7
src/gproc_dist.erl

@@ -24,11 +24,14 @@
 
 -export([start_link/0, start_link/1,
          reg/1, reg/2, unreg/1,
+	 reg_shared/2, unreg_shared/1,
          mreg/2,
          munreg/2,
          set_value/2,
          give_away/2,
-         update_counter/2]).
+         update_counter/2,
+	 update_shared_counter/2,
+	 reset_counter/1]).
 
 -export([leader_call/1,
          leader_cast/1,
@@ -97,6 +100,12 @@ reg({_,g,_} = Key, Value) ->
 reg(_, _) ->
     erlang:error(badarg).
 
+reg_shared({_,g,_} = Key, Value) ->
+    leader_call({reg, Key, Value, shared});
+reg_shared(_, _) ->
+    erlang:error(badarg).
+
+
 mreg(T, KVL) ->
     if is_list(KVL) -> leader_call({mreg, T, g, KVL, self()});
        true -> erlang:error(badarg)
@@ -112,6 +121,12 @@ unreg({_,g,_} = Key) ->
 unreg(_) ->
     erlang:error(badarg).
 
+unreg_shared({T,g,_} = Key) when T==c; T==a ->
+    leader_call({unreg, Key, shared});
+unreg_shared(_) ->
+    erlang:error(badarg).
+
+
 set_value({T,g,_} = Key, Value) when T==a; T==c ->
     if is_integer(Value) ->
             leader_call({set, Key, Value});
@@ -132,6 +147,18 @@ update_counter({c,g,_} = Key, Incr) when is_integer(Incr) ->
 update_counter(_, _) ->
     erlang:error(badarg).
 
+update_shared_counter({c,g,_} = Key, Incr) when is_integer(Incr) ->
+    leader_call({update_counter, Key, Incr, shared});
+update_shared_counter(_, _) ->
+    erlang:error(badarg).
+
+
+reset_counter({c,g,_} = Key) ->
+    leader_call({reset_counter, Key, self()});
+reset_counter(_) ->
+    erlang:error(badarg).
+
+
 %% @spec sync() -> true
 %% @doc Synchronize with the gproc leader
 %%
@@ -261,6 +288,22 @@ handle_leader_call({update_counter, {c,g,_Ctr} = Key, Incr, Pid}, _From, S, _E)
         error:_ ->
             {reply, badarg, S}
     end;
+handle_leader_call({reset_counter, {c,g,_Ctr} = Key, Pid}, _From, S, _E) ->
+    try  Current = ets:lookup_element(?TAB, {Key, Pid}, 3),
+	 Initial = case ets:lookup_element(?TAB, {Pid, Key}, 2) of
+		       r -> 0;
+		       Opts when is_list(Opts) ->
+			   proplists:get_value(initial, Opts, 0)
+		   end,
+	 Incr = Initial - Current,
+	 New = ets:update_counter(?TAB, {Key, Pid}, {3, Incr}),
+	 Vals = [{{Key,Pid},Pid,New} | update_aggr_counter(Key, Incr)],
+	 {reply, {Current, New}, [{insert, Vals}], S}
+    catch
+	error:_R ->
+	    io:fwrite("reset_counter failed: ~p~n~p~n", [_R, erlang:get_stacktrace()]),
+	    {reply, badarg, S}
+    end;
 handle_leader_call({unreg, {T,g,Name} = K, Pid}, _From, S, _E) ->
     Key = if T == n; T == a -> {K,T};
              true -> {K, Pid}
@@ -293,7 +336,7 @@ handle_leader_call({give_away, {T,g,_} = K, To, Pid}, _From, S, _E)
                     {reply, Pid, S};
                 ToPid when is_pid(ToPid) ->
                     ets:insert(?TAB, [{Key, ToPid, Value},
-                                      {{ToPid,K}, r}]),
+                                      {{ToPid,K}, []}]),
                     _ = gproc_lib:ensure_monitor(ToPid, g),
                     {reply, ToPid, [{delete, [Key, {Pid,K}]},
                                    {insert, [{Key, ToPid, Value}]}], S};
@@ -394,7 +437,7 @@ handle_leader_cast({remove_globals, Globals}, S, _E) ->
     delete_globals(Globals),
     {ok, S};
 handle_leader_cast({pid_is_DOWN, Pid}, S, _E) ->
-    Globals = ets:select(?TAB, [{{{Pid,'$1'},r},
+    Globals = ets:select(?TAB, [{{{Pid,'$1'}, '_'},
                                  [{'==',{element,2,'$1'},g}],[{{'$1',Pid}}]}]),
     ets:delete(?TAB, {Pid,g}),
     case process_globals(Globals) of
@@ -440,9 +483,9 @@ from_leader(Ops, S, _E) ->
               ets:insert(?TAB, Globals),
               lists:foreach(
                 fun({{{_,g,_}=Key,_}, P, _}) ->
-                        ets:insert(?TAB, {{P,Key},r}),
+                        ets:insert(?TAB, {{P,Key}, []}),
                         gproc_lib:ensure_monitor(P,g);
-                   ({{P,_K},r}) ->
+                   ({{P,_K}, _}) when is_pid(P) ->
                         gproc_lib:ensure_monitor(P,g);
                    (_) ->
                         skip
@@ -454,11 +497,11 @@ delete_globals(Globals) ->
     lists:foreach(
       fun({{_,g,_},T} = K) when is_atom(T) ->
               ets:delete(?TAB, K);
-         ({Key, Pid}) when is_pid(Pid) ->
+         ({Key, Pid}) when is_pid(Pid); Pid==shared ->
               K = ets_key(Key,Pid),
               ets:delete(?TAB, K),
               ets:delete(?TAB, {Pid, Key});
-         ({Pid, K}) when is_pid(Pid) ->
+         ({Pid, K}) when is_pid(Pid); Pid==shared ->
               ets:delete(?TAB, {Pid, K})
       end, Globals).
 

+ 10 - 8
src/gproc_lib.erl

@@ -42,7 +42,7 @@
 %% Pid around as payload as well. This is a bit redundant, but
 %% symmetric.
 %%
--spec insert_reg(key(), any(), pid(), scope()) -> boolean().
+-spec insert_reg(key(), any(), pid() | shared, scope()) -> boolean().
 
 insert_reg({T,_,Name} = K, Value, Pid, Scope) when T==a; T==n ->
     MaybeScan = fun() ->
@@ -53,7 +53,7 @@ insert_reg({T,_,Name} = K, Value, Pid, Scope) when T==a; T==n ->
                                 true
                         end
                 end,
-    Info = [{{K, T}, Pid, Value}, {{Pid,K},r}],
+    Info = [{{K, T}, Pid, Value}, {{Pid,K}, []}],
     case ets:insert_new(?TAB, Info) of
         true ->
             MaybeScan();
@@ -68,7 +68,7 @@ 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},
     Kr = {Pid, Key},
-    Res = ets:insert_new(?TAB, [{K, Pid, Value}, {Kr,r}]),
+    Res = ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, [{initial, Value}]}]),
     case Res of
         true ->
             update_aggr_counter(Scope, Ctr, Value);
@@ -80,7 +80,7 @@ insert_reg({_,_,_} = Key, Value, Pid, _Scope) 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,r}]).
+    ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, []}]).
 
 
 
@@ -119,7 +119,7 @@ insert_many(T, Scope, KVL, Pid) ->
 insert_objects(Objs) ->
     lists:foreach(
       fun({{{Id,_} = _K, Pid, V} = Obj, Existing}) ->
-              ets:insert(?TAB, [Obj, {{Pid, Id}, r}]),
+              ets:insert(?TAB, [Obj, {{Pid, Id}, []}]),
               case Existing of
                   [] -> ok;
                   [{_, Waiters}] ->
@@ -129,7 +129,7 @@ insert_objects(Objs) ->
 
 
 await({T,C,_} = Key, WPid, {_Pid, Ref} = From) ->
-    Rev = {{WPid,Key}, r},
+    Rev = {{WPid,Key}, []},
     case ets:lookup(?TAB, {Key,T}) of
         [{_, P, Value}] ->
             %% for symmetry, we always reply with Ref and then send a message
@@ -173,7 +173,7 @@ maybe_waiters(K, Pid, Value, T, Info) ->
 notify_waiters(Waiters, K, Pid, V) ->
     _ = [begin
              P ! {gproc, Ref, registered, {K, Pid, V}},
-             case P of 
+             case P of
 		 Pid -> ignore;
 		 _ ->
 		     ets:delete(?TAB, {P, K})
@@ -197,9 +197,11 @@ mk_reg_objs(p = T, Scope, Pid, L) ->
               end, L).
 
 mk_reg_rev_objs(T, Scope, Pid, L) ->
-    [{{Pid,{T,Scope,K}},r} || {K,_} <- L].
+    [{{Pid,{T,Scope,K}}, []} || {K,_} <- L].
 
 
+ensure_monitor(shared, _) ->
+    ok;
 ensure_monitor(Pid, Scope) when Scope==g; Scope==l ->
     case node(Pid) == node() andalso ets:insert_new(?TAB, {{Pid, Scope}}) of
         false -> ok;

+ 96 - 2
test/gproc_dist_tests.erl

@@ -44,6 +44,15 @@ dist_test_() ->
 			       	       ?debugVal(t_simple_reg(Ns))
 			       end,
 			       fun() ->
+			       	       ?debugVal(t_simple_counter(Ns))
+			       end,
+			       fun() ->
+			       	       ?debugVal(t_aggr_counter(Ns))
+			       end,
+			       fun() ->
+			       	       ?debugVal(t_shared_counter(Ns))
+			       end,
+			       fun() ->
 			       	       ?debugVal(t_mreg(Ns))
 			       end,
 			       fun() ->
@@ -75,6 +84,7 @@ dist_test_() ->
 
 -define(T_NAME, {n, g, {?MODULE, ?LINE}}).
 -define(T_KVL, [{foo, "foo"}, {bar, "bar"}]).
+-define(T_COUNTER, {c, g, {?MODULE, ?LINE}}).
 
 t_simple_reg([H|_] = Ns) ->
     Name = ?T_NAME,
@@ -84,6 +94,47 @@ t_simple_reg([H|_] = Ns) ->
     ?assertMatch(ok, t_lookup_everywhere(Name, Ns, undefined)),
     ?assertMatch(ok, t_call(P, die)).
 
+t_simple_counter([H|_] = Ns) ->
+    Ctr = ?T_COUNTER,
+    P = t_spawn_reg(H, Ctr, 3),
+    ?assertMatch(ok, t_read_everywhere(Ctr, P, Ns, 3)),
+    ?assertMatch(5, t_call(P, {apply, gproc, update_counter, [Ctr, 2]})),
+    ?assertMatch(ok, t_read_everywhere(Ctr, P, Ns, 5)),
+    ?assertMatch(ok, t_call(P, die)).
+
+t_shared_counter([H|_] = Ns) ->
+    Ctr = ?T_COUNTER,
+    P = t_spawn_reg_shared(H, Ctr, 3),
+    ?assertMatch(ok, t_read_everywhere(Ctr, shared, Ns, 3)),
+    ?assertMatch(5, t_call(P, {apply, gproc, update_shared_counter, [Ctr, 2]})),
+    ?assertMatch(ok, t_read_everywhere(Ctr, shared, Ns, 5)),
+    ?assertMatch(ok, t_call(P, die)),
+    ?assertMatch(ok, t_read_everywhere(Ctr, shared, Ns, 5)),
+    ?assertMatch(ok, t_read_everywhere(Ctr, shared, Ns, 5)), % twice
+    P1 = t_spawn(H),
+    ?assertMatch(true, t_call(P1, {apply, gproc, unreg_shared, [Ctr]})),
+    ?assertMatch(ok, t_read_everywhere(Ctr, shared, Ns, badarg)).
+
+
+t_aggr_counter([H1,H2|_] = Ns) ->
+    {c,g,Nm} = Ctr = ?T_COUNTER,
+    Aggr = {a,g,Nm},
+    Pc1 = t_spawn_reg(H1, Ctr, 3),
+    Pa = t_spawn_reg(H2, Aggr),
+    ?assertMatch(ok, t_read_everywhere(Ctr, Pc1, Ns, 3)),
+    ?assertMatch(ok, t_read_everywhere(Aggr, Pa, Ns, 3)),
+    Pc2 = t_spawn_reg(H2, Ctr, 3),
+    ?assertMatch(ok, t_read_everywhere(Ctr, Pc2, Ns, 3)),
+    ?assertMatch(ok, t_read_everywhere(Aggr, Pa, Ns, 6)),
+    ?assertMatch(5, t_call(Pc1, {apply, gproc, update_counter, [Ctr, 2]})),
+    ?assertMatch(ok, t_read_everywhere(Ctr, Pc1, Ns, 5)),
+    ?assertMatch(ok, t_read_everywhere(Aggr, Pa, Ns, 8)),
+    ?assertMatch(ok, t_call(Pc1, die)),
+    ?assertMatch(ok, t_read_everywhere(Aggr, Pa, Ns, 3)),
+    ?assertMatch(ok, t_call(Pc2, die)),
+    ?assertMatch(ok, t_call(Pa, die)).
+
+
 t_mreg([H|_] = Ns) ->
     Kvl = ?T_KVL,
     Keys = [K || {K,_} <- Kvl],
@@ -211,13 +262,39 @@ t_lookup_everywhere(Key, Nodes, Exp, I) ->
     Expected = [{N, Exp} || N <- Nodes],
     Found = [{N,rpc:call(N, gproc, where, [Key])} || N <- Nodes],
     if Expected =/= Found ->
-	    ?debugFmt("lookup ~p failed (~p), retrying...~n", [Key, Found]),
+	    ?debugFmt("lookup ~p failed~n"
+		      "(Expected: ~p;~n"
+		      " Found   : ~p), retrying...~n",
+		      [Key, Expected, Found]),
 	    t_sleep(),
 	    t_lookup_everywhere(Key, Nodes, Exp, I-1);
        true ->
 	    ok
     end.
 
+t_read_everywhere(Key, Pid, Nodes, Exp) ->
+    t_read_everywhere(Key, Pid, Nodes, Exp, 3).
+
+t_read_everywhere(Key, _, _, Exp, 0) ->
+    {read_failed, Key, Exp};
+t_read_everywhere(Key, Pid, Nodes, Exp, I) ->
+    Expected = [{N, Exp} || N <- Nodes],
+    Found = [{N, read_result(rpc:call(N, gproc, get_value, [Key, Pid]))}
+	     || N <- Nodes],
+    if Expected =/= Found ->
+	    ?debugFmt("read ~p failed~n"
+		      "(Expected: ~p;~n"
+		      " Found   : ~p), retrying...~n",
+		      [{Key, Pid}, Expected, Found]),
+	    t_sleep(),
+	    t_read_everywhere(Key, Pid, Nodes, Exp, I-1);
+       true ->
+	    ok
+    end.
+
+read_result({badrpc, {'EXIT', {badarg, _}}}) -> badarg;
+read_result(R) -> R.
+
 t_spawn(Node) ->
     t_spawn(Node, false).
 
@@ -232,9 +309,23 @@ t_spawn(Node, Selective) when is_boolean(Selective) ->
     end.
 
 t_spawn_reg(Node, Name) ->
+    t_spawn_reg(Node, Name, default_value(Name)).
+
+t_spawn_reg(Node, Name, Value) ->
+    Me = self(),
+    spawn(Node, fun() ->
+			?assertMatch(true, gproc:reg(Name, Value)),
+			Me ! {self(), ok},
+			t_loop()
+		end),
+    receive
+	{P, ok} -> P
+    end.
+
+t_spawn_reg_shared(Node, Name, Value) ->
     Me = self(),
     spawn(Node, fun() ->
-			?assertMatch(true, gproc:reg(Name)),
+			?assertMatch(true, gproc:reg_shared(Name, Value)),
 			Me ! {self(), ok},
 			t_loop()
 		end),
@@ -242,6 +333,9 @@ t_spawn_reg(Node, Name) ->
 	{P, ok} -> P
     end.
 
+default_value({c,_,_}) -> 0;
+default_value(_) -> undefined.
+
 t_spawn_mreg(Node, KVL) ->
     Me = self(),
     spawn(Node, fun() ->

+ 35 - 1
test/gproc_tests.erl

@@ -71,6 +71,10 @@ reg_test_() ->
      [
       {spawn, ?_test(t_simple_reg())}
       , ?_test(t_is_clean())
+      , {spawn, ?_test(t_simple_counter())}
+      , ?_test(t_is_clean())
+      , {spawn, ?_test(t_simple_aggr_counter())}
+      , ?_test(t_is_clean())
       , {spawn, ?_test(t_simple_prop())}
       , ?_test(t_is_clean())
       , {spawn, ?_test(t_await())}
@@ -115,6 +119,36 @@ t_simple_reg() ->
     ?assert(gproc:unreg({n,l,name}) =:= true),
     ?assert(gproc:where({n,l,name}) =:= undefined).
 
+t_simple_counter() ->
+    ?assert(gproc:reg({c,l,c1}, 3) =:= true),
+    ?assert(gproc:get_value({c,l,c1}) =:= 3),
+    ?assert(gproc:update_counter({c,l,c1}, 4) =:= 7),
+    ?assert(gproc:reset_counter({c,l,c1}) =:= {7, 3}).
+
+t_simple_aggr_counter() ->
+    ?assert(gproc:reg({c,l,c1}, 3) =:= true),
+    ?assert(gproc:reg({a,l,c1}) =:= true),
+    ?assert(gproc:get_value({a,l,c1}) =:= 3),
+    P = self(),
+    P1 = spawn_link(fun() ->
+			    gproc:reg({c,l,c1}, 5),
+			    P ! {self(), ok},
+			    receive
+				{P1, goodbye} -> ok
+			    end
+		    end),
+    receive {P1, ok} -> ok end,
+    ?assert(gproc:get_value({a,l,c1}) =:= 8),
+    ?assert(gproc:update_counter({c,l,c1}, 4) =:= 7),
+    ?assert(gproc:get_value({a,l,c1}) =:= 12),
+    P1 ! {self(), goodbye},
+    R = erlang:monitor(process, P1),
+    receive {'DOWN', R, _, _, _} ->
+	    gproc:audit_process(P1)
+    end,
+    ?assert(gproc:get_value({a,l,c1}) =:= 7).
+
+
 t_simple_prop() ->
     ?assert(gproc:reg({p,l,prop}) =:= true),
     ?assert(t_other_proc(fun() ->
@@ -162,7 +196,7 @@ t_await_self() ->
 t_is_clean() ->
     sys:get_status(gproc), % in order to synch
     T = ets:tab2list(gproc),
-    ?assert(T =:= []).
+    ?assertMatch([], T).
 
 t_simple_mreg() ->
     P = self(),