Open-source project
uwiger/gproc avatar
uwiger/gproc

gproc: a process registry for Erlang that scales to a cluster

Extended process registry for Erlang

1,101 stars230 forksErlangApache-2.0

At a glance

What is it?
gproc is an extended process dictionary for Erlang, giving any term as a process alias, multiple aliases per process, non unique properties registered by many processes, counters that maintain their own totals, and a global registry with the same features across a network of nodes. It suits a system that needs to find processes by role rather than by name, and it is one of the older libraries in the BEAM ecosystem.
Who is it for?
gproc fits an Erlang system where processes need to be found by role and property rather than by a registered name, because it supports any term as an alias, lets one process answer to several aliases, and lets many processes register the same property, which the built in dictionary cannot do.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 77 days ago.
What is it written in?
Mainly Erlang, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the built-in dictionary cannot do

Gproc is a process dictionary for Erlang, which provides a number of useful features beyond what the built-in dictionary has.

The list is the specification, and each item is a capability the standard `erlang:register` does not offer.

Use any term as a process alias, so a registry key does not have to be an atom. Register a process under several aliases, which is what you want when a process has several roles. Non-unique properties can be registered simultaneously by many processes, which is the opposite of the built-in dictionary's one name to one process rule.

A QLC and match specification interface for efficient queries on the dictionary, so you can ask for all processes with a given property without scanning.

Await registration, which lets you wait until a process registers itself, so a supervisor can start a dependent only once the thing it depends on exists.

Atomically give away registered names and properties to another process, which is a handover primitive the standard registry does not provide.

Counters, and aggregated counters, which automatically maintain the total of all counters with a given name.

And a global registry, with all the above functions applied to a network of nodes, which is the feature that takes gproc from a local convenience to a cluster wide one.

The design intent is stated as a use case rather than a feature list. Gproc was designed to work as a central index for process metadata, that is properties that describe the role and characteristics of each process, and having a single registry that is flexible enough to hold important types of property makes it easier to find processes of a certain type and to query and browse key data in a running system.

So the question gproc answers is not how do I find this process, which the built-in registry answers, but how do I find every process playing this role, which is the question a supervision tree and a registry of names cannot answer well.

The property notation, and why pub/sub is the obvious use

The second use case in the documentation is publish and subscribe patterns, and the example is short enough to be the clearest illustration of the notation.

erlang
subscribe(EventType) ->
    %% Gproc notation: {p, l, Name} means {(p)roperty, (l)ocal, Name}
    gproc:reg({p, l, {?MODULE, EventType}}).

notify(EventType, Msg) ->
    Key = {?MODULE, EventType},
    gproc:send({p, l, Key}, {self(), Key, Msg}).

The notation is a tuple whose first element is a kind and whose second is a scope, and the comment decodes it, with {p, l, Name} meaning a property, local, with that name. The {l} is the scope, and it is the part that carries the most weight, because the same property name in the local scope and in the global scope are different things, and the local one cannot be seen from another node while the global one can.

The pub/sub example works because of the combination of two features. Registering a property rather than a name means many subscribers can register the same key, since a non-unique property can be registered simultaneously by many processes. And `gproc:send` sends to all of them, which is what makes it a broadcast to a set rather than a message to one process.

That pairing is the reason gproc exists rather than a plain ETS table of registrations. A table could hold many entries per key, but the registry has to do the bookkeeping of who is registered, handle the atomic handover, and keep the per-scope consistency, and a hand rolled table that does that correctly is a project in itself.

The third use case, environment handling, is the one that is easier to overlook. Gproc provides a set of functions to read environment variables, possibly from alternative sources, and cache them for efficient lookup, and the caching also provides a way to see which processes rely on certain configuration values, as well as which values they actually ended up using.

That last point is the useful one. Caching an environment value is a performance measure, but recording which process read which value is an observability feature, and it is the sort of thing that turns a configuration lookup into something you can audit in a running system.

Dependencies that are mostly not fetched, and a build that checks itself

The project has an old fashioned Erlang build, and the details in the documentation and the Makefile show the era it comes from.

