Skip to content

[Bug] Edge 4.1 cloud uplink does not recover after gRPC keepalive failure when EpollEventLoopGroup creation fails #229

Description

@bcblr1993

Describe the bug

On a ThingsBoard Edge 4.1.0-based deployment, the Edge-to-Cloud uplink stopped permanently although the host-to-cloud network was reachable and local data ingestion continued.

The first transport error was a gRPC keepalive failure. Every subsequent reconnect attempt (once per minute) failed locally with Cannot create Epoll EventLoopGroup. The Edge stayed up and continued storing cloud events locally, but no data was sent to the Cloud until the Edge process was restarted. After restart, the uplink and PostgreSQL history backfill resumed without any Cloud restart.

This report is based on the incident logs, a thread dump, a heap histogram, an Eclipse MAT leak-suspect report, process/cgroup snapshots, and a source review. Identifiers, addresses, and payloads have been removed.

Your Server Environment

  • Own setup, Docker on Linux x86_64
  • ThingsBoard Edge: custom build based on upstream v4.1 (4.1.0EDGE); the two relevant upstream classes below are byte-for-byte identical to tag v4.1
    • common/edge-api/.../EdgeGrpcClient.java
    • common/script/script-api/.../NashornJsInvokeService.java
  • Java: OpenJDK 17.0.15
  • Cloud RPC: port 7070 only
  • TELEMETRY_SEPARATION_ENABLED=false
  • Queue: in-memory (no Kafka)
  • Local cloud-event queue: PostgreSQL, with realtime-priority/backfill enabled
  • History storage: Cassandra
  • Latest-value storage: Cassandra
  • Host memory: approximately 32 GiB

Impact and timeline

  • Process started on July 7.
  • Last successful uplink was on July 9 at 19:08:49.
  • Persistent Cannot create Epoll EventLoopGroup started on July 9 at 19:10:08.
  • From July 10 through July 16, both errors occurred 1,440 times per day (approximately one reconnect attempt per minute), with no successful uplink.
  • Across the retained logs there were approximately 11,936 keepalive failures and 10,370 Epoll group creation failures.
  • The Edge was restarted on July 17 at 18:31; uplink and PostgreSQL history backfill resumed at 18:33.

Representative sanitized log sequence:

... Trying to reconnect due to error:
io.grpc.StatusRuntimeException: UNAVAILABLE: Keepalive failed. The connection is likely gone!

... Exception during connect: Cannot create Epoll EventLoopGroup

The same pair then repeated every minute. The current reconnect log records only ex.getMessage(), so the nested cause of the Epoll construction failure is missing.

Resource and heap evidence captured while affected

  • Container RSS: approximately 15.65 GiB
  • Java process swap: approximately 3.08 GiB
  • No Docker memory limit and no explicit -Xmx
  • Java threads: approximately 619
  • Open file descriptors: approximately 504 of 65,536
  • Sockets: 139; epoll FDs: 175
  • No Java deadlock in the thread dump
  • cgroup PID limit was unlimited
  • PostgreSQL cloud-event writes continued successfully and local events accumulated for uplink

The FD, PID/thread-limit, and local database snapshots therefore do not indicate an exhausted FD/PID limit or a local storage outage.

Eclipse MAT found two dominant org.openjdk.nashorn.internal.runtime.PropertyMap retainers:

  • 1,292,323,656 bytes (44.52%)
  • 1,282,165,656 bytes (44.17%)
  • Combined: approximately 2.574 GB / 88.69% of the reachable heap

The retained path is centered on:

PropertyMap.history -> WeakHashMap -> PropertyHashMap

The heap histogram included:

PropertyMap                         3,918,530
PropertyHashMap$Element             9,018,082
PropertyHashMap$Element[]           2,562,535

One MAT path reaches the retained Nashorn state through NashornSandboxImpl -> NashornJsInvokeService -> rule-engine consumer manager.

In upstream tag v4.1, NashornJsInvokeService.doRelease() releases a generated script function by assigning undefined:

