Catalog Import & Resolve Worker
The master module (com.astralrealms.heads.AstralHeads extends MageHicPlugin, a headless MageHic platform plugin — not a Bukkit/Paper plugin) is the background process that keeps the shared heads catalog populated. It has no commands, actions, or placeholders; it exists purely to run two services started from onEnable: ImportService and ResolveService.
Pipeline
Only HASH-type rows are ever produced by ImportService today (the CSV import path); NAME/UUID rows — resolving a specific player's skin on demand — would need to be inserted into queued_heads by some other caller, but no such caller exists in this codebase.
Data model
master/src/main/resources/schema.sql defines both tables (shared with the paper module — see Overview — data model for heads).
queued_heads
Mapped to QueuedHead (@Entity("queued_heads")).
Column | Field | Notes |
|---|---|---|
|
| Primary key. |
|
|
|
| — | The lookup key, one populated per |
| — | Catalog metadata carried through to |
|
| Higher claims sooner; always |
|
|
|
|
| Incremented per failed resolve. |
|
| Truncated to 255 chars. |
|
| Claim gate for backoff; defaults to |
Resolvers
ResolveService holds one HeadResolver per QueuedHead.Type, each acquiring rate-limit tokens (unbounded wait — only the network call itself is timed by worker.request-timeout) before calling out:
Type | Resolver | Endpoint(s) | Behavior |
|---|---|---|---|
|
|
| Wraps |
|
| Session server only | Wraps |
|
|
| Issues a |
ResolveException#terminal() distinguishes a permanent failure (row will never succeed — marked FAILED immediately, acting as a negative cache) from a transient one (network blip, 5xx, timeout — retried with backoff). ResolveException.from(...) also infers terminal from the message text for exceptions raised outside a resolver (matching 204/400/404/410).
Rate limiting
RateLimiter enforces a separate requests-per-minute budget per Endpoint (SESSIONSERVER, NAME_LOOKUP, TEXTURE_CDN), configured under worker.rate-limits. The shipped defaults are already halved from Mojang's documented public limits as a safety margin (see Configuration below).
Configuration
The master module loads its own database.properties (pointing at the same heads schema — note it also sets useBulkStmts=false, unlike the paper module's database.properties) and config.yml, into HeadsConfiguration (a distinct record from the paper module's config of the same name).
Key | Type | Default | Description |
|---|---|---|---|
| String | (none — required) | The CSV catalog URL |
| Duration | (none — required) | How often |
| Duration |
| How often |
| int |
| Rows claimed per tick (also capped by remaining |
| int |
| Ceiling on concurrently-resolving rows. |
| int |
| Attempts before a row is marked |
| Duration |
| Retry backoff base; doubles per attempt. |
| Duration |
| Retry backoff ceiling. |
| Duration |
| Per-request timeout before a resolve attempt is treated as failed (not counted against the rate-limit wait). |
| Duration |
|
|
| int |
| Budget for |
| int |
| Budget for username→UUID lookups. |
| int |
| Budget for texture-CDN existence checks. |
Every worker.* field falls back to its default independently (WorkerSettings) when absent or non-positive, so a partial or missing worker: block never disables the worker — it just runs with defaults.