Installation is through the Hex package manager, which means declaring the dependency on `{gproc, "0.5.0"}` in a rebar3 based application or `{:gproc, "~> 0.5.0"}` in a mix based application.

The dependency situation is handled in a slightly unusual way. Gproc has two dependencies, `gen_leader` and `edown`, and since most people don't actively use either, they are no longer fetched by default. To enable fetching of `gen_leader`, you export the OS environment variable `GPROC_DIST=true`, which can be done for example from a GNU Makefile. And `edown` is fetched on demand whenever `rebar get-deps doc` is called, which happens when you call `make doc`.

So the distributed variant of the library, which presumably needs `gen_leader` for leader election across the cluster, is opt in, and the documentation tooling is fetched only when you build the documentation. That is a sensible arrangement for a library where most users are local only, and it means a plain `rebar3 compile` pulls a very small dependency tree.

The Makefile shows the checks the project runs on itself. The `all` target is `check test`, and `check` is `compile dialyzer`. There are separate targets for `compile`, `clean`, `eunit`, `test`, `doc` and `dialyzer`. The dialyzer target sets `GPROC_DIST=true` before running, so the type analysis runs against the distributed code as well as the local code.

The doc target is `$(REBAR3) as edown edoc`, and the documentation section explains the default. By default `./rebar doc` generates GitHub-flavored Markdown files, and if you want to change that, you remove the `edoc_opts` line from `rebar.config`.

The REBAR3 variable is resolved with a fallback, `REBAR3=$(shell which rebar3 || echo./rebar3)`, and there is a `rebar3` file at the top of the repository. So the project ships its own rebar3 for anyone who does not have one installed.

The testing story is worth a note. Gproc has a QuickCheck test suite, covering a fairly large part of the local gproc functionality, although none of the global registry. It requires a commercial EQC license, but rebar is smart enough to detect whether EQC is available, and if it isn't, the code in gproc_eqc.erl will be defined away. There is also an eunit suite, covering the basic operations for local and global gproc.

So the property based tests need a commercial licence to run, and the project degrades gracefully when the licence is absent. That is an honest constraint for a library from 2007 whose testing approach was ahead of the norm at the time.

The global registry and what it changes

The feature that distinguishes gproc from any local registry is the last item in the list, and it deserves to be separated out, because it changes the failure model rather than the API.

A global registry means the same operations, over a network of nodes. A process registered in the local scope of node A is invisible to node B. A process registered in the global scope is visible everywhere, and the registry is replicated.

That replication is the whole cost. A node that holds registrations has to keep them consistent when another node joins or leaves, and a registration that disappears with a crashed node has to be cleaned up, and a lookup from another node has to tolerate the network being slow or the node being gone. None of that is free, and all of it is why the global registry is the part of gproc that the test suite explicitly does not cover.

The documentation is honest about this, saying the QuickCheck suite covers a fairly large part of the local gproc functionality, although none of the global registry. So the property based testing, which is the strongest testing in the project, applies to the local case, and the global case is covered by the eunit suite for its basic operations.

For a user, the practical guidance is to keep the global scope for the cases where a process on one node genuinely has to be found from another, and to use the local scope for everything else. The local scope is cheaper, it is better tested, and it fails in the local way, where the process is gone, rather than in the distributed way, where the answer is a timeout.

The `gproc_dist` module, and the `GPROC_DIST=true` flag that fetches the `gen_leader` dependency it needs, exist for the global case. The fact that it is opt in rather than always fetched is the clearest signal that the distributed path is a specialist choice rather than the default one.

So the picture is a library with a cheap, well tested core and an expensive, less tested extension for the cases that need it. That is a reasonable design, and it is also the thing to check before you commit, because a global registry in a large cluster is a distributed systems problem wearing a small API.

The module list, and what the global registry costs

The modules list in the documentation is the clearest inventory of the library, and a few names in it explain the design.

