CDI container and component lifecycle

After 0.0.8. The storage repair job, RepairService, is on the master branch and not in release 0.0.8, where a node starts the peer-to-peer listener and the REST interface eagerly.

A Hedgehog node is a single program that runs several long-lived services at once: a peer-to-peer listener, a local web interface, a record of the network’s parameters and a storage repair job. This page describes the part that creates those services, starts them in the right order and shuts them down cleanly, so that one daemon command is enough to bring a whole node to life.

Hedgehog is written in Java and delegates this work to a standard dependency-injection container (CDI, implemented by Weld). Each service is declared as a managed component, the container builds them and hands each one the others it needs, and nothing in the node wires itself together by hand. The wider picture is in the Architecture overview.

How a node starts

The hedgehog daemon command reads its options, starts the container and then simply waits. Everything that matters happens while the container starts:

sequenceDiagram
    autonumber
    participant M as Hedgehog.main
    participant PC as picocli
    participant W as Weld container
    participant EE as EagerExtension
    participant S as P2PServer, RestServer,<br/>RepairService

    M->>PC: execute(args)
    PC->>PC: bind NetOptions / RestOptions
    PC->>W: Daemon.run() starts the container
    W->>W: discover components
    W->>EE: ProcessBean for every component
    EE->>EE: remember those marked @Eager
    W->>EE: AfterDeploymentValidation
    EE->>S: create now (@PostConstruct)
    S->>S: bind P2P and REST ports,<br/>start topology thread and repair job
    W->>W: install shutdown hook, fire ContainerInitialized
    W-->>PC: container returned, main thread waits

The container creates components lazily, on first use, so a component that must be running from the start is marked @Eager. A small extension, EagerExtension, finds those components once discovery is finished and creates them before the container reports that it is ready. This is how the peer-to-peer server, the REST server and the storage repair job are already running when startup completes.

Shared state and its guards

The list of known peers (Topology) and the map from open connections to peers (ChannelMap) are used by many threads. Their methods carry @Protected and @Lock annotations, and ProtectedInterceptor turns those into a read-write lock: any number of readers at once, or a single writer. There is one lock per component instance, so the topology and the channel map do not block each other.

Some objects are not created by the container at all. Network handlers and scheduled tasks are built by the networking library, and the REST resources by the web framework, so they look their dependencies up in the container on demand (CDIUtil, CDIBridgeResource). The same pattern lets the REST layer reach the peer network described in Peer-to-peer network protocol.

Some values are produced rather than declared. The application directory is created per use, and the spork database is shared by every user of it and loaded from disk on first use. Sporks are the signed network parameters described in Grid sporks; the container saves the database again when it shuts down.

How a node stops

POST /stop on the local REST interface, or the hedgehog cli stop command, wakes the waiting main thread. The program exits, the container’s shutdown hook runs, and each component’s @PreDestroy method closes its sockets and threads. The spork database is written to disk at that point.

Key facts

Item Value
Peer-to-peer listener QUIC over UDP, default 0.0.0.0:52883
REST interface default localhost:52884
Components started eagerly P2PServer, RestServer, RepairService
Event-loop threads 4 per server
QUIC data limit 256 MiB per connection and stream
QUIC streams and idle timeout 512 streams, 15 minutes
hedgehog subcommands bootstrap, cli, daemon, util

In tests

The test suite starts real containers instead of mocking components. Each test class gets its own container, and the server tests start 20 independent ones to check that nodes bind distinct ports. A test container does not start the eager components unless it asks for the extension, so tests start their servers explicitly. How the suite is built and run is covered in Build, testing and native image.

In the source

  • application/src/main/java/org/unigrid/hedgehog/Hedgehog.java - the entry point and its subcommands.
  • application/src/main/java/org/unigrid/hedgehog/command/Daemon.java - the daemon command.
  • application/src/main/java/org/unigrid/hedgehog/model/cdi/CDIContext.java - starts the container and waits.
  • application/src/main/java/org/unigrid/hedgehog/model/cdi/EagerExtension.java - eager component startup.
  • application/src/main/java/org/unigrid/hedgehog/model/cdi/ProtectedInterceptor.java - the read-write guard.
  • application/src/main/java/org/unigrid/hedgehog/server/p2p/P2PServer.java - the peer-to-peer listener.
  • application/src/main/java/org/unigrid/hedgehog/server/rest/RestServer.java - the REST server.
  • application/src/main/java/org/unigrid/hedgehog/model/producer/ - producers for shared objects.