Skip to content

feat(maintenance): thread dump and info REST endpoints (#35205)#35463

Draft
hassandotcms wants to merge 1 commit intomainfrom
35205-task-implement-thread-dump-and-info-endpoints
Draft

feat(maintenance): thread dump and info REST endpoints (#35205)#35463
hassandotcms wants to merge 1 commit intomainfrom
35205-task-implement-thread-dump-and-info-endpoints

Conversation

@hassandotcms
Copy link
Copy Markdown
Member

@hassandotcms hassandotcms commented Apr 27, 2026

Adds GET /api/v1/maintenance/_threads and GET /api/v1/maintenance/_threads/info, returning structured JSON via Immutables-backed view classes. Replaces the legacy DWR ThreadMonitorTool which served HTML blobs and is unusable for the Angular Maintenance portlet rewrite. Both responses include serverId and serverName so the UI can identify which cluster node served the request.

This PR fixes: #35205

Adds GET /api/v1/maintenance/_threads and GET /api/v1/maintenance/_threads/info,
returning structured JSON via Immutables-backed view classes. Replaces the
legacy DWR ThreadMonitorTool which served HTML <pre> blobs and is unusable for
the Angular Maintenance portlet rewrite. Both responses include serverId and
serverName so the UI can identify which cluster node served the request.
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 27, 2026

Claude finished @hassandotcms's task in 1m 50s —— View job


PR Review: feat(maintenance) thread dump and info endpoints

  • Gather context
  • Read changed files
  • Analyze for issues
  • Post review findings

Issues Found

1. System.getProperty violates project coding standards

MaintenanceResource.java:844-845

.vmInfo(System.getProperty("java.vm.name") + " "
        + System.getProperty("java.runtime.version"))

CLAUDE.md explicitly forbids System.getProperty. Should use Config.getStringProperty("java.vm.name", ""). These are standard JVM system properties that Config passes through.

Fix this →


2. deadlockedCount is JVM-wide, but threads list is filtered — misleading combination

MaintenanceResource.java:779-849

deadlockedCount is set from mxBean.findDeadlockedThreads() (all JVM threads), but the threads list is filtered by hideSystem. If the deadlocked threads are pure JVM/system threads with no com.dotmarketing/com.dotcms frame, the response shows deadlockedCount > 0 but contains zero deadlocked threads in the list. A consumer seeing that would think there's a bug in the response.

Either filter deadlockedCount to match the thread list, or document explicitly that the count is always JVM-wide regardless of filtering.

Fix this →


3. Timestamp format is locale/timezone-sensitive

MaintenanceResource.java:843

.timestamp(new Date().toString())

Date.toString() uses the JVM's default timezone — different cluster nodes in different timezones produce non-comparable timestamps. Use Instant.now().toString() for unambiguous UTC ISO 8601.


4. SimpleDateFormat in getThreadInfo has no timezone

MaintenanceResource.java:894

final SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");

No timezone offset in the output. The schema docs say "server local time" but a UI comparing two cluster nodes in different timezones won't know which timezone each node used. Add z or Z to the format, or expose only startTimeMillis and let the UI format it.


5. Silent thread omission when a thread dies mid-dump

MaintenanceResource.java:795-799

final Thread thread = threadMap.get(info.getThreadId());
if (thread == null) {
    continue;  // silently dropped
}

dumpAllThreads() and Thread.getAllStackTraces() are two separate JVM snapshots. A thread alive during the first call can die before the second, causing it to be silently omitted — including its daemon/priority fields. The daemon and priority fields are only needed for display. A safer option is to use the values from ThreadInfo directly where available, or fall back to defaults (daemon=false, priority=5) rather than dropping the thread entirely.


6. No size guard on dumpAllThreads(true, true)

MaintenanceResource.java:777

Calling dumpAllThreads(true, true) collects locked monitors and synchronizers for every thread. On a production server with 400+ threads and deep stack traces this response can be several MB. No truncation, no pagination, no max-stack-depth parameter. The hideSystem=true default helps but doesn't bound the size. Worth adding a maxStackDepth parameter or at least a stack-frame truncation limit.


7. OpenAPI schema exposes internal Immutables list properties

In openapi.yaml, the generated schemas for ThreadDumpView.threads, ThreadDescriptorView.stackTrace, lockedMonitors, lockedSynchronizers, and ImmutableListThreadDescriptorView all expose first, last, and empty as properties:

properties:
  empty:
    type: boolean
  first:
    $ref: "#/components/schemas/ThreadDescriptorView"
  last:
    $ref: "#/components/schemas/ThreadDescriptorView"

These are internal ImmutableList accessor methods, not real response fields. They'll confuse API consumers. This appears to be a known artifact of how the Swagger generator processes Immutables collections — but it's still noise in the published spec.


Summary

The implementation is solid overall — auth checks are consistent, Immutables usage follows existing patterns, and the integration tests are meaningful. The blocking issue is the System.getProperty violation. The deadlockedCount mismatch under filtering is a real semantic bug. The timestamp/timezone issues are correctness concerns for multi-node clusters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[TASK] Implement thread dump and info endpoints

1 participant