There is `gproc` itself, and `gproc_app` and `gproc_sup`, which are the application and supervisor, the standard OTP wrapper. `gproc_init` is the initialisation, and `gproc_lib` is the shared helper library. `gproc_info` is the introspection module, and the fact that it is a separate module tells you that inspecting a running registry is a first class operation rather than an afterthought.

The cluster-facing modules are `gproc_dist` for the distributed variant, `gproc_bcast` for broadcasting, and `gproc_monitor` for monitoring. And then there is a group that is about the mechanics of a large registry: `gproc_pool` for pools of processes, and `gproc_ps` for the process list.

`gproc_pt` is the one that catches the eye, because `pt` stands for process table, and it is the local counterpart of the built-in `erlang:processes/0`. So gproc does not replace the process table, it indexes it, which is the right relationship between the two: the process table tells you what exists, and gproc tells you what each process is for.

The repository layout supports the same picture. There is a `src/` directory with the modules, an `include/` directory, a `priv/` directory, a `test/` directory, a `doc/` directory, and a `reference/` directory. There is also an `eqc/` directory for the QuickCheck specifications, an `ebin/` directory, a `tetrapak/` directory, and files called `zomp.ignore`, `zomp.meta` and `zompify.sh`, which look like tooling for a release workflow rather than for the library itself.

The `Emakefile` and the `dist_shell.config` point at the older Erlang build conventions, and `rebar.config.script` sits beside `rebar.config` for the generated parts of the build.

On the cost side, the honest assessment is that the global registry is the expensive part. It is the feature that makes gproc more than a local convenience, and it is the feature that has to be replicated across a network, kept consistent when nodes join and leave, and reasoned about when a node holding a registration disappears. The documentation is candid that the QuickCheck suite covers a fairly large part of the local functionality although none of the global registry, so the local behaviour is the well tested part.

The provenance is stated clearly. Gproc was first introduced at the ACM SIGPLAN Erlang Workshop in Freiburg in 2007, with the paper available in the repository's doc directory. Nearly twenty years later it is still the standard answer to the question of how to find a process by role in Erlang.

Editorial conclusion

gproc fits an Erlang system where processes need to be found by role and property rather than by a registered name, because it supports any term as an alias, lets one process answer to several aliases, and lets many processes register the same property, which the built in dictionary cannot do. Skip it if a simple `register` and a supervised name lookup are enough, since gproc is a registry with a much larger surface, and note the origin, since gproc was first introduced at the ACM SIGPLAN Erlang Workshop in 2007 and the dependency handling in the documentation still reflects that era. Before you adopt it, check whether the Erlang release you target still supports the global registry the way you need, and read the local versus global distinction carefully, because the global registry is where the cost and the failure modes live. The last push was on 2026-08-09.

Frequently asked questions

What is gproc and what does it add to the Erlang process dictionary?

It is a process dictionary for Erlang that adds using any term as a process alias, registering a process under several aliases, non-unique properties registered simultaneously by many processes, a QLC and match specification interface for queries, await registration, atomic handover of names and properties, counters with automatic totals, and a global registry with the same features across a network of nodes.

What does the {p, l, Name} notation mean in gproc?

It is a property in the local scope, with {p} meaning property and {l} meaning local, and the scope is what distinguishes a local registration from a global one that other nodes in the cluster can see and use.

How do I install gproc?

Through the Hex package manager, by declaring the dependency on `{gproc, "0.5.0"}` in a rebar3 based application or `{:gproc, "~> 0.5.0"}` in a mix based application. The two dependencies, gen_leader and edown, are no longer fetched by default.

How is gproc tested?

There is a QuickCheck suite covering a fairly large part of the local functionality, although none of the global registry, and it requires a commercial EQC license, with the code in gproc_eqc.erl defined away when it is not available. There is also an eunit suite covering the basic operations for local and global gproc.

When was gproc first introduced?

It was first introduced at the ACM SIGPLAN Erlang Workshop in Freiburg in 2007, with the paper available in the repository's doc directory. The licence is Apache-2.0 and the last push was on 2026-08-09.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. uwiger/gproc on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/uwiger-gproc.svg)](https://hysenlabs.com/projects/uwiger-gproc)

Was this useful?