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.
- Run Edge 4.1 with local Nashorn JS evaluation/sandbox and a sustained stream of telemetry.
- Repeatedly load, replace, and release uniquely named JS rule scripts while watching
PropertyMap, PropertyHashMap$Element, heap, RSS, and swap.
- After prolonged runtime/resource pressure, interrupt the Cloud RPC connection on port 7070 so the gRPC client must reconnect.
- Observe whether the original channel is shut down and a new
EpollEventLoopGroup can be created.
- 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
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.
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.
- 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.
- 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.
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
v4.1(4.1.0EDGE); the two relevant upstream classes below are byte-for-byte identical to tagv4.1common/edge-api/.../EdgeGrpcClient.javacommon/script/script-api/.../NashornJsInvokeService.javaTELEMETRY_SEPARATION_ENABLED=falseImpact and timeline
Cannot create Epoll EventLoopGroupstarted on July 9 at 19:10:08.Representative sanitized log sequence:
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
-XmxThe 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.PropertyMapretainers:The retained path is centered on:
The heap histogram included:
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 assigningundefined: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:
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.
PropertyMap,PropertyHashMap$Element, heap, RSS, and swap.EpollEventLoopGroupcan be created.Two controlled tests help narrow the behavior:
UNAVAILABLEerrors and one sampled ByteBuf leak, but did not reproduce the persistent Epoll creation failure. This indicates that an ordinary network flap alone is insufficient.Expected behavior
PropertyMapretention.Actual behavior
Cannot create Epoll EventLoopGroupfor several days.Relevant source paths / investigation areas
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.javaNettyChannelBuilder/channel is built on every connect.common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.javadoRelease()assignsundefinedrather than deleting the generated Global property or periodically rebuilding the sandbox.log.error(..., ex)) rather than onlyex.getMessage()so native-memory, direct-memory, thread-creation, and underlying epollIOExceptioncauses can be distinguished.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
ByteBufleaks/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 underlyingIOException.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.