## Verified PluginAPI Mechanism
PluginAPI `0.9.2` exposes:
- `Plugin.enqueue(Runnable)` for server-thread dispatch
- `Plugin.isMainThread()` for execution-context checks
- `Plugin.executeDelayed(float, Runnable)` for delayed server-managed work
Tools owns `ServerThreadDispatcher`, a lifecycle-bound wrapper around
`isMainThread()` and `enqueue(...)`. Foreign callbacks must pass immutable data
into this dispatcher and must not retain Rising World API objects.
| Repository | Foreign-thread entry point | Current disposition |
| ------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Tools | `PluginFileWatcher` callbacks | Remediated through server-thread dispatcher |
| Tools | `PluginReloadDebouncer` | Remediated: worker dispatches before scheduling server-managed delayed execution |
| Tools | `WSClientEndpoint` callbacks | Foreign-thread transport callbacks; consumers must dispatch before game API access |
| Tools | Log4j JVM shutdown hook | No game API access |
| Discord Connect | JavaCord message listener | Remediated through server-thread dispatcher |
| Discord Connect | JavaCord slash-command listener | Remediated through server-thread dispatcher; blocking Discord response waits removed |
| Discord Connect | restart `java.util.TimerTask` | Remediated: timer enqueues game operations only |
| Discord Connect | activity `java.util.TimerTask` | Remediated through server-thread dispatcher |
| Discord Connect | screenshot callbacks | Remediated: stable IDs/immutable values only; notifications dispatched |
| Discord Connect | async HTTP callbacks and JavaCord sends | Remediated: immutable transport inputs on bounded lifecycle-owned worker; no retained game objects |
| Global Intercom | Tools WebSocket callbacks | Remediated through server-thread dispatcher |
| Global Intercom | screenshot callbacks | Remediated: transport-only callback, no retained player |
| Admin Utils | map capture worker | Safe pattern: game reads before worker; immutable data persistence on worker |
| Shop, Land Claim, GPS, Tools UI | PluginAPI `Timer` callbacks | Managed by PluginAPI `PluginTimerManager`; explicit thread guarantee absent, runtime verification required |
| Tools | `SQLiteCachedStore` flush timer | PluginAPI timer; database/cache only, no game objects |
| Rewards, Shop, GPS, Marketplace, Land Claim | reflection bridges | Synchronous plugin-to-plugin calls from existing game/plugin call paths; no owned worker threads |
The portfolio scan covered all Java sources outside generated `target/` and
- Java threads, executors, scheduled executors, futures, and `TimerTask`
- JavaCord message/slash listeners and asynchronous HTTP callbacks
- WebSocket handlers and reconnect workers
- PluginAPI `Timer` usage
- `FileChangeListener` implementations
- reflection/proxy plugin bridges
No additional owned Java worker was found that directly accesses Rising World
objects or static game APIs. Development runtime logging confirmed PluginAPI
`Timer` callbacks execute on the server thread.
Only thread termination or disappearance from JVM thread snapshots is
observable. The diagnostics cannot reliably determine whether a terminated
thread is already eligible for garbage collection or has been collected.
| Owner/source | Thread name | Creation frequency | API access | Shutdown behavior | Expected lifetime | Known risk |
| ----------------------------- | -------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------- | ----------------------------------------------- |
| Tools file watcher | `PluginFileWatcher-Thread` | once per Tools enable | filesystem only; callbacks dispatch to server thread | watcher and executor closed on Tools disable | Tools enable lifetime | blocked watch or missed shutdown |
| Tools reload debouncer | `PluginReloadDebouncer-Thread` | once per Tools enable, thread created on first task | dispatches reload request to server thread | scheduler stopped on Tools disable | idle/on-demand during Tools enable | delayed task retaining plugin callback |
| Tools WebSocket reconnect | `WebSocket-Reconnect` | once per endpoint instance, thread created on first work | transport callbacks only | all endpoints stopped on Tools disable | endpoint lifetime | library threads or reconnect churn |
| Tools Log4j shutdown hook | `OZ-Log4j-Terminator` | once per JVM | no game API access | runs only during JVM shutdown | JVM lifetime | intentionally remains across plugin reloads |
| Discord transport executor | `OZDiscordConnect-Transport` | once per Discord Connect enable, thread created on first work | transport only | bounded executor drained/stopped on disable | Discord Connect enable lifetime | blocking HTTP operations delaying shutdown |
| Discord restart timer | `OZDiscordConnect-RestartTimer` | once per Discord Connect enable | callback dispatches to server thread | tasks cancelled; timer cancelled and purged on disable | Discord Connect enable lifetime | fixed: former classloader/reload retention |
| Discord activity timer | `OZDiscordConnect-ActivityTimer` | once per Discord Connect enable | callback dispatches to server thread | tasks cancelled; timer cancelled and purged on disable | Discord Connect enable lifetime | fixed: former classloader/reload retention |
| Admin Utils map source worker | `OZAdminUtils-MapSource` | once per Admin Utils enable, thread created on first persistence task | immutable data persistence only | executor interrupted and awaited on disable | Admin Utils enable lifetime | database operation can delay shutdown |
| JavaCord | library-defined | library managed | Discord API only; listeners dispatch before game API access | `JavaCordBot.disconnect()` on Discord disable | bot connection lifetime | library-owned thread churn must be measured |
| WebSocket library | library-defined | per socket/connection as managed by library | transport only | socket disconnect through Tools endpoint shutdown | connection lifetime | library-owned thread churn must be measured |
| Apache Async HTTP | library-defined | currently per asynchronous client use | transport only | client closes after request scope | request lifetime | repeated client creation may cause thread churn |
| Screenshot callbacks | API-defined | per screenshot request | immutable values and transport; server notifications dispatch | API-managed completion | request lifetime | callback thread lifecycle is API-owned |
| PluginAPI timers | API-defined server/timer threads | per registered PluginAPI timer | plugin-specific; runtime observed on server thread | PluginAPI/plugin lifecycle managed | timer/plugin lifetime | explicit API thread guarantee is absent |
| PluginAPI server threads | API-defined | JVM/server managed | game API owner | server lifecycle managed | server lifetime | JVM-wide behavior is measured only |
## Diagnostic Instrumentation
OZTools `0.21.2` adds the default-disabled `threadDiagnosticsEnabled=false`
setting. When enabled, `OZ.ThreadDiagnostics` samples JVM threads every five
seconds, reports new and disappeared thread IDs, and writes grouped summaries
every minute. Tools-owned file watcher, reload debouncer, WebSocket reconnect,
Discord transport, and Admin Utils map source workers additionally log exact
creation, start, and end events through `DiagnosticThreadFactory`.
The Apache HTTP, JavaCord, WebSocket library, screenshot callback, and PluginAPI
thread implementations remain unchanged in this work package and are measured
## Runtime Validation Still Required
- Enable diagnostics on Development and perform repeated plugin reloads.
- Verify both Discord timer threads disappear after every disable/reload.
- Record JavaCord, WebSocket, Apache HTTP, and screenshot callback thread churn.
- Optionally enable diagnostics on Main and correlate summaries with later
## Development Observation 2026-06-14
Source: `local.res/2026-06-14-21-08-41.log`, approximately 21:08:41 through
21:17:14, without a plugin reload or crash.
- Diagnostics started successfully. Controlled long-lived workers appeared
exactly once: `PluginFileWatcher-Thread`, `WebSocket-Reconnect`,
`OZDiscordConnect-Transport`, and both Discord timer threads.
- After startup transients ended, the steady JVM-visible thread count was 29.
First screenshot/Java2D use raised the apparent steady count to about 31.
- Peak thread count was 35. Total-started count rose from 38 to 57 while
Discord messages and screenshots were exercised, but transient workers
subsequently disappeared and the current count returned toward the steady
baseline. No unbounded thread-count growth was observed.
- JavaCord/Okio repeatedly created short-lived `Javacord - Handlers Processor`,
`Javacord - Event Dispatch Queues Manager`, `Javacord - Central
ExecutorService - N`, and `Okio Watchdog` threads. Observed lifetimes were
approximately 60 to 80 seconds. This is measurable churn, not a confirmed
- Persistent JavaCord/WebSocket transport threads remained stable:
`Javacord - Central Scheduler - 1`, request-member consumer, frame sender,
`OkHttp discord.com`, one `OkHttp TaskRunner`, two `ReadingThread`, and two
- Generic daemon threads `Thread-8` through `Thread-12` appeared during player
connect/world initialization. `Thread-13` appeared after the first screenshot
and remained. Their stacks were empty at each sample, so ownership is not yet
proven. Additional screenshots did not create further generic threads in
- No Apache Async HTTP worker was long-lived enough to appear under an
identifiable Apache thread name at the five-second sampling interval.
- No Admin Utils map-source worker appeared, so that worker lifecycle was not
- No reload occurred, so disappearance and recreation of the fixed Discord
timer threads remains unverified by runtime logs.
- The only server warnings were three `ChunkMultiRequest: Unable to create
chunk!` messages during player/world loading. No Java exception, fatal error,
rejected transport task, queue overflow, or crash marker was present.
At 21:21:03, `rp` successfully initiated a full plugin reload. The captured log
continues only until approximately 21:21:16, while plugin enable was still in
- Controlled Tools and Discord executor workers logged exact shutdown:
`OZ-ThreadDiagnostics`, `PluginFileWatcher-Thread`, `WebSocket-Reconnect`,
and `OZDiscordConnect-Transport` all emitted `Thread ended`.
- The pre-reload Discord timers had IDs 43 and 44. Neither was present in the
first post-reload snapshot. New lifecycle-bound timers appeared with IDs 108
and 109. This runtime observation confirms the Discord timer leak fix for one
- New controlled workers were created after reload with new IDs:
`OZDiscordConnect-Transport` 132, diagnostics 133, file watcher 136, and
- The first post-reload snapshot still contained four old JavaCord/OkHttp
threads alongside newly created equivalents: `OkHttp discord.com` 59,
`OkHttp TaskRunner` 61, JavaCord request-member consumer 71, and JavaCord
frame sender 72. Later snapshots confirmed that all four disappeared. The
request-member consumer and frame sender ended after approximately 45 to 60
seconds; the old OkHttp connection and task runner remained for approximately
six minutes. This confirms delayed cleanup rather than a retained reload leak
- The old JavaCord scheduler and Discord WebSocket `ReadingThread` /
`WritingThread` IDs were absent and were replaced by new IDs, indicating that
most Discord connection lifecycle threads did terminate.
- Tools shuts down all logger contexts before later plugins finish disable and
before all plugins complete re-enable. This produced extensive
`OZTools.s is null` fallback messages and one Log4j default-configuration
warning from `Thread-13`. This is a confirmed reload-order logging problem,
separate from the Discord timer fix.
- File-watcher interruption during expected shutdown is logged as
`InterruptedException: null`; this is harmless shutdown noise but should not
be emitted at fatal/error severity.
### Extended Post-Reload Observation
The log continues through approximately 22:01:43, around 40 minutes after the
- Controlled plugin threads remained singular and stable. No duplicate Tools,
Discord timer, Discord transport, WebSocket reconnect, or Admin Utils map
worker remained after transients settled.
- JavaCord/OkHttp activity repeatedly raised the current count to 43-48 and the
total-started count to 131. The short-lived workers consistently disappeared
after roughly 60-95 seconds. Long-lived OkHttp connection/task pairs remained
for approximately 6-11 minutes before disappearing. This remains churn, not
- The stable current count after reload rose from approximately 34 to 38-40.
`OZAdminUtils-MapSource` accounts for one expected persistent thread.
- The remaining increase is a set of generic daemon threads with empty sampled
stacks: `Thread-24` through `Thread-30`. They appeared during ordinary world
interaction such as object/furnace use, damage, chunk/world-part generation,
terrain/vegetation changes, and storage work. `Thread-31` appeared when the
- Each new generic `Thread-N` was preceded by the server's native
`Create new JNIEnv` output on a previously observed native server thread.
This strongly suggests Rising World or PluginAPI-attached native callback
threads rather than plugin-owned Java executors. None of these generic
threads disappeared during the observation window. Their continuing
accumulation is the primary thread-lifecycle anomaly to correlate with
longer runs and future crashes.
- Managed memory remained broadly stable around 133-143 MB. Committed JVM
memory increased from approximately 128 MB to 175 MB but stayed far below the
2 GB maximum. Reported current native memory oscillated between approximately
9 MB and 480 MB, primarily because the reported active `Cpp` allocation
switched between zero and approximately 470 MB; it repeatedly returned to
the low state. The log does not demonstrate monotonic current-memory growth.
- No Java exception, fatal error, deadlock, rejected task, queue overflow,
SIGSEGV marker, or crash was logged in the extended window.
### Complete Run Through Regular Shutdown
The completed log continues through the scheduled shutdown at 00:00:09.
- `Thread-24` through `Thread-31` remained present in every final diagnostic
summary, including the last summary at approximately 23:59:54. No
`Thread disappeared` event was logged for any of them. They therefore
remained alive for hours after the player disconnected and until diagnostics
stopped during regular JVM shutdown.
- The steady pre-shutdown count remained at 39 threads. The generic daemon
threads account for the persistent increase observed after player activity;
the completed run does not support interpreting them as short-lived callback
- The controlled `OZ-ThreadDiagnostics` worker ended during plugin-manager
shutdown before Log4j termination. The completed run therefore confirms the
intended Tools diagnostic lifecycle on regular server shutdown.
- Discord/JavaCord initiated a fresh OkHttp connection and additional
JavaCord/OkHttp workers immediately before and during shutdown. The log ends
before diagnostics can observe their cleanup. This is shutdown-time churn,
not evidence of a retained thread after JVM termination, but should be
correlated with future shutdown and crash logs.
- The server reached `[END]` without a SIGSEGV or other crash marker.
## Main Crash Observation 2026-06-15
Sources: `local.res/2026-06-15-06-27-01.log` and
`local.res/diag-crash-01.log`.
- The diagnosed Main session crashed at 06:43:47, approximately 0.15 to
0.20 seconds after player `Boss` connected.
- The last summary before the crash was stable at `current=23`, `peak=32`, and
`totalStarted=41`. Every controlled Tools and Discord thread was singular.
- Only generic daemon `Thread-8` was present. The Development accumulation of
generic `Thread-24` through `Thread-31` was not present in this session.
- Native server threads created new `JNIEnv` handles during the player-connect
path immediately before the SIGSEGV. The full native trace enters
`checked_jni_SetObjectArrayElement` and the G1 SATB post-write barrier while a
native caller invokes a static Java boolean method.
- The crash therefore does not correlate with Java-visible thread-count growth
or an owned-plugin-thread leak. It remains compatible with a native
PluginAPI/JNI callback race or previously corrupted JNI state.
- The crash occurred before the next five-second sample, so diagnostics could
not determine whether either newly attached native thread would have appeared
as another persistent generic Java thread.
- A following clean session again created a single generic `Thread-8`, making
that thread routine rather than uniquely predictive of the crash.
- The previous Docker session reached `[END]` during a regular restart and then
received `SIGABRT`. This is a separate native shutdown-lifecycle anomaly.
## Remaining Inventory Work
- Complete the Discord slash-command request/result DTO split. Blocking
JavaCord response waits have already been removed.
- Replace parsed Global Intercom entity DTOs with immutable message values;
JSON parsing already occurs before server-thread dispatch.
- Add focused regression tests for remaining plugin-specific callback
boundaries. Dispatcher lifecycle and exception isolation are covered.