sandbox.eval(scriptInfo.getFunctionName() + " = undefined;");

The observed heap suggests that repeated script load/release leaves Global-object shape history/property metadata retained even after the function value is cleared.

The runtime also emitted two sampled Netty leak-detector reports:

LEAK: ByteBuf.release() was not called before it's garbage-collected
MessageFramer.writeCompressed
  -> EdgeGrpcClient.sendUplinkMsg
  -> BaseCloudManagerService.sendUplinkMsgPack

This is additional evidence of resource pressure, but it is not claimed as the sole cause of the outage.

To Reproduce / validation performed

The long-running production trigger has not yet been reduced to a short deterministic upstream-only test.

  1. Run Edge 4.1 with local Nashorn JS evaluation/sandbox and a sustained stream of telemetry.
  2. Repeatedly load, replace, and release uniquely named JS rule scripts while watching PropertyMap, PropertyHashMap$Element, heap, RSS, and swap.
  3. After prolonged runtime/resource pressure, interrupt the Cloud RPC connection on port 7070 so the gRPC client must reconnect.
  4. Observe whether the original channel is shut down and a new EpollEventLoopGroup can be created.
  5. Restore network connectivity and verify whether the uplink self-recovers without restarting Edge.

Two controlled tests help narrow the behavior:

  • Network churn alone (43 disconnect/reconnect cycles) produced gRPC UNAVAILABLE errors and one sampled ByteBuf leak, but did not reproduce the persistent Epoll creation failure. This indicates that an ordinary network flap alone is insufficient.
  • Artificially forcing native-thread creation failure while closing the 7070 socket reproduced the same externally visible state: Edge HTTP stayed healthy, local PostgreSQL events continued accumulating, and the RPC uplink did not self-recover. This is only a symptom-level validation; the field snapshot did not have an exhausted PID limit.

Expected behavior

  • A keepalive failure should cause the old channel/event-loop resources to be fully released and the Edge should reconnect automatically after network recovery.
  • Repeated JS script load/release should not cause unbounded Nashorn PropertyMap retention.
  • If channel creation fails, the full nested exception should be logged and later reconnect attempts should recover when resources are available.
  • No Edge restart should be required to resume uplink/backfill.

Actual behavior

  • All reconnect attempts failed with Cannot create Epoll EventLoopGroup for several days.
  • Local PostgreSQL/Cassandra activity continued, but the 7070 Cloud uplink remained down.
  • Only restarting the Edge process released the condition and resumed history backfill.

Relevant source paths / investigation areas

  1. common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
    • A new NettyChannelBuilder/channel is built on every connect.
    • Please verify channel, executor, and event-loop termination across repeated disconnect/reconnect and send-in-progress races.
  2. common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java
    • doRelease() assigns undefined rather than deleting the generated Global property or periodically rebuilding the sandbox.
    • Please add a repeated eval/release regression test that verifies Nashorn property metadata is bounded.
  3. Edge reconnect error logging
    • Please log the complete exception (log.error(..., ex)) rather than only ex.getMessage() so native-memory, direct-memory, thread-creation, and underlying epoll IOException causes can be distinguished.
  4. Uplink buffer lifecycle
    • Please test disconnect during compressed batch send with Netty leak detection enabled.

Root-cause assessment and limits

The direct failure is well supported: after the gRPC keepalive failure, the Edge could no longer create the local Netty Epoll event-loop resources required for a new Cloud channel.

The strongest preceding resource-pressure evidence is the Nashorn heap retention (about 88.69% of the reachable heap), with sampled Netty ByteBuf leaks/reconnect churn as a possible additional contributor. However, because the reconnect code logs only the outer message, the existing evidence cannot prove whether the final Epoll constructor failed specifically because of native memory, direct memory, thread creation, or another underlying IOException.

A sanitized MAT report, histogram, thread dump, and selected log excerpts can be provided if maintainers specify a safe upload method. The original heap dump and logs contain customer data and cannot be posted publicly